40 lines
4.5 KiB
Vue
40 lines
4.5 KiB
Vue
<template>
|
|
<view v-if="user" class="page-shell portal-page">
|
|
<PortalHeader title="入库签收" :user="user" fallback-url="/pages/home/home" />
|
|
<view class="portal-content">
|
|
<view v-if="active" class="locked-card"><text class="title">当前柜 {{ active.containerNo }}</text><text>当前柜完成前不能处理其他柜号</text><button class="primary" @tap="continueReceiving">继续收货</button></view>
|
|
<template v-else>
|
|
<text class="page-title">选择本次处理柜号</text>
|
|
<view v-for="item in containers" :key="item.containerNo" class="container-card" :class="{ selected: selected?.containerNo === item.containerNo }" @tap="selectContainer(item)">
|
|
<text class="title">{{ item.containerNo }}</text><text>{{ item.inboundCodeCount }} 个入仓码 · {{ item.totalPallets }} 托 · {{ item.totalPackages }} 箱</text><text v-if="item.estimatedWarehouseAt">预计到仓 {{ item.estimatedWarehouseAt }}</text>
|
|
</view>
|
|
<view v-if="selected" class="sign-panel"><text class="title">签收 {{ selected.containerNo }}</text><button class="secondary" :disabled="busy" @tap="choosePhotos">拍摄到仓/卸柜照片</button><view class="photo-grid"><image v-for="photo in photos" :key="photo.fileId" :src="photo.path" mode="aspectFill" /></view><text class="hint">已上传 {{ photos.length }} 张,确认签收后只能处理此柜,完成前不能切换。</text><button class="primary" :disabled="busy || !photos.length" @tap="confirmSign">确认签收</button></view>
|
|
</template>
|
|
<view v-if="error" class="error"><text>{{ error.message }}</text><text v-if="error.code">code={{ error.code }}</text><text v-if="error.requestId">requestId={{ error.requestId }}</text><text v-if="error.traceId">traceId={{ error.traceId }}</text></view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import PortalHeader from '@/components/PortalHeader.vue'
|
|
import { session, requireSession } from '@/stores/session.js'
|
|
import { getActiveReceivingContainer, listReceivingContainers, signReceivingContainer, uploadSignPhoto } from '@/api/receiving.js'
|
|
const operationKey = () => `sign-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
|
export default {
|
|
components: { PortalHeader },
|
|
data() { return { user: session.currentUser, containers: [], active: null, selected: null, photos: [], busy: false, error: null, idempotencyKey: operationKey() } },
|
|
onShow() { if (!requireSession()) return; this.user = session.currentUser; this.load() },
|
|
methods: {
|
|
async load() { this.busy = true; this.error = null; try { this.active = await getActiveReceivingContainer(); if (!this.active) { const page = await listReceivingContainers(); this.containers = page?.records || [] } } catch (error) { if (error.code !== 'A0404') this.error = error; else this.active = null } finally { this.busy = false } },
|
|
selectContainer(item) { if (this.active) return; this.selected = item; this.photos = []; this.idempotencyKey = operationKey() },
|
|
choosePhotos() { if (!this.selected) return; uni.chooseImage({ count: 9, sourceType: ['camera','album'], success: async ({ tempFilePaths }) => { this.busy = true; try { for (const path of tempFilePaths) { const uploaded = await uploadSignPhoto(this.selected.containerNo, path); this.photos.push({ fileId: String(uploaded.fileId), path }) } } catch (error) { this.error = error } finally { this.busy = false } } }) },
|
|
async confirmSign() { this.busy = true; this.error = null; try { this.active = await signReceivingContainer(this.selected.containerNo, { photoFileIds: this.photos.map((item) => item.fileId), idempotencyKey: this.idempotencyKey }); this.continueReceiving() } catch (error) { this.error = error } finally { this.busy = false } },
|
|
continueReceiving() { uni.redirectTo({ url: '/pages/inbound-receiving/inbound-receiving' }) }
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.page-title,.title{display:block;font-weight:700}.page-title{margin-bottom:12px;font-size:18px}.container-card,.sign-panel,.locked-card,.error{margin-bottom:12px;padding:14px;border-radius:12px;background:#fff;box-shadow:0 4px 15px rgba(32,67,111,.07)}.container-card text,.locked-card text,.sign-panel text,.error text{display:block;margin-bottom:7px}.container-card.selected{border:2px solid #135ee8}.photo-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:10px 0}.photo-grid image{width:100%;height:86px;border-radius:8px}.primary,.secondary{width:100%;margin-top:10px}.primary{color:#fff;background:#135ee8}.secondary{color:#135ee8;border:1px solid #8cb5f5;background:#edf4ff}.hint{color:#52657c;font-size:12px}.error{color:#b42318;background:#fff1f0}
|
|
</style>
|