|
|
@@ -0,0 +1,337 @@
|
|
|
+<template>
|
|
|
+ <van-popup
|
|
|
+ v-model:show="visible"
|
|
|
+ position="center"
|
|
|
+ round
|
|
|
+ :close-on-click-overlay="false"
|
|
|
+ class="return-call-hik-popup"
|
|
|
+ >
|
|
|
+ <div class="return-call-hik-panel">
|
|
|
+ <div class="return-call-hik-header">
|
|
|
+ <div class="return-call-hik-title">{{ headerTitle }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="return-call-hik-list-wrap">
|
|
|
+ <van-radio-group v-model="selectedStationCode" class="return-call-hik-radio-group">
|
|
|
+ <div
|
|
|
+ v-for="s in stationList"
|
|
|
+ :key="s.code"
|
|
|
+ class="return-call-hik-item"
|
|
|
+ :class="{ 'return-call-hik-item--active': selectedStationCode === s.code }"
|
|
|
+ role="button"
|
|
|
+ @click="selectedStationCode = s.code"
|
|
|
+ >
|
|
|
+ <van-radio :name="s.code" checked-color="#3f8dff" icon-size="16px" />
|
|
|
+ <div class="return-call-hik-item-body">
|
|
|
+ <span class="return-call-hik-code">{{ s.code }}</span>
|
|
|
+ <!-- 站点状态:接口里是数字,这里直接写死两种 -->
|
|
|
+ <span v-if="s.status === 0" class="return-call-hik-tag return-call-hik-tag--idle">空闲</span>
|
|
|
+ <span v-else-if="s.status === 1" class="return-call-hik-tag return-call-hik-tag--busy">繁忙</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </van-radio-group>
|
|
|
+ </div>
|
|
|
+ <div class="return-call-hik-actions">
|
|
|
+ <van-button
|
|
|
+ class="return-call-hik-btn return-call-hik-btn--ghost"
|
|
|
+ round
|
|
|
+ plain
|
|
|
+ hairline
|
|
|
+ type="default"
|
|
|
+ @click="onNoCall"
|
|
|
+ >
|
|
|
+ 不呼叫
|
|
|
+ </van-button>
|
|
|
+ <van-button
|
|
|
+ class="return-call-hik-btn return-call-hik-btn--primary"
|
|
|
+ round
|
|
|
+ type="primary"
|
|
|
+ @click="onCallNow"
|
|
|
+ >
|
|
|
+ 立即呼叫小车
|
|
|
+ </van-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </van-popup>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import {ref} from 'vue'
|
|
|
+import {useRouter} from 'vue-router'
|
|
|
+import {showToast} from 'vant'
|
|
|
+import {closeLoading, showLoading} from '@/utils/loading'
|
|
|
+import {scanError, scanSuccess} from '@/utils/android'
|
|
|
+import {batchCreateHikBoxOutboundTask, getHikUsableStationList} from '@/api/haikang/index'
|
|
|
+import {getReturnTaskBinList, returnTaskFirstStepComplete} from '@/api/returnTask/index'
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ warehouse: {type: String, required: true},
|
|
|
+ headerTitle: {
|
|
|
+ type: String,
|
|
|
+ default: '分格口完成,呼叫小车请选择海康拣货站点',
|
|
|
+ },
|
|
|
+})
|
|
|
+
|
|
|
+const router = useRouter()
|
|
|
+
|
|
|
+const visible = ref(false)
|
|
|
+const stationList = ref([])
|
|
|
+const selectedStationCode = ref('')
|
|
|
+// 弹层打开到关闭之间要用的上下文(父组件只调 open,不重复传参)
|
|
|
+const pendingTaskNo = ref('')
|
|
|
+const pendingContainerCode = ref('')
|
|
|
+const pendingBoxCodeList = ref([])
|
|
|
+
|
|
|
+// 交互失败既要 Toast,也要给 PDA 扫码枪那边一个失败信号(和原逻辑一致)
|
|
|
+function scanFail(message) {
|
|
|
+ scanError()
|
|
|
+ showToast({duration: 3000, message})
|
|
|
+}
|
|
|
+
|
|
|
+function resetSession() {
|
|
|
+ pendingTaskNo.value = ''
|
|
|
+ pendingContainerCode.value = ''
|
|
|
+ pendingBoxCodeList.value = []
|
|
|
+ stationList.value = []
|
|
|
+ selectedStationCode.value = ''
|
|
|
+}
|
|
|
+
|
|
|
+// 格口行里的 containerCode 可能带后缀(如箱号-格),呼车只要物理箱号:取第一个 '-' 前面那段,再去重
|
|
|
+function boxCodesFromBinRows(rows) {
|
|
|
+ const set = new Set()
|
|
|
+ for (const item of rows) {
|
|
|
+ const raw = item.containerCode
|
|
|
+ if (!raw) continue
|
|
|
+ const base = String(raw).split('-')[0].trim()
|
|
|
+ if (base) set.add(base)
|
|
|
+ }
|
|
|
+ return [...set]
|
|
|
+}
|
|
|
+
|
|
|
+// taskNo:还库任务号;containerCode:选填,呼车成功后回还库任务页要带到 query 里做筛选/高亮
|
|
|
+const open = async ({taskNo, containerCode = ''}) => {
|
|
|
+ if (!taskNo) {
|
|
|
+ showToast({duration: 3000, message: '任务号缺失'})
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ resetSession()
|
|
|
+ pendingTaskNo.value = taskNo
|
|
|
+ pendingContainerCode.value = containerCode
|
|
|
+
|
|
|
+ showLoading()
|
|
|
+ try {
|
|
|
+ const binRes = await getReturnTaskBinList({taskNo})
|
|
|
+ const boxCodeList = boxCodesFromBinRows(binRes.data || [])
|
|
|
+ if (!boxCodeList.length) {
|
|
|
+ scanFail('未获取到料箱号,请先完成格口分配')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ pendingBoxCodeList.value = boxCodeList
|
|
|
+
|
|
|
+ const stationRes = await getHikUsableStationList({
|
|
|
+ warehouse: props.warehouse,
|
|
|
+ typeEnum: 'HIK_PICKING_STATION',
|
|
|
+ })
|
|
|
+ const rawList = stationRes.data || []
|
|
|
+ if (!rawList.length) {
|
|
|
+ scanFail('暂无可选站点')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // status:0 空闲 1 繁忙,把空闲排前面少点一次
|
|
|
+ const sorted = [...rawList].sort(
|
|
|
+ (a, b) => (a.status === 0 ? 0 : 1) - (b.status === 0 ? 0 : 1),
|
|
|
+ )
|
|
|
+ stationList.value = sorted
|
|
|
+ selectedStationCode.value = (sorted.find((s) => s.status === 0) || sorted[0])?.code || ''
|
|
|
+
|
|
|
+ visible.value = true
|
|
|
+ } catch (e) {
|
|
|
+ scanFail(e.message || '加载失败')
|
|
|
+ } finally {
|
|
|
+ closeLoading()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 不呼海康:只走还库第一步完成,回到列表
|
|
|
+const onNoCall = async () => {
|
|
|
+ visible.value = false
|
|
|
+ const taskNo = pendingTaskNo.value
|
|
|
+ if (!taskNo) return
|
|
|
+
|
|
|
+ showLoading()
|
|
|
+ try {
|
|
|
+ await returnTaskFirstStepComplete({taskNo, callHikQuickIn: false})
|
|
|
+ scanSuccess()
|
|
|
+ router.push({name: 'ReturnList'})
|
|
|
+ } catch {
|
|
|
+ // 接口 catch 里原先就不弹 Toast,只震动
|
|
|
+ scanError()
|
|
|
+ } finally {
|
|
|
+ closeLoading()
|
|
|
+ resetSession()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 先批量下发出库任务(呼车),再标记第一步完成,跳回还库任务页继续扫
|
|
|
+const onCallNow = async () => {
|
|
|
+ if (!selectedStationCode.value) {
|
|
|
+ showToast({duration: 3000, message: '请选择站点'})
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const taskNo = pendingTaskNo.value
|
|
|
+ const boxCodeList = pendingBoxCodeList.value
|
|
|
+ if (!taskNo || !boxCodeList.length) {
|
|
|
+ showToast({duration: 3000, message: '数据异常,请重试'})
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ visible.value = false
|
|
|
+ showLoading()
|
|
|
+ try {
|
|
|
+ await batchCreateHikBoxOutboundTask({
|
|
|
+ boxCodeList,
|
|
|
+ warehouse: props.warehouse,
|
|
|
+ deviceType: 'HIK_PICKING_STATION',
|
|
|
+ stationCode: selectedStationCode.value,
|
|
|
+ })
|
|
|
+ await returnTaskFirstStepComplete({taskNo, callHikQuickIn: true})
|
|
|
+ scanSuccess()
|
|
|
+ router.replace({
|
|
|
+ name: 'ReturnTask',
|
|
|
+ query: {code: taskNo, container: pendingContainerCode.value},
|
|
|
+ })
|
|
|
+ } catch (e) {
|
|
|
+ scanFail(e.message || '操作失败')
|
|
|
+ } finally {
|
|
|
+ closeLoading()
|
|
|
+ resetSession()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+defineExpose({open})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+$rh-blue: #3f8dff;
|
|
|
+$rh-border: #ebedf0;
|
|
|
+
|
|
|
+.return-call-hik-popup {
|
|
|
+ background: transparent;
|
|
|
+}
|
|
|
+
|
|
|
+.return-call-hik-panel {
|
|
|
+ width: calc(100vw - 56px);
|
|
|
+ max-width: 296px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ border-radius: 10px;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #fff;
|
|
|
+ box-shadow: 0 2px 14px rgba(0, 0, 0, 0.12);
|
|
|
+}
|
|
|
+
|
|
|
+.return-call-hik-header {
|
|
|
+ padding: 10px 12px;
|
|
|
+ text-align: center;
|
|
|
+ color: #fff;
|
|
|
+ background: $rh-blue;
|
|
|
+}
|
|
|
+
|
|
|
+.return-call-hik-title {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: 1.35;
|
|
|
+}
|
|
|
+
|
|
|
+.return-call-hik-list-wrap {
|
|
|
+ padding: 8px;
|
|
|
+ max-height: min(32vh, 188px);
|
|
|
+ overflow-y: auto;
|
|
|
+ -webkit-overflow-scrolling: touch;
|
|
|
+ background: #f7f8fa;
|
|
|
+}
|
|
|
+
|
|
|
+.return-call-hik-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 6px;
|
|
|
+ padding: 8px;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid $rh-border;
|
|
|
+ border-radius: 6px;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ &--active {
|
|
|
+ border-color: $rh-blue;
|
|
|
+ background: #f0f6ff;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.return-call-hik-item-body {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 6px;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.return-call-hik-code {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #323233;
|
|
|
+}
|
|
|
+
|
|
|
+.return-call-hik-tag {
|
|
|
+ flex-shrink: 0;
|
|
|
+ padding: 1px 6px;
|
|
|
+ font-size: 10px;
|
|
|
+ font-weight: 600;
|
|
|
+ border-radius: 4px;
|
|
|
+
|
|
|
+ &--idle {
|
|
|
+ color: #07c160;
|
|
|
+ background: #e6f7ed;
|
|
|
+ }
|
|
|
+
|
|
|
+ &--busy {
|
|
|
+ color: #ed6a0c;
|
|
|
+ background: #fff3e8;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.return-call-hik-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 10px;
|
|
|
+ border-top: 1px solid $rh-border;
|
|
|
+
|
|
|
+ :deep(.return-call-hik-btn) {
|
|
|
+ flex: 1;
|
|
|
+ height: 36px;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.return-call-hik-btn--ghost) {
|
|
|
+ color: #646566;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.return-call-hik-btn--primary.van-button--primary) {
|
|
|
+ background: $rh-blue;
|
|
|
+ border-color: $rh-blue;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.return-call-hik-radio-group .van-radio) {
|
|
|
+ margin-right: 0;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.return-call-hik-radio-group .van-radio__icon) {
|
|
|
+ line-height: 1;
|
|
|
+}
|
|
|
+</style>
|