Files
pda-overseas-uniapp/src/pages/query/query.vue
T
2026-10-22 14:38:15 +08:00

56 lines
8.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view v-if="user" class="page-shell portal-page work-page">
<PortalHeader :title="title" :user="user" />
<scroll-view class="work-scroll" scroll-y><view class="portal-content work-content">
<view class="work-heading"><text class="work-title">{{ title }}</text></view>
<view class="work-card"><ScanTargetInput name="query" v-model="code" :label="label" :placeholder="placeholder" :active="true" @submit="search" /><button class="primary" :disabled="!code.trim() || loading" @tap="search(code)">{{ loading ? '查询中…' : '查询' }}</button></view>
<view v-if="loading" class="empty-box"><uni-load-more status="loading" /><text class="empty-copy">正在读取最新库存事实…</text></view>
<template v-else-if="searched && mode === 'inventory'">
<view v-if="!inventoryItems.length" class="empty-box"><uni-icons type="info" color="#8090a3" size="32" /><text class="empty-copy">未找到与 {{ result?.queryCode || code }} 匹配的库存</text></view>
<view v-else class="item-list"><view v-for="(item,index) in inventoryItems" :key="`${item.palletCode}:${item.locationCode}:${item.skuCode}:${index}`" class="item-card"><view class="row-head"><view><text class="business-code">{{ item.skuCode || 'SKU 未关联' }}</text><text class="meta-line">{{ item.productName || '商品名称未关联' }}</text></view><text class="status-pill">{{ statusLabel(item.status) }}</text></view><text class="meta-line">入仓码:{{ item.inboundCode || '—' }}</text><text class="meta-line">箱码:{{ item.cartonMark || '—' }}</text><text class="meta-line">托盘:{{ item.palletCode || '未绑定' }} · 库位:{{ item.locationCode || '未绑定' }}</text><view class="metric-row"><view class="metric"><strong>{{ item.availablePackages }}</strong><text>可用</text></view><view class="metric"><strong>{{ item.holdPackages }}</strong><text>冻结</text></view><view class="metric"><strong>{{ item.receivingPackages }}</strong><text>收货中</text></view></view></view></view>
</template>
<view v-else-if="searched && mode === 'location' && result" class="work-card"><view class="row-head"><view><text class="business-code">{{ result.locationCode }}</text><text class="meta-line">{{ locationTypeLabel(result.locationType) }}</text></view><text class="status-pill">{{ statusLabel(result.status) }}</text></view><view class="metric-row"><view class="metric"><strong>{{ result.occupiedPackages }}</strong><text>在库箱数</text></view><view class="metric"><strong>{{ result.inventoryPositionCount }}</strong><text>库存位置</text></view><view class="metric"><strong>{{ result.maxLoadKg || '—' }}</strong><text>最大载重 kg</text></view></view></view>
<template v-else-if="searched && mode === 'pallet' && result"><view class="work-card"><view class="row-head"><view><text class="business-code">{{ result.palletCode }}</text><text class="meta-line">库位:{{ result.locationCode || '未绑定' }}</text></view><text class="status-pill">{{ statusLabel(result.status) }}</text></view><view class="metric-row"><view class="metric"><strong>{{ result.occupiedPackages }}</strong><text>在托箱数</text></view><view class="metric"><strong>{{ result.inventoryPositionCount }}</strong><text>库存位置</text></view></view></view><view v-if="!palletItems.length" class="empty-box"><text class="empty-copy">当前托盘未绑定库存</text></view><view v-else class="item-list"><view v-for="(item,index) in palletItems" :key="`${item.skuCode}:${item.inboundCode}:${index}`" class="item-card"><text class="business-code">{{ item.skuCode || 'SKU 未关联' }}</text><text class="meta-line">{{ item.productName || '商品名称未关联' }}</text><text class="meta-line">{{ item.inboundCode || '无入仓码' }} · {{ item.cartonMark || '无箱码' }}</text><view class="metric-row"><view class="metric"><strong>{{ item.availablePackages }}</strong><text>可用</text></view><view class="metric"><strong>{{ item.holdPackages }}</strong><text>冻结</text></view><view class="metric"><strong>{{ item.receivingPackages }}</strong><text>收货中</text></view></view></view></view></template>
<view v-if="error" class="error-box" role="alert"><text class="error-title">{{ error.message }}</text><text v-if="error.code">错误码:{{ error.code }}</text><text v-if="error.requestId">requestId:{{ error.requestId }}</text><text v-if="error.traceId">traceId:{{ error.traceId }}</text><button class="secondary" @tap="search(code)">重新查询</button></view>
</view></scroll-view>
</view>
</template>
<script>
import PortalHeader from '@/components/PortalHeader.vue'
import ScanTargetInput from '@/components/ScanTargetInput.vue'
import { resolveInventory, resolveWarehouseLocation, resolveWarehousePallet } from '@/api/warehouse-operations.js'
import { session, requireSession } from '@/stores/session.js'
import { restoreWarehouseWork, updateWarehouseWork, warehouseWork } from '@/stores/warehouse-work.js'
import { startScanBroadcast } from '@/utils/scan-broadcast.js'
import { playErrorSound, playSuccessSound } from '@/utils/audio-feedback.js'
const MODES = {
inventoryQuery: { mode: 'inventory', title: '库存查询', label: '商品码或箱码', placeholder: '扫描或输入 SKU / 箱码' },
locationQuery: { mode: 'location', title: '库位查询', label: '库位码', placeholder: '扫描或输入库位码' },
palletQuery: { mode: 'pallet', title: '托盘查询', label: '托盘码', placeholder: '扫描或输入托盘码' }
}
export default {
components: { PortalHeader, ScanTargetInput },
data() { return { user: session.currentUser, operationKey: 'inventoryQuery', mode: 'inventory', title: '库存查询', label: '商品码或箱码', placeholder: '扫描或输入 SKU / 箱码', loading: false, searched: false, result: null, error: null } },
computed: {
storeField() { return `${this.mode}Code` },
code: { get() { return warehouseWork.query[this.storeField] || '' }, set(value) { updateWarehouseWork('query', { [this.storeField]: value }) } },
inventoryItems() { return Array.isArray(this.result?.items) ? this.result.items : [] }, palletItems() { return Array.isArray(this.result?.inventory) ? this.result.inventory : [] }
},
onLoad(options) { this.operationKey = options.op || 'inventoryQuery'; Object.assign(this, MODES[this.operationKey] || MODES.inventoryQuery) },
onShow() { if (!requireSession()) return; this.user = session.currentUser; restoreWarehouseWork(`${this.user.id}:${this.user.warehouseId}`); this.startScanner() },
onHide() { this.stopScanner() }, onUnload() { this.stopScanner() }, beforeUnmount() { this.stopScanner() },
methods: {
startScanner() { this.stopScanner(); this._stopScanner = startScanBroadcast({ onScan: (value) => { this.code = value; this.search(value) }, onError: (error) => this.setError(error, '扫码广播注册失败') }) },
stopScanner() { if (this._stopScanner) this._stopScanner(); this._stopScanner = null },
async search(raw) { const code = String(raw?.detail?.value ?? raw ?? this.code).trim(); if (!code || this.loading) return; this.code = code; this.loading = true; this.error = null; this.result = null; this.searched = false; try { if (this.mode === 'location') this.result = await resolveWarehouseLocation(code); else if (this.mode === 'pallet') this.result = await resolveWarehousePallet(code); else this.result = await resolveInventory(code); this.searched = true; playSuccessSound() } catch (error) { this.searched = true; this.setError(error, `${this.title}失败`) } finally { this.loading = false } },
setError(error, fallback) { this.error = { message: error?.message || fallback, code: error?.code, requestId: error?.requestId, traceId: error?.traceId }; playErrorSound() },
statusLabel(value) { return ({ AVAILABLE: '可用', ACTIVE: '可用', IDLE: '空闲', OCCUPIED: '已占用', HOLD: '冻结', LOCKED: '已锁定' })[value] || value || '状态未知' },
locationTypeLabel(value) { return ({ STORAGE: '存储库位', STAGING: '暂存库位', RELABEL: '换标库位', RECEIVING: '收货库位' })[value] || value || '库位类型未标注' }
}
}
</script>
<style scoped lang="scss">@import '../warehouse/operations.scss';</style>