Explorar o código

调拨-入库

zhaohuanhuan hai 8 meses
pai
achega
cc4d8b9439

+ 59 - 0
src/api/transferMove/index.ts

@@ -0,0 +1,59 @@
+// @ts-ignore
+import request from '@/utils/request'
+// @ts-ignore
+import {getMoveTaskListType} from '@/types/transferMove'
+/**
+ * 调拨-移库任务列表
+ * @param params
+ */
+export function getMoveTaskList(params:getMoveTaskListType) {
+  return request({
+    url: 'api/wms/app/move-task/query',
+    method: 'get',
+    params
+  })
+}
+/**
+ * 调拨-移库下架任务
+ * @param params
+ */
+export function getMoveTaskDown(code:any) {
+  return request({
+    url: `/api/wms/app/move-task/down/${code}`,
+    method: 'get',
+  })
+}
+/**
+ * 调拨-移库下架
+ * @param params
+ */
+export function moveTaskDown(data:any) {
+  return request({
+    url: '/api/wms/app/move-task/down',
+    method: 'post',
+    data,
+  })
+}
+/**
+ * 调拨-移库上架任务
+ * @param params
+ */
+export function getMoveTaskUp(code:any) {
+  return request({
+    url: `/api/wms/app/move-task/up/${code}`,
+    method: 'get',
+  })
+}
+
+
+/**
+ * 调拨-移库上架
+ * @param params
+ */
+export function moveTaskUp(data:any) {
+  return request({
+    url: '/api/wms/app/move-task/up',
+    method: 'post',
+    data,
+  })
+}

+ 18 - 1
src/router/index.ts

@@ -79,7 +79,24 @@ const routes: RouteRecordRaw[] = [
     meta:{title:'海康-入库'},
     component: () => import('@/views/haikang/boxReturn/boxReturn/index.vue')
   },
-
+  {
+    path: '/move-list',
+    name: 'MoveList',
+    meta:{title:'移库列表'},
+    component: () => import('@/views/transfer/move/list/index.vue')
+  },
+  {
+    path: '/move-down',
+    name: 'MoveDown',
+    meta:{title:'移库下架'},
+    component: () => import('@/views/transfer/move/down/index.vue')
+  },
+  {
+    path: '/move-putaway',
+    name: 'MovePutaway',
+    meta:{title:'移库上架'},
+    component: () => import('@/views/transfer/move/putaway/index.vue')
+  },
 ];
 
 // 创建路由实例

+ 11 - 0
src/types/transferMove.ts

@@ -0,0 +1,11 @@
+
+/**
+ * 调拨-移库任务列表
+ */
+export interface getMoveTaskListType {
+  /**
+   * 仓库编码
+   */
+  warehouseCode?: string;
+  [property: string]: any;
+}

+ 1 - 0
src/views/index.vue

@@ -11,6 +11,7 @@
     <div class="home" @click="onRouter('hik-putaway-allocation')">海康上架-调度</div>
     <div class="home" @click="onRouter('hik-putaway')">海康上架</div>
     <div class="home" @click="onRouter('hik-box-return')">海康-入库/解绑</div>
+    <div class="home" @click="onRouter('move-list')">调拨-移库</div>
   </div>
 </template>
 <script setup>

+ 485 - 0
src/views/transfer/move/down/index.vue

@@ -0,0 +1,485 @@
+<template>
+  <div class="container">
+    <div class="task">
+      <div class="top">
+        <div class="nav-bar">
+          <van-nav-bar title="移库任务-下架" left-arrow @click-left="goBack" @click-right="onClickRight" >
+            <template #left>
+              <van-icon name="arrow-left" size="25" />
+              <div style="color: #fff;height: 46px;padding-right:20px;line-height: 46px">返回</div>
+            </template>
+            <template #right>
+              <div class="nav-right" style="color: #fff">重置</div>
+            </template>
+          </van-nav-bar>
+        </div>
+        <div class="content">
+          <div class="take-delivery">
+            <div class="take-info">
+              <div class="take-info-no">
+                <div class="info-no-title">
+                  <div>任务号:{{ code || '--' }}</div>
+                  <div v-if="taskDetail.expectQuantity">
+                    <div><span style="font-size: 13px">任务数:</span><span
+                      style="color: #0077ff;font-weight: bold;">{{ taskDetail.doneQuantity || 0
+                      }}/{{ taskDetail.expectQuantity || 0 }}</span></div>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <van-progress v-if="taskDetail.expectQuantity && taskDetail.doneQuantity/taskDetail.expectQuantity>=0"
+                          :percentage="((taskDetail.doneQuantity/taskDetail.expectQuantity)*100).toFixed(2)"
+                          stroke-width="4" />
+            <div class="take-barcode">
+              <div style="display: flex;justify-content: space-between;padding-right: 10px;">
+                <div style="flex: 1">
+                  <van-notice-bar :background="'none'" :speed="50" :text="tips"  />
+                </div>
+                <div>箱规:{{activeBarcode.packId || '--'}}</div>
+              </div>
+              <div class="barcode-input">
+                <van-search
+                  ref="searchRef"
+                  v-model.lazy="searchBarcode"
+                  placeholder="请扫描商品条码"
+                  @search="_handlerScan(searchBarcode)"
+                  label="商品条码:"
+                  left-icon=""
+                  :class="[scanType===2?'search-input-barcode':'','van-hairline--bottom']"
+                  @focus="scanType=2"
+                  autocomplete="off"
+                >
+                </van-search>
+              </div>
+              <div class="barcode-input">
+                <van-search
+                  ref="searchRef"
+                  v-model.lazy="searchLocation"
+                  placeholder="请扫描移出库位"
+                  @search="_handlerScan(searchLocation)"
+                  label="移出库位:"
+                  left-icon=""
+                  :class="[scanType===3?'search-input-barcode':'','van-hairline--bottom']"
+                  @focus="scanType=3"
+                  autocomplete="off"
+                >
+                </van-search>
+              </div>
+              <div class="barcode-input">
+                <van-search
+                  ref="numberRef"
+                  v-model="count"
+                  placeholder="请输入移出数量"
+                  type="number"
+                  label="移出数量:"
+                  left-icon=""
+                  autocomplete="off"
+                  show-action
+                  :min="1"
+                  :class="[scanType===4?'search-input-barcode':'','van-hairline--bottom']"
+                  @focus="scanType=4"
+                  :max="activeBarcode.id?activeBarcode.remainDownQuantity:10000"
+                  @search="onConfirm"
+                >
+                  <template #action >
+                    <div style="display: flex; align-items: center;flex-direction: column;margin-left: 20px" v-if="activeBarcode.id">
+                      <div style="font-size: 14px">待移出:{{activeBarcode.remainDownQuantity}}</div>
+                    </div>
+                  </template>
+                </van-search>
+              </div>
+              <div class="barcode-input" v-if="activeBarcode.id" >
+                <van-search
+                  v-model="activeBarcode.name"
+                  readonly
+                  label="商品名称:"
+                  left-icon=""
+                >
+                </van-search>
+              </div>
+            </div>
+            <div class="take-button">
+              <van-button type="primary" size="large"  style="height: 36px"  @click="onConfirm"  >下架</van-button>
+            </div>
+          </div>
+          <van-divider :style="{ color: '#333', borderColor: '#1989fa', padding: '0 16px',margin:'5px 0' }">待移出列表
+          </van-divider>
+          <div class="move-stock-list">
+            <table class="task-table">
+              <thead>
+              <tr>
+                <th style="width: 100px;">库位</th>
+                <th style="width: 100px;">商品条码</th>
+                <th style="width: 50px">数量</th>
+                <th>生产</th>
+                <th>失效</th>
+              </tr>
+              </thead>
+              <tbody>
+              <tr v-for="(item, index) in orderList" :key="index" v-if="orderList.length>0" :style="rowStyle(item)">
+                <td>{{ item.location }}</td>
+                <td>{{ item.barcode }}</td>
+                <td>{{ item.remainDownQuantity }}</td>
+                <td>{{ item.productDate }}</td>
+                <td>{{ item.invalidDate }}</td>
+              </tr>
+              <tr v-else>
+                <td colspan="5">
+                  <div>暂无数据</div>
+                </td>
+              </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+      <div class="tab-bar">
+        <van-tabbar :fixed="false" route>
+          <van-tabbar-item replace to="/move-list" icon="description">列表</van-tabbar-item>
+          <van-tabbar-item replace to="/move-down"  icon="descending">下架</van-tabbar-item>
+          <van-tabbar-item replace @click.prevent="linkTask()" icon="ascending">上架</van-tabbar-item>
+        </van-tabbar>
+      </div>
+    </div>
+    <!--  单据选择-->
+    <van-action-sheet v-model:show="lotBarcodeTrueFalseBy" cancel-text="取消" description="请选择商品批次" close-on-click-action>
+      <van-cell-group>
+        <van-cell v-for="item in matchBarcodeList" @click="setBarcode(item)" >
+          <template #title>
+            {{ item.barcode }}(数量:{{ item.remainDownQuantity }})
+          </template>
+          <template #label>
+            <div>生产日期:{{ item.productDate || '--' }}</div>
+            <div>失效日期:{{ item.invalidDate || '--' }}</div>
+            <div>质量状态:{{ item.quality || '--' }}</div>
+            <div>属性仓:{{ item.attribute || '--' }}</div>
+          </template>
+        </van-cell>
+      </van-cell-group>
+    </van-action-sheet>
+  </div>
+</template>
+<script setup>
+import { ref, computed,onMounted, onUnmounted } from 'vue'
+import { androidFocus, getHeader, goBack, scanError, scanSuccess } from '@/utils/android'
+import { useStore } from '@/store/modules/user'
+import { useRouter, useRoute } from 'vue-router'
+import { getMoveTaskDown, moveTaskDown } from '@/api/transferMove/index'
+import { closeLoading, showLoading } from '@/utils/loading'
+import { barcodeToUpperCase } from '@/utils/dataType.js'
+import { showConfirmDialog, showNotify } from 'vant'
+import { closeListener, openListener, scanInit } from '@/utils/keydownListener.js'
+try {
+  getHeader()
+  androidFocus()
+} catch (error) {
+}
+// 页面初始化
+onMounted(() => {
+  openListener()
+  scanInit(_handlerScan)
+  loadData()
+})
+const router = useRouter()
+const storeUser = useStore()
+const route = useRoute()
+const warehouse = storeUser.warehouse
+const code = route.query.code
+//商品条码
+const searchBarcode = ref('')
+//库位
+const searchLocation = ref('')
+//提示
+const tips=ref('请扫描商品条码')
+const count = ref('')
+const scanType = ref(2)
+const taskDetail = ref({})
+const taskList = ref([])
+//待移出列表
+const orderList = computed(() => {
+  return taskList.value
+    .filter(item => item.remainDownQuantity !== 0)
+})
+//匹配列表
+const matchBarcodeList = ref([])
+//当前条码
+const activeBarcode = ref({})
+//批次选择
+const lotBarcodeTrueFalseBy = ref(false)
+const _handlerScan = async (code) => {
+  if (!code) return
+  const checkBarcode = barcodeToUpperCase(code)
+  if (scanType.value == 2) {
+    const barcode = [...new Set(
+      orderList.value
+        .flatMap(item => [item.barcode, item.barcode2, item.sku])
+        .filter(value => value !== null && value !== '' && value !== undefined),
+    )]
+    count.value=''
+    if (barcode.some(item => barcodeToUpperCase(item) === checkBarcode)) {
+      taskList.value = await barcodeMatching(checkBarcode)
+      matchBarcodeList.value = orderList.value.filter(item => ((barcodeToUpperCase(item.barcode) === checkBarcode || barcodeToUpperCase(item.sku) == checkBarcode || (item.barcode2 ? barcodeToUpperCase(item.barcode2) === checkBarcode : item.barcode2 === checkBarcode))))
+       if (matchBarcodeList.value.length == 1) {
+        tips.value='请扫描移出库位'
+        activeBarcode.value = matchBarcodeList.value[0]
+        scanType.value=3
+        searchLocation.value = ''
+        scanSuccess()
+      } else if (matchBarcodeList.value.length > 1) {
+        tips.value='请选择产品批次'
+        lotBarcodeTrueFalseBy.value = true
+        activeBarcode.value = {}
+        searchLocation.value = ''
+      }
+    }else {
+      scanError()
+      tips.value=`${code}-商品条码不匹配,请重新扫描`
+      showNotify({ type: 'warning', duration: 3000, message: `${code}-商品条码不匹配,请重新扫描` })
+      searchBarcode.value = ''
+      searchLocation.value = ''
+      activeBarcode.value = {}
+    }
+  }else if(scanType.value == 3){
+    if(checkBarcode==activeBarcode.value.location){
+      scanSuccess()
+      searchLocation.value= barcodeToUpperCase(checkBarcode)
+      tips.value='请输入移出数量'
+      scanType.value=4
+      count.value=1
+    }else {
+      scanError()
+      tips.value=`${code}-库位不匹配,请重新扫描`
+      showNotify({ type: 'warning', duration: 3000, message: `${code}-库位不匹配,请重新扫描` })
+      searchLocation.value = ''
+    }
+
+  }
+}
+const onConfirm=()=>{
+  if(!activeBarcode.value.id ){
+    scanError()
+    tips.value='请扫描商品条码'
+    showNotify({ type: 'warning', duration: 3000, message: '请扫描商品条码' })
+    return
+  }
+  if(!searchLocation.value){
+    scanError()
+    tips.value='请扫描移出库位'
+    showNotify({ type: 'warning', duration: 3000, message: '请扫描移出库位' })
+    return
+  }
+  if(!count.value || count.value==0){
+    scanError()
+    tips.value='请输入移出数量'
+    showNotify({ type: 'warning', duration: 3000, message: '请输入移出数量' })
+    return
+  }
+  if(count.value<activeBarcode.value.remainDownQuantity){
+    showConfirmDialog({
+      title: '温馨提示', message: '移出数量小于待移出数量是否确认下架?',
+    }).then(() => {
+      _moveTaskDown()
+      }).catch(() => {
+        scanType.value=4
+      });
+    return
+  }
+  _moveTaskDown()
+}
+//下架
+const _moveTaskDown=()=>{
+  showLoading()
+  const data={id:activeBarcode.value.id,container:'*',doQty:count.value}
+  moveTaskDown(data).then(res => {
+    scanSuccess()
+    showNotify({ type: 'success', duration: 3000, message: '下架成功,正在刷新数据' })
+    loadData()
+  }).catch(err=>{
+    scanError()
+  }).finally(() => {
+
+    closeLoading()
+  })
+}
+const setBarcode=(item)=>{
+  activeBarcode.value=item
+  lotBarcodeTrueFalseBy.value=false
+  scanType.value=3
+  searchBarcode.value = item.barcode
+  scanSuccess()
+}
+const onClickRight=()=>{
+  showConfirmDialog({
+    title: '温馨提示', message: '是否重置扫描商品数据?',
+  }).then(() => {
+    loadData()
+  }).catch(() => {});
+}
+const rowStyle=( row )=>{
+  if(activeBarcode.value.id && row.id ==activeBarcode.value.id){
+    return { background: '#E6A23C'}
+  }
+  return ''
+}
+//条码匹配放到前边
+const barcodeMatching = (checkBarcode) => {
+  const matchingItems = [];
+  const nonMatchingItems = [];
+  taskList.value.forEach(item => {
+    const isMatchingBarcode =
+      barcodeToUpperCase(item.barcode) === checkBarcode ||
+      checkBarcode === barcodeToUpperCase(item.sku) ||
+      (item.barcode2 && barcodeToUpperCase(item.barcode2) === checkBarcode) ||
+      (item.barcode2 === checkBarcode);
+
+    if (isMatchingBarcode) {
+      matchingItems.push(item); // 匹配条形码的项
+    } else {
+      nonMatchingItems.push(item); // 不匹配的项
+    }
+  });
+  return [...matchingItems, ...nonMatchingItems];
+};
+
+const loadData = async () => {
+  if (code == 0) return
+  showLoading()
+  getMoveTaskDown(code).then(res => {
+    taskDetail.value = res.data
+    if (res.data.itemList.length > 0) {
+      // scanSuccess()
+      taskList.value = res.data.itemList
+      activeBarcode.value={}
+      searchLocation.value=''
+      searchBarcode.value=''
+      count.value=''
+      scanType.value=2
+      tips.value='请扫描商品条码'
+    }
+  }).catch(err=>{
+    scanError()
+  }).finally(() => {
+    closeLoading()
+  })
+}
+// 进入任务
+const linkTask=()=>{
+  router.push({ name: 'MovePutaway', query: { code } });
+}
+onUnmounted(() => {
+  closeListener()
+})
+window.onRefresh = loadData
+</script>
+
+<style scoped lang="sass">
+.container
+  width: 100%
+
+  .task
+    display: flex
+    flex-direction: column
+    height: 100vh
+
+    .top
+      flex: 1
+      display: flex
+      flex-direction: column
+
+      .nav-bar
+        height: 46px
+
+      .content
+        flex: 1
+        font-size: 14px
+
+        .take-delivery
+          .take-info
+            padding: 6px 10px
+            background: linear-gradient(160deg, #cfe1ff 20%, white 50%, white 100%)
+            display: flex
+            flex-direction: column
+            text-align: left
+
+            .take-info-no
+              flex: 1
+
+              .info-no-title
+                font-size: 17px
+                font-width: 500
+                display: flex
+                justify-content: space-between
+                align-items: center
+
+          .take-barcode
+            margin-top: 10px
+            text-align: left
+            background: #FFFFFF
+
+            .barcode-input
+              ::v-deep(.van-search)
+                padding: 0
+
+              ::v-deep(.van-search__field)
+                border-bottom: 2px solid #ffffff
+
+              ::v-deep(.van-search__content)
+                background: #fff
+
+              ::v-deep(.van-field__control)
+                font-size: 15px
+
+              ::v-deep(.van-search__label)
+                font-size: 15px
+
+              .search-input-barcode
+                ::v-deep(.van-search__field)
+                  border-bottom: 2px solid #0077ff
+                  z-index: 2
+
+        .take-button
+          padding: 10px 20px 0 20px
+
+        .move-stock-list
+          width: 100%
+          overflow-y: auto
+          max-height: 60vh
+          min-height: 100px
+
+          .move-button
+            background: #1989fa
+            color: #fff
+            width: 100%
+            height: 30px
+            font-size: 15px
+            line-height: 30px
+            font-weight: bold
+
+          .task-table, .task-table-bin, .task-table-box
+            width: 100%
+            table-layout: fixed
+            border-collapse: collapse
+            font-size: 13px
+
+          .task-table th, .task-table-bin th, .task-table td, .task-table-bin td, .task-table-box th, .task-table-box td
+            text-align: center
+            border: 1px solid #ccc
+            word-wrap: break-word
+            word-break: break-all
+
+          .task-table thead, .task-table-bin thead, .task-table-box thead
+            background-color: #3f8dff
+            position: sticky
+            top: 0
+            color: white
+            font-size: 15px
+
+          .task-table-bin thead
+            background-color: #3f8dff
+
+          .task-table-bin tbody
+            background: #cde7ff
+
+    .tab-bar
+      height: 50px
+</style>

+ 165 - 0
src/views/transfer/move/list/index.vue

@@ -0,0 +1,165 @@
+<template>
+  <div class="container">
+    <div class="task">
+      <div class="top">
+        <div class="nav-bar">
+          <van-nav-bar title="移库任务" left-arrow  @click-left="goBack" @click-right="onClickRight" >
+            <template #left>
+              <van-icon name="arrow-left" size="25"   />
+              <div style="color: #fff;height: 46px;padding-right:20px;line-height: 46px" >返回</div>
+            </template>
+            <template #right>
+              <div style="color: #fff;line-height: 46px " >开始作业</div>
+            </template>
+          </van-nav-bar>
+        </div>
+        <div class="content">
+          <van-pull-refresh v-model="loading" @refresh="onRefresh" style="min-height: 85vh;">
+            <table border="1" style="width: 100%;border-collapse: collapse;text-align: center;table-layout: fixed;" >
+              <thead>
+              <tr>
+                <th >货主</th>
+                <th>任务</th>
+                <th style="width: 50px">库位数</th>
+                <th style="width: 50px">状态</th>
+                <th style="width: 60px">操作</th>
+              </tr>
+              </thead>
+              <tbody>
+              <tr v-for="(row, rowIndex) in taskList" :key="rowIndex">
+                <td style="word-wrap: break-word" >{{ row.customerName }}</td>
+                <td style="word-wrap: break-word" >{{ row.code }}</td>
+                <td style="word-wrap: break-word;">{{ row.locationQuantity }}</td>
+                <td style="word-wrap: break-word" >{{ statusMap[row.status] || row.status }}</td>
+                <td>
+                  <van-button type="primary" v-if="row.status=='CREATED'" @click="linkTask(row.code,'MoveDown')">获取</van-button>
+                  <van-button type="success" v-else-if="row.status=='IN_PROGRESS'" @click="linkTask(row.code,'MoveDown')">继续</van-button>
+                </td>
+              </tr>
+              <tr v-if="taskList.length==0">
+                <td colspan="5">
+                  <van-empty :image="nodataUrl" image-size="140" >
+                    <van-button round type="primary" class="bottom-button" size="small" @click="loadData">刷新</van-button>
+                  </van-empty>
+                </td>
+              </tr>
+              </tbody>
+            </table>
+            <van-back-top right="80vw" bottom="10vh" />
+          </van-pull-refresh>
+        </div>
+      </div>
+      <div class="tab-bar">
+        <van-tabbar :fixed="false" route >
+          <van-tabbar-item replace  to="/move-list"  icon="description">列表</van-tabbar-item>
+          <van-tabbar-item replace  @click.prevent="linkTask(0,'MoveDown')" icon="descending">下架</van-tabbar-item>
+          <van-tabbar-item replace  @click.prevent="linkTask(0,'MovePutaway')"   icon="ascending">上架</van-tabbar-item>
+        </van-tabbar>
+      </div>
+    </div>
+    <input-barcode ref="inputBarcodeRef"  @setBarcode="setBarcode"  />
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { getHeader, goBack } from '@/utils/android'
+import { useStore } from '@/store/modules/user'
+import { getMoveTaskList } from '@/api/transferMove/index'
+import { showToast } from 'vant'
+import nodataUrl from '@/assets/nodata.png'
+import { closeLoading, showLoading } from '@/utils/loading'
+import { useRouter } from 'vue-router'
+import { toMap } from '@/utils/dataType.js'
+import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
+const router = useRouter()
+try {
+  getHeader()
+}catch (error) {
+  router.push('/login')
+}
+const storeUser = useStore()
+const warehouse = storeUser.warehouse
+//任务列表
+const taskList=ref([])
+const statusMap=ref({
+  'CREATED':'创建',
+  'IN_PROGRESS':'进行中',
+  'FINISHED':'完成'
+})
+const loadData = async () => {
+  showLoading()
+  getMoveTaskList({ warehouse }).then(res => {
+    taskList.value=res.data
+    console.log(res, "res")
+  }).finally(() => {
+    closeLoading()
+  })
+}
+loadData()
+
+const inputBarcodeRef=ref(null)
+const onClickRight=()=>{
+  inputBarcodeRef.value?.show(undefined,'请扫描移库任务','')
+}
+const setBarcode=(code)=>{
+  const listMap = toMap(taskList.value, 'code');
+  if(listMap[code]){
+    linkTask(code,'MoveDown')
+  }else {
+    setTimeout(()=>{
+      inputBarcodeRef.value?.show(undefined,'请扫描移库任务',code+'暂未查询到任务')
+    },300)
+  }
+}
+
+// 进入任务
+const linkTask=(code,name)=>{
+  if (code !== 0) {
+    localStorage.setItem('MOVETASKCODE', code);
+  }
+  let taskCode= localStorage.getItem('MOVETASKCODE') || code;
+  const listMap = toMap(taskList.value, 'code');
+  taskCode = listMap[taskCode] ? taskCode : taskList.value[0].code;
+  router.push({ name, query: { code: taskCode } });
+  localStorage.setItem('MOVETASKCODE', taskCode);
+}
+/**
+ * 下拉刷新
+ */
+const loading = ref(false)
+const onRefresh = () => {
+  setTimeout(() => {
+    loadData()
+    showToast('刷新成功')
+    loading.value = false
+  }, 1000)
+}
+
+window.onRefresh = loadData
+</script>
+
+<style scoped lang="sass">
+.container
+  width: 100%
+
+  .task
+    display: flex
+    flex-direction: column
+    height: 100vh
+
+    .top
+      flex: 1
+      display: flex
+      flex-direction: column
+
+      .nav-bar
+        height: 46px
+
+      .content
+        flex: 1
+        font-size: 14px
+
+    .tab-bar
+      height: 50px
+</style>

+ 513 - 0
src/views/transfer/move/putaway/index.vue

@@ -0,0 +1,513 @@
+<template>
+  <div class="container">
+    <div class="task">
+      <div class="top">
+        <div class="nav-bar">
+          <van-nav-bar title="移库任务-上架" left-arrow @click-left="goBack" @click-right="onClickRight" >
+            <template #left>
+              <van-icon name="arrow-left" size="25" />
+              <div style="color: #fff;height: 46px;padding-right:20px;line-height: 46px">返回</div>
+            </template>
+            <template #right>
+              <div class="nav-right" style="color: #fff">重置</div>
+            </template>
+          </van-nav-bar>
+        </div>
+        <div class="content">
+          <div class="take-delivery">
+            <div class="take-info">
+              <div class="take-info-no">
+                <div class="info-no-title">
+                  <div>任务号:{{ code || '--' }}</div>
+                  <div v-if="taskDetail.expectQuantity">
+                    <div><span style="font-size: 13px">任务数:</span><span
+                      style="color: #0077ff;font-weight: bold;">{{ taskDetail.doneQuantity || 0
+                      }}/{{ taskDetail.expectQuantity || 0 }}</span></div>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <van-progress v-if="taskDetail.expectQuantity && taskDetail.doneQuantity/taskDetail.expectQuantity>=0"
+                          :percentage="((taskDetail.doneQuantity/taskDetail.expectQuantity)*100).toFixed(2)"
+                          stroke-width="4" />
+            <div class="take-barcode">
+              <div style="display: flex;justify-content: space-between;padding-right: 10px;">
+                <div style="flex: 1">
+                  <van-notice-bar :background="'none'" :speed="50" :text="tips"  />
+                </div>
+                <div>箱规:{{activeBarcode.packId || '--'}}</div>
+              </div>
+              <div class="barcode-input">
+                <van-search
+                  ref="searchRef"
+                  v-model.lazy="searchBarcode"
+                  placeholder="请扫描商品条码"
+                  @search="_handlerScan(searchBarcode)"
+                  label="商品条码:"
+                  left-icon=""
+                  :class="[scanType===2?'search-input-barcode':'','van-hairline--bottom']"
+                  @focus="scanType=2"
+                  autocomplete="off"
+                >
+                </van-search>
+              </div>
+              <div class="barcode-input">
+                <van-search
+                  ref="searchRef"
+                  v-model.lazy="searchLocation"
+                  placeholder="请扫描移入库位"
+                  @search="_handlerScan(searchLocation)"
+                  label="移入库位:"
+                  left-icon=""
+                  :class="[scanType===3?'search-input-barcode':'','van-hairline--bottom']"
+                  @focus="scanType=3"
+                  autocomplete="off"
+                >
+                </van-search>
+              </div>
+              <div class="barcode-input">
+                <van-search
+                  ref="numberRef"
+                  v-model="count"
+                  placeholder="请输入移入数量"
+                  type="number"
+                  label="移入数量:"
+                  left-icon=""
+                  autocomplete="off"
+                  show-action
+                  :min="1"
+                  :class="[scanType===4?'search-input-barcode':'','van-hairline--bottom']"
+                  @focus="scanType=4"
+                  :max="activeBarcode.id?activeBarcode.remainUpQuantity:10000"
+                  @search="onConfirm"
+                >
+                  <template #action >
+                    <div style="display: flex; align-items: center;flex-direction: column;margin-left: 20px" v-if="activeBarcode.id">
+                      <div style="font-size: 14px">待移入:{{activeBarcode.remainUpQuantity}}</div>
+                    </div>
+                  </template>
+                </van-search>
+              </div>
+              <div class="barcode-input" v-if="activeBarcode.id" >
+                <van-search
+                  v-model="activeBarcode.name"
+                  readonly
+                  label="商品名称:"
+                  left-icon=""
+                >
+                </van-search>
+              </div>
+              <div class="lot" v-if="activeBarcode.id">
+                <div class="lot-list">
+                  <div>生产:{{activeBarcode.productDate}}</div>
+                  <div>失效:{{activeBarcode.invalidDate}}</div>
+                  <div>质量:{{activeBarcode.quality}}</div>
+                  <div>属性仓:{{activeBarcode.attribute}}</div>
+                </div>
+              </div>
+            </div>
+            <div class="take-button">
+              <van-button type="primary" size="large"  style="height: 36px"  @click="onConfirm"  >上架</van-button>
+            </div>
+          </div>
+          <van-divider :style="{ color: '#333', borderColor: '#1989fa', padding: '0 16px',margin:'5px 0' }">推荐库位
+          </van-divider>
+          <div class="move-stock-list">
+            <table class="task-table">
+              <thead>
+              <tr>
+                <th style="width: 100px;">库位</th>
+                <th style="width: 100px;">类型</th>
+                <th style="width: 50px">数量</th>
+              </tr>
+              </thead>
+              <tbody>
+              <tr v-for="(item, index) in locationList" :key="index" v-if="locationList.length>0" >
+                <td>{{ item.location }}</td>
+                <td>{{ locationType[item.type] || item.type }}</td>
+                <td>{{ item.quantity ||'空' }}</td>
+              </tr>
+              <tr v-else>
+                <td colspan="3">
+                  <div>暂无数据</div>
+                </td>
+              </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+      <div class="tab-bar">
+        <van-tabbar :fixed="false" route>
+          <van-tabbar-item replace to="/move-list" icon="description">列表</van-tabbar-item>
+          <van-tabbar-item replace @click.prevent="linkTask()" icon="descending">下架</van-tabbar-item>
+          <van-tabbar-item replace to="/move-putaway"   icon="ascending">上架</van-tabbar-item>
+        </van-tabbar>
+      </div>
+    </div>
+    <!--  单据选择-->
+    <van-action-sheet v-model:show="lotBarcodeTrueFalseBy" cancel-text="取消" description="请选择商品批次" close-on-click-action>
+      <van-cell-group>
+        <van-cell v-for="item in matchBarcodeList" @click="setBarcode(item)" >
+          <template #title>
+            {{ item.barcode }}(数量:{{ item.remainUpQuantity }})
+          </template>
+          <template #label>
+            <div>生产日期:{{ item.productDate || '--' }}</div>
+            <div>失效日期:{{ item.invalidDate || '--' }}</div>
+            <div>质量状态:{{ item.quality || '--' }}</div>
+            <div>属性仓:{{ item.attribute || '--' }}</div>
+          </template>
+        </van-cell>
+      </van-cell-group>
+    </van-action-sheet>
+  </div>
+</template>
+<script setup>
+import { ref, computed,onMounted, onUnmounted } from 'vue'
+import { androidFocus, getHeader, goBack, scanError, scanSuccess } from '@/utils/android'
+import { useStore } from '@/store/modules/user'
+import { useRouter, useRoute } from 'vue-router'
+import { getMoveTaskUp, moveTaskDown, moveTaskUp } from '@/api/transferMove/index'
+import { closeLoading, showLoading } from '@/utils/loading'
+import { barcodeToUpperCase } from '@/utils/dataType.js'
+import { showConfirmDialog, showNotify } from 'vant'
+import { getRecommendedLocation } from '@/api/haikang/index'
+import { closeListener, openListener, scanInit } from '@/utils/keydownListener.js'
+
+try {
+  getHeader()
+  androidFocus()
+} catch (error) {
+}
+// 页面初始化
+onMounted(() => {
+  openListener()
+  scanInit(_handlerScan)
+  loadData()
+})
+const router = useRouter()
+const storeUser = useStore()
+const route = useRoute()
+const warehouse = storeUser.warehouse
+const code = route.query.code
+//商品条码
+const searchBarcode = ref('')
+//库位
+const searchLocation = ref('')
+//提示
+const tips=ref('请扫描商品条码')
+//库位类型
+const locationType = {
+  'EA': '件拣货库位',
+  'AP': '补充拣货位',
+  'CS': '箱拣货库位',
+  'HP': '快拣补货位',
+  'PC': '箱/件合并拣货库位',
+  'PT': '播种库位',
+  'RS': '存储库位',
+  'SS': '理货站',
+  'ST': '过渡库位',
+  'WB': '组装工作区',
+}
+const count = ref('')
+const scanType = ref(2)
+const taskDetail = ref({})
+const taskList = ref([])
+const locationList = ref([])
+//待移入列表
+const orderList = computed(() => {
+  return taskList.value
+    .filter(item => item.remainUpQuantity !== 0)
+})
+//匹配列表
+const matchBarcodeList = ref([])
+//当前条码
+const activeBarcode = ref({})
+//批次选择
+const lotBarcodeTrueFalseBy = ref(false)
+const _handlerScan = async (code) => {
+  if (!code) return
+  const checkBarcode = barcodeToUpperCase(code)
+  if (scanType.value == 2) {
+    const barcode = [...new Set(
+      orderList.value
+        .flatMap(item => [item.barcode, item.barcode2, item.sku])
+        .filter(value => value !== null && value !== '' && value !== undefined),
+    )]
+    count.value=''
+    if (barcode.some(item => barcodeToUpperCase(item) === checkBarcode)) {
+      taskList.value = await barcodeMatching(checkBarcode)
+      matchBarcodeList.value = orderList.value.filter(item => ((barcodeToUpperCase(item.barcode) === checkBarcode || barcodeToUpperCase(item.sku) == checkBarcode || (item.barcode2 ? barcodeToUpperCase(item.barcode2) === checkBarcode : item.barcode2 === checkBarcode))))
+      if (matchBarcodeList.value.length == 1) {
+        activeBarcode.value = matchBarcodeList.value[0]
+        searchLocation.value = ''
+        _getLocation(activeBarcode.value)
+      } else if (matchBarcodeList.value.length > 1) {
+        tips.value='请选择产品批次'
+        scanError()
+        lotBarcodeTrueFalseBy.value = true
+        activeBarcode.value = {}
+        searchLocation.value = ''
+        locationList.value=[]
+      }
+    }else {
+      scanError()
+      tips.value=`${code}-商品条码不匹配,请重新扫描`
+      showNotify({ type: 'warning', duration: 3000, message: `${code}-商品条码不匹配,请重新扫描` })
+      searchBarcode.value = ''
+      searchLocation.value = ''
+      activeBarcode.value = {}
+      locationList.value=[]
+    }
+  }else if(scanType.value == 3){
+      scanSuccess()
+      searchLocation.value= barcodeToUpperCase(checkBarcode)
+      tips.value='请输入移入数量'
+      scanType.value=4
+      count.value=1
+  }
+}
+const onConfirm=()=>{
+  if(!activeBarcode.value.id ){
+    scanError()
+    tips.value='请扫描商品条码'
+    showNotify({ type: 'warning', duration: 3000, message: '请扫描商品条码' })
+    return
+  }
+  if(!searchLocation.value){
+    scanError()
+    tips.value='请扫描移入库位'
+    showNotify({ type: 'warning', duration: 3000, message: '请扫描移入库位' })
+    return
+  }
+  if(!count.value || count.value==0){
+    scanError()
+    tips.value='请输入移入数量'
+    showNotify({ type: 'warning', duration: 3000, message: '请输入移入数量' })
+    return
+  }
+  if(count.value<activeBarcode.value.remainUpQuantity){
+    showConfirmDialog({
+      title: '温馨提示', message: '移入数量小于待移入数量是否确认上架?',
+    }).then(() => {
+      _moveTaskDown()
+    }).catch(() => {
+      scanType.value=4
+    });
+    return
+  }
+  _moveTaskDown()
+}
+//上架
+const _moveTaskDown=()=>{
+  showLoading()
+  const data={id:activeBarcode.value.id,container:'*',doQty:count.value,location:searchLocation.value}
+  moveTaskUp(data).then(res => {
+    showNotify({ type: 'success', duration: 3000, message: '上架成功,正在刷新数据' })
+    loadData()
+  }).catch(err=>{
+    scanError()
+  }).finally(() => {
+
+    closeLoading()
+  })
+}
+const setBarcode=(item)=>{
+  activeBarcode.value=item
+  lotBarcodeTrueFalseBy.value=false;
+  _getLocation(item)
+}
+const _getLocation=(item)=>{
+  const params = { warehouse, lotNum:item.lotNum, owner:item.customerId}
+  showLoading()
+  getRecommendedLocation(params).then(res => {
+    scanSuccess()
+    locationList.value=res.data
+    tips.value='请扫描移入库位'
+    scanType.value=3
+    searchLocation.value=''
+  }).catch(err=>{
+    locationList.value=[]
+    scanError()
+    tips.value=err.message
+  }).finally(f=>{
+    closeLoading()
+  })
+}
+const onClickRight=()=>{
+  showConfirmDialog({
+    title: '温馨提示', message: '是否重置扫描商品数据?',
+  }).then(() => {
+    loadData()
+  }).catch(() => {});
+}
+//条码匹配放到前边
+const barcodeMatching = (checkBarcode) => {
+  const matchingItems = [];
+  const nonMatchingItems = [];
+  taskList.value.forEach(item => {
+    const isMatchingBarcode =
+      barcodeToUpperCase(item.barcode) === checkBarcode ||
+      checkBarcode === barcodeToUpperCase(item.sku) ||
+      (item.barcode2 && barcodeToUpperCase(item.barcode2) === checkBarcode) ||
+      (item.barcode2 === checkBarcode);
+
+    if (isMatchingBarcode) {
+      matchingItems.push(item); // 匹配条形码的项
+    } else {
+      nonMatchingItems.push(item); // 不匹配的项
+    }
+  });
+  return [...matchingItems, ...nonMatchingItems];
+};
+
+const loadData = async () => {
+  if (code == 0) return
+  showLoading()
+  getMoveTaskUp(code).then(res => {
+    taskDetail.value = res.data
+    if (res.data.itemList.length > 0) {
+      // scanSuccess()
+      taskList.value = res.data.itemList
+      activeBarcode.value={}
+      searchLocation.value=''
+      searchBarcode.value=''
+      count.value=''
+      tips.value='请扫描商品条码'
+      locationList.value=[]
+      scanType.value=2
+    }
+  }).catch(err=>{
+    scanError()
+  }).finally(() => {
+    closeLoading()
+  })
+}
+// 进入任务
+const linkTask=()=>{
+  router.push({ name: 'MoveDown', query: { code } });
+}
+onUnmounted(() => {
+  closeListener()
+})
+window.onRefresh = loadData
+</script>
+
+<style scoped lang="sass">
+.container
+  width: 100%
+
+  .task
+    display: flex
+    flex-direction: column
+    height: 100vh
+
+    .top
+      flex: 1
+      display: flex
+      flex-direction: column
+
+      .nav-bar
+        height: 46px
+
+      .content
+        flex: 1
+        font-size: 14px
+        .lot
+          font-size: 12px
+          padding: 5px
+          color: #666
+          .lot-list
+            display: flex
+            justify-content: space-between
+        .take-delivery
+          .take-info
+            padding: 6px 10px
+            background: linear-gradient(160deg, #cfe1ff 20%, white 50%, white 100%)
+            display: flex
+            flex-direction: column
+            text-align: left
+
+            .take-info-no
+              flex: 1
+
+              .info-no-title
+                font-size: 17px
+                font-width: 500
+                display: flex
+                justify-content: space-between
+                align-items: center
+
+          .take-barcode
+            margin-top: 10px
+            text-align: left
+            background: #FFFFFF
+
+            .barcode-input
+              ::v-deep(.van-search)
+                padding: 0
+
+              ::v-deep(.van-search__field)
+                border-bottom: 2px solid #ffffff
+
+              ::v-deep(.van-search__content)
+                background: #fff
+
+              ::v-deep(.van-field__control)
+                font-size: 15px
+
+              ::v-deep(.van-search__label)
+                font-size: 15px
+
+              .search-input-barcode
+                ::v-deep(.van-search__field)
+                  border-bottom: 2px solid #0077ff
+                  z-index: 2
+
+        .take-button
+          padding: 10px 20px 0 20px
+
+        .move-stock-list
+          width: 100%
+          overflow-y: auto
+          max-height: 60vh
+          min-height: 100px
+
+          .move-button
+            background: #1989fa
+            color: #fff
+            width: 100%
+            height: 30px
+            font-size: 15px
+            line-height: 30px
+            font-weight: bold
+
+          .task-table, .task-table-bin, .task-table-box
+            width: 100%
+            table-layout: fixed
+            border-collapse: collapse
+            font-size: 13px
+
+          .task-table th, .task-table-bin th, .task-table td, .task-table-bin td, .task-table-box th, .task-table-box td
+            text-align: center
+            border: 1px solid #ccc
+            word-wrap: break-word
+            word-break: break-all
+
+          .task-table thead, .task-table-bin thead, .task-table-box thead
+            background-color: #3f8dff
+            position: sticky
+            top: 0
+            color: white
+            font-size: 15px
+
+          .task-table-bin thead
+            background-color: #3f8dff
+
+          .task-table-bin tbody
+            background: #cde7ff
+
+    .tab-bar
+      height: 50px
+</style>