index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. <template>
  2. <div class="container">
  3. <van-nav-bar
  4. title="海康上架-调度" left-arrow fixed placeholder @click-left="goBack">
  5. <template #left>
  6. <van-icon name="arrow-left" size="25" />
  7. <div style="color: #fff">返回</div>
  8. </template>
  9. <template #right>
  10. <div class="nav-right" style="color: #fff" @click="onClickRight">释放分拨墙</div>
  11. </template>
  12. </van-nav-bar>
  13. <div class="allocation">
  14. <div class="code">
  15. <div class="code-title">
  16. <div><span style="font-size: 12px">容器:</span>{{ containerNo || '--' }}</div>
  17. <div class="code-tips">
  18. <van-notice-bar :background="'none'" :speed="50" :text="tips" />
  19. </div>
  20. <van-button plain size="mini" type="primary" @click="_setContainerNo()">切换容器</van-button>
  21. </div>
  22. <div class="code-input">
  23. <van-search
  24. v-model="wallNo"
  25. placeholder="请扫描分拨墙号"
  26. label="分拨墙号:"
  27. @search="scanType=2"
  28. left-icon=""
  29. :class="[scanType===3?'search-input-barcode':'','van-hairline--bottom']"
  30. @focus="scanType=3"
  31. autocomplete="off"
  32. >
  33. </van-search>
  34. <van-search
  35. ref="searchRef"
  36. v-model="searchBarcode"
  37. placeholder="请扫描商品条码"
  38. @search="_handlerScan(searchBarcode)"
  39. label="商品条码:"
  40. left-icon=""
  41. :class="[scanType===2?'search-input-barcode':'','van-hairline--bottom']"
  42. @focus="scanType=2"
  43. autocomplete="off"
  44. >
  45. </van-search>
  46. <van-search
  47. class="code-bin"
  48. ref="searchRef"
  49. v-model="bin"
  50. placeholder="分配格口"
  51. label="分配格口:"
  52. readonly
  53. left-icon=""
  54. >
  55. </van-search>
  56. </div>
  57. </div>
  58. <van-divider :style="{ color: '#333', borderColor: '#1989fa', padding: '0 16px',margin:'5px 0' }">上架库位
  59. </van-divider>
  60. <div class="move-stock-list">
  61. <table class="task-table">
  62. <thead>
  63. <tr>
  64. <th>库位</th>
  65. <th>类型</th>
  66. <th>数量</th>
  67. <th style="width: 80px"></th>
  68. </tr>
  69. </thead>
  70. <tbody>
  71. <tr v-for="(item, index) in locationActive" :key="index" v-if="locationActive.length>0">
  72. <td>{{ item.location }}</td>
  73. <td>{{ locationType[item.type] || item.type }}</td>
  74. <td>{{ item.quantity || 0 }}</td>
  75. <td>
  76. <!-- <van-button type="primary" plain size="mini" style="width:100%" @click="setLocation(item)" v-if="!locationActive.location">选择</van-button>-->
  77. <div>
  78. <van-icon size="24" color="#07c160" name="success" />
  79. </div>
  80. </td>
  81. </tr>
  82. <tr v-else>
  83. <td colspan="4">
  84. <div>暂无数据</div>
  85. </td>
  86. </tr>
  87. </tbody>
  88. </table>
  89. </div>
  90. </div>
  91. <!-- 条码输入组件 -->
  92. <input-barcode :back="back" @setBarcode="setBarcode" ref="inputBarcodeRef" />
  93. <!-- 单据选择-->
  94. <van-action-sheet v-model:show="lotBarcodeTrueFalseBy" cancel-text="取消" description="请选择商品批次"
  95. close-on-click-action>
  96. <van-cell-group>
  97. <van-cell v-for="item in lotBarcodeList"
  98. @click="_getRecommendedLocation(item.lotNumber,item.owner);barcodeActive=item;lotBarcodeTrueFalseBy=false">
  99. <template #title>
  100. {{ item.barcode }}({{ item.quantity }}件)
  101. </template>
  102. <template #label>
  103. 生产日期:{{ item.lotAtt01 || '--' }}-失效日期:{{ item.lotAtt02 || '--' }}
  104. </template>
  105. </van-cell>
  106. </van-cell-group>
  107. </van-action-sheet>
  108. </div>
  109. </template>
  110. <script setup lang="ts">
  111. import { onMounted, onUnmounted, ref } from 'vue'
  112. import { showConfirmDialog, showNotify, showToast } from 'vant'
  113. import { androidFocus, getHeader, goBack, playVoiceBin, scanError, scanSuccess } from '@/utils/android'
  114. import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
  115. import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
  116. import { useStore } from '@/store/modules/user'
  117. import { finishTask, getRecommendedLocation, getWaitPutawayList, setBindAllocateWall } from '@/api/haikang'
  118. import { barcodeToUpperCase } from '@/utils/dataType'
  119. import { closeLoading, showLoading } from '@/utils/loading'
  120. try {
  121. getHeader()
  122. androidFocus()
  123. } catch (error) {
  124. }
  125. // 页面初始化
  126. onMounted(() => {
  127. openListener()
  128. scanInit(_handlerScan)
  129. loadData()
  130. })
  131. const store = useStore()
  132. const warehouse = store.warehouse
  133. //收货容器号
  134. const containerNo = ref('')
  135. //分拨墙号
  136. const wallNo = ref('')
  137. //扫描类型
  138. const scanType = ref(3)
  139. //条码
  140. const searchBarcode = ref('')
  141. // 错误提示
  142. const tips = ref('')
  143. //强制返回
  144. const back = ref(true)
  145. //输入框类型
  146. const inputBarcodeRef = ref(null)
  147. const inputBarcodeType = ref('barcode')
  148. const location=ref('')
  149. //库位类型
  150. const locationType = {
  151. 'EA': '件拣货库位',
  152. 'AP': '补充拣货位',
  153. 'CS': '箱拣货库位',
  154. 'HP': '快拣补货位',
  155. 'PC': '箱/件合并拣货库位',
  156. 'PT': '播种库位',
  157. 'RS': '存储库位',
  158. 'SS': '理货站',
  159. 'ST': '过渡库位',
  160. 'WB': '组装工作区',
  161. }
  162. // 格口数据
  163. const bin = ref('') //当前格口
  164. //推荐库位
  165. const locationList = ref([])
  166. const dataMap = ref({})
  167. // 设置容器号
  168. const setBarcode = (code) => {
  169. if (inputBarcodeType.value === 'barcode') {
  170. _handlerScan(code)
  171. return
  172. }
  173. const params = { warehouse, container: code, page: 1, size: 1000, status: false }
  174. getWaitPutawayList(params).then(res => {
  175. if (res.data.records.length == 0) {
  176. scanError()
  177. inputBarcodeRef.value?.show('', '请扫描收货容器号', '暂未查询到待上架任务,请切换容器')
  178. } else {
  179. scanSuccess()
  180. dataMap.value = groupedData(res.data.records)
  181. scanType.value = 3
  182. containerNo.value = code
  183. }
  184. }).catch(err => {
  185. scanError()
  186. inputBarcodeRef.value?.show('', '请扫描收货容器号', err.message)
  187. })
  188. }
  189. //切换容器
  190. const _setContainerNo = () => {
  191. inputBarcodeType.value = 'container'
  192. back.value = false
  193. inputBarcodeRef.value?.show('', '请扫描收货容器号', '')
  194. }
  195. //批次数据
  196. const lotBarcodeList = ref([])
  197. const lotBarcodeTrueFalseBy = ref(false)
  198. const barcodeActive = ref({})
  199. // 扫描条码监听
  200. const _handlerScan = (code) => {
  201. if (code) {
  202. if (scanType.value == 2) {
  203. if (!wallNo.value) {
  204. tips.value = `请先扫描分拨墙号`
  205. showToast({ duration: 3000, message: '请先扫描分拨墙号' })
  206. scanType.value = 3
  207. searchBarcode.value = ''
  208. bin.value = ''
  209. locationActive.value = []
  210. scanError()
  211. return
  212. }
  213. scanSuccess()
  214. searchBarcode.value = code
  215. barcodeActive.value = {}
  216. lotBarcodeList.value = matchingBarcodeItem(dataMap.value, code)
  217. locationActive.value = []
  218. bin.value = ''
  219. if (lotBarcodeList.value.length > 0) {
  220. if (lotBarcodeList.value.length == 1) {
  221. barcodeActive.value = lotBarcodeList.value[0]
  222. _getRecommendedLocation(lotBarcodeList.value[0].lotNumber, lotBarcodeList.value[0].owner)
  223. } else if (lotBarcodeList.value.length > 1) {
  224. locationList.value = []
  225. lotBarcodeTrueFalseBy.value = true
  226. }
  227. } else {
  228. scanError()
  229. tips.value = `${code}-商品条码不匹配,请重新扫描`
  230. showNotify({ type: 'danger', duration: 3000, message: `${code}-商品条码不匹配,请重新扫描` })
  231. searchBarcode.value = ''
  232. locationList.value = []
  233. }
  234. } else if (scanType.value == 3) {
  235. wallNo.value = code
  236. scanType.value = 2
  237. }
  238. }
  239. }
  240. //匹配待上架列表数据
  241. const matchingBarcodeItem = (data, barcode) => {
  242. const matchingItems = []
  243. for (const key in data) {
  244. const barcodeList = key.match(/\((.*?)\)/)[1].split('、')
  245. if (data.hasOwnProperty(key)) {
  246. if (barcodeList.some(item => barcodeToUpperCase(item) === barcodeToUpperCase(barcode))) {
  247. matchingItems.push(data[key])
  248. }
  249. }
  250. }
  251. return matchingItems.length > 0 ? matchingItems : []
  252. }
  253. // 获取库存数据
  254. const _getRecommendedLocation = async (lotNum, owner) => {
  255. try {
  256. const params = { warehouse, lotNum, owner,zoneGroup:'WH01-01' }
  257. const res = await getRecommendedLocation(params)
  258. locationList.value = res.data
  259. // 'EA'数据
  260. const eaItems = res.data.filter(item => item.type === 'EA')
  261. // 获取 quantity < 300 的
  262. let result = eaItems.find(item => item.quantity !== null && item.quantity < 1000)
  263. // 获取 quantity 为 null 的
  264. if (!result) {
  265. result = eaItems.find(item => item.quantity === null)
  266. }
  267. if (result) {
  268. if(result.quantity===null){
  269. scanError()
  270. tips.value = `${searchBarcode.value}:库区内无商品库存,请调空料箱进行入库`
  271. showNotify({ type: 'danger', duration: 3000, message: `${searchBarcode.value}:库区内无商品库存,请调空料箱进行入库` })
  272. searchBarcode.value = ''
  273. scanType.value=2
  274. return
  275. }
  276. setLocation([result])
  277. }
  278. } catch (err) {
  279. console.error(err)
  280. }
  281. }
  282. // 设置库位
  283. const locationActive = ref([])
  284. const wallBinCounts = ref(new Map())
  285. const setLocation = (item) => {
  286. locationActive.value = item
  287. const data = {
  288. warehouse,
  289. equipment: wallNo.value,
  290. container: containerNo.value,
  291. barcode: barcodeActive.value.barcode,
  292. barcodeAS: barcodeActive.value.barcodeAs,
  293. asnCode: barcodeActive.value.asnNo,
  294. sku: barcodeActive.value.sku,
  295. location: locationActive.value[0].location,
  296. lotNum: barcodeActive.value.lotNumber,
  297. }
  298. showLoading()
  299. setBindAllocateWall(data).then(res => {
  300. bin.value =res.data
  301. tips.value = `请将${searchBarcode.value},放入分拨墙-${wallNo.value}-《${res.data}》格口`
  302. playVoiceBin(Number(bin.value))
  303. closeLoading()
  304. }).catch(err => {
  305. searchBarcode.value = ''
  306. locationActive.value = []
  307. tips.value = err.message
  308. scanError()
  309. closeLoading()
  310. })
  311. }
  312. // 数据刷新
  313. const loadData = () => {
  314. if (!containerNo.value) {
  315. inputBarcodeType.value = 'container'
  316. inputBarcodeRef.value?.show('', '请扫描收货容器号', '')
  317. return
  318. } else {
  319. const params = { warehouse, container: containerNo.value, page: 1, size: 1000, status: false }
  320. getWaitPutawayList(params).then(res => {
  321. dataMap.value = groupedData(res.data.records)
  322. })
  323. }
  324. }
  325. //根据条码批次分组数据
  326. const groupedData = (data) => {
  327. return data.reduce((acc, item) => {
  328. const key = `(${item.barcode}、${item.barcodeAs}、${item.sku})-${item.lotNumber}`
  329. if (acc[key]) {
  330. acc[key].quantity += item.quantity
  331. } else {
  332. acc[key] = { ...item }
  333. }
  334. return acc
  335. }, {})
  336. }
  337. onUnmounted(() => {
  338. closeListener()
  339. })
  340. //删除分拨
  341. const onClickRight = () => {
  342. showConfirmDialog({
  343. title: '温馨提示',
  344. message:'您正在进行释放分拨墙是否继续?',
  345. keyboardEnabled:false
  346. }).then(() => {
  347. showLoading()
  348. const params={warehouse,container:containerNo.value}
  349. finishTask(params).then(res=>{
  350. showNotify({ type: 'success', duration: 3000, message: `解绑成功` })
  351. scanSuccess()
  352. reset()
  353. loadData()
  354. }).catch(err=>{
  355. scanError()
  356. }).finally(() => {
  357. closeLoading()
  358. })
  359. }).catch(() => {})
  360. }
  361. const reset=()=>{
  362. containerNo.value=''
  363. wallNo.value=''
  364. scanType.value=3
  365. searchBarcode.value = ''
  366. bin.value=''
  367. locationActive.value = []
  368. tips.value='请扫描容器号'
  369. }
  370. window.onRefresh = loadData
  371. </script>
  372. <style scoped lang="sass">
  373. .container
  374. background: #e9f4ff
  375. .allocation
  376. .code
  377. box-sizing: border-box
  378. padding: 8px 0
  379. .code-title
  380. display: flex
  381. justify-content: space-between
  382. padding: 0 15px 8px 15px
  383. .code-input
  384. ::v-deep(.van-search)
  385. padding: 0
  386. ::v-deep(.van-search__label)
  387. font-size: 16px
  388. ::v-deep(.van-search__field)
  389. border-bottom: 2px solid #efefef
  390. font-size: 16px
  391. ::v-deep(.van-search__content)
  392. background: #fff
  393. .search-input-barcode
  394. ::v-deep(.van-search__field)
  395. border-bottom: 2px solid #0077ff
  396. z-index: 2
  397. .code-bin
  398. ::v-deep(.van-field__control)
  399. font-size: 20px
  400. color: #ff0d00
  401. .code-tips
  402. color: #ed6a0c
  403. flex: 1
  404. .code-count
  405. font-size: 16px
  406. font-weight: bold
  407. span
  408. color: #0077ff
  409. .location
  410. background: #fff
  411. text-align: left
  412. padding: 10px 15px
  413. margin-bottom: 10px
  414. font-size: 14px
  415. .move-stock-list
  416. width: 100%
  417. overflow-y: auto
  418. max-height: 60vh
  419. min-height: 100px
  420. .move-button
  421. background: #1989fa
  422. color: #fff
  423. width: 100%
  424. height: 30px
  425. font-size: 15px
  426. line-height: 30px
  427. font-weight: bold
  428. .task-table, .task-table-bin, .task-table-box
  429. width: 100%
  430. table-layout: fixed
  431. border-collapse: collapse
  432. font-size: 15px
  433. .task-table th, .task-table-bin th, .task-table td, .task-table-bin td, .task-table-box th, .task-table-box td
  434. text-align: center
  435. border: 1px solid #ccc
  436. word-wrap: break-word
  437. word-break: break-all
  438. .task-table thead, .task-table-bin thead, .task-table-box thead
  439. background-color: #3f8dff
  440. position: sticky
  441. top: 0
  442. color: white
  443. font-size: 15px
  444. .task-table-bin thead
  445. background-color: #3f8dff
  446. .task-table-bin tbody
  447. background: #cde7ff
  448. .nav-right
  449. padding: 14px 0 12px 5px
  450. color: #fff
  451. </style>