This commit is contained in:
2026-08-26 15:59:08 +08:00
parent ee83d87a85
commit 3c283ada46
19 changed files with 196 additions and 46 deletions
+7 -1
View File
@@ -2,12 +2,17 @@ import { API_ROOT } from '../config/runtime.js'
function authRequest(path, data) {
return new Promise((resolve, reject) => {
const url = `${API_ROOT}${path}`
console.info('[Overseas PDA auth] request', { method: 'POST', url })
uni.request({
url: `${API_ROOT}${path}`,
url,
method: 'POST',
data,
success(response) {
const body = response.data || {}
console.info('[Overseas PDA auth] response', { url, statusCode: response.statusCode, code: body.code,
requestId: body.requestId || response.header?.['X-Request-Id'] || response.header?.['x-request-id'],
traceId: body.traceId || response.header?.['X-Trace-Id'] || response.header?.['x-trace-id'] })
if (String(body.code ?? response.statusCode) !== '200') {
const error = new Error(body.message || '认证服务请求失败')
error.code = body.code || response.statusCode
@@ -19,6 +24,7 @@ function authRequest(path, data) {
resolve(body.data)
},
fail(error) {
console.error('[Overseas PDA auth] network failure', { url, errMsg: error.errMsg || 'unknown' })
const next = new Error(error.errMsg || '网络连接失败')
next.network = true
reject(next)
+2 -1
View File
@@ -1,4 +1,4 @@
import { overseasRequest, overseasUpload } from '@/api/overseas.js'
import { overseasDownload, overseasRequest, overseasUpload } from '@/api/overseas.js'
import { normalizeReceivingCandidate, normalizeReceivingSession } from '@/api/receiving-adapter.js'
const RECEIVING_BASE = '/pda/v1/last-leg/receiving-containers'
@@ -8,6 +8,7 @@ export const listReceivingCandidates = async () => (await overseasRequest('GET',
export const getReceivingContainer = async (containerNo) => normalizeReceivingSession(await overseasRequest('GET', `${RECEIVING_BASE}/${encodeURIComponent(containerNo)}`))
export const prepareReceivingContainer = async (containerNo, request) => normalizeReceivingSession(await overseasRequest('POST', `${RECEIVING_BASE}/${encodeURIComponent(containerNo)}/prepare`, request))
export const uploadCurrentUnloadPhoto = (filePath) => overseasUpload(current('/unload-photos/files'), filePath)
export const downloadCurrentUnloadPhoto = (downloadPath) => overseasDownload(downloadPath)
export const signCurrentReceiving = async (request) => normalizeReceivingSession(await overseasRequest('POST', current('/sign'), request))
export const getCurrentReceiving = async () => normalizeReceivingSession(await overseasRequest('GET', current()))
export const resolveCurrentCarton = (cartonMark) => overseasRequest('POST', current('/cartons/resolve'), { cartonMark })
+32
View File
@@ -78,6 +78,25 @@ function rawUpload(path, filePath, name, token) {
})
}
function overseasUrl(path) {
const value = String(path || '')
const normalized = /\/(?:api|overseas-api)$/.test(session.apiBase)
? value.replace(/^\/api(?=\/)/, '')
: value
return `${session.apiBase}${normalized}`
}
function rawDownload(path, token) {
return new Promise((resolve, reject) => {
uni.downloadFile({
url: overseasUrl(path),
header: { Authorization: `Bearer ${token}` },
success: resolve,
fail: (cause) => reject(Object.assign(new Error(cause.errMsg || '签收照片下载失败'), { network: true }))
})
})
}
export async function overseasUpload(path, filePath, name = 'file', retry = true) {
const attemptedToken = session.token
let response = await rawUpload(path, filePath, name, attemptedToken)
@@ -92,4 +111,17 @@ export async function overseasUpload(path, filePath, name = 'file', retry = true
return body.data
}
export async function overseasDownload(path, retry = true) {
const attemptedToken = session.token
let response = await rawDownload(path, attemptedToken)
if (retry && unauthorized(response)) {
const replacementToken = await tokenAfterUnauthorized(attemptedToken)
response = await rawDownload(path, replacementToken)
}
if (response?.statusCode !== 200 || !response?.tempFilePath) {
throw errorFrom({ ...response, data: {} }, '签收照片下载失败')
}
return response.tempFilePath
}
export function resetOverseasRequestStateForTest() { refreshPromise = null }
+1 -1
View File
@@ -30,7 +30,7 @@ export function normalizeReceivingSession(value) {
: []
return {
...value,
photos: RECEIVING_PHOTO_ROLES.map((role) => photos.find((photo) => photo?.role === role)).filter(Boolean),
photos: RECEIVING_PHOTO_ROLES.map((role) => photos.find((photo) => photo?.role === role && photo?.fileId)).filter(Boolean),
totalPackages: number(value.totalPackages),
receivedPackages: number(value.receivedPackages),
pendingPackages: number(value.pendingPackages),
+9
View File
@@ -5,6 +5,8 @@ const development = Boolean(runtimeEnvironment.DEV)
let selectedAuthApiRoot = configuredAuthApiRoot || '/api'
let selectedOverseasApiRoot = configuredOverseasApiRoot || '/overseas-api'
const nativeRuntime = typeof plus !== 'undefined'
|| (typeof location !== 'undefined' && location.protocol === 'file:')
// H5 must stay same-origin and use Vite's CORS-safe proxy; App builds keep the absolute API roots above.
// #ifdef H5
@@ -12,6 +14,13 @@ selectedAuthApiRoot = runtimeEnvironment.VITE_H5_AUTH_API_BASE_URL || '/api'
selectedOverseasApiRoot = runtimeEnvironment.VITE_H5_OVERSEAS_API_BASE_URL || '/overseas-api'
// #endif
// HBuilderX/App 基座 can expose a file:// location; never allow a relative API
// root there, even if a stale H5 bundle or compiler flag was reused.
if (nativeRuntime) {
selectedAuthApiRoot = configuredAuthApiRoot || selectedAuthApiRoot
selectedOverseasApiRoot = configuredOverseasApiRoot || selectedOverseasApiRoot
}
export const API_ROOT = String(selectedAuthApiRoot).replace(/\/$/, '')
export const OVERSEAS_API_ROOT = String(selectedOverseasApiRoot).replace(/\/$/, '')
export const DEV_LOGIN = Object.freeze({
+10 -10
View File
@@ -1,21 +1,21 @@
export const MODULES = [
{ key: 'inbound', name: '入仓作业', icon: '入', description: '单账号锁定一柜,完成柜号级入库闭环', operations: [
{ name: '拆柜收货', icon: '卸', iconType: 'download', description: '三类照片签收,FBA 按 SKU 箱数、海外仓逐箱收货', available: true, badge: '真实接口' }
{ name: '拆柜收货', icon: '卸', iconType: 'download', description: '三类照片签收,FBA 按 SKU 箱数、海外仓逐箱收货', available: true }
] },
{ key: 'inventory', name: '库内作业', icon: '库', description: '盘点、移库与托盘管理', operations: [
{ name: '库存盘点', icon: '盘', iconType: 'list', description: '按任务扫描库位和库存', available: false, badge: '待 PDA 接口' },
{ name: '商品移库', icon: '移', iconType: 'loop', description: '扫描来源与目标库位完成移库', available: false, badge: '待 PDA 接口' },
{ name: '托盘管理', icon: '托', iconType: 'grid', description: '组托、拆托及托盘状态核对', available: false, badge: '待 PDA 接口' }
{ name: '库存盘点', icon: '盘', iconType: 'list', description: '按任务扫描库位和库存', available: false },
{ name: '商品移库', icon: '移', iconType: 'loop', description: '扫描来源与目标库位完成移库', available: false },
{ name: '托盘管理', icon: '托', iconType: 'grid', description: '组托、拆托及托盘状态核对', available: false }
] },
{ key: 'relabel', name: '换标作业', icon: '换', description: '换标收货、执行与复核', operations: [
{ name: '换标收货', icon: '收', iconType: 'download', description: '扫描换标单并确认待处理库存', available: false, badge: '待 PDA 接口' },
{ name: '换标执行', icon: '换', iconType: 'compose', description: '扫描旧标和新标记录换标结果', available: false, badge: '待 PDA 接口' },
{ name: '换标复核', icon: '核', iconType: 'checkbox', description: '复核标签和商品关联关系', available: false, badge: '待 PDA 接口' }
{ name: '换标收货', icon: '收', iconType: 'download', description: '扫描换标单并确认待处理库存', available: false },
{ name: '换标执行', icon: '换', iconType: 'compose', description: '扫描旧标和新标记录换标结果', available: false },
{ name: '换标复核', icon: '核', iconType: 'checkbox', description: '复核标签和商品关联关系', available: false }
] },
{ key: 'query', name: '现场查询', icon: '查', description: '库存、库位与托盘查询', operations: [
{ name: '库存查询', icon: '存', iconType: 'search', description: '扫描商品或箱码查询可用库存', available: false, badge: '待 PDA 接口' },
{ name: '库位查询', icon: '位', iconType: 'location', description: '扫描库位查看容量和在库明细', available: false, badge: '待 PDA 接口' },
{ name: '托盘查询', icon: '托', iconType: 'flag', description: '扫描托盘码查看绑定库存', available: false, badge: '待 PDA 接口' }
{ name: '库存查询', icon: '存', iconType: 'search', description: '扫描商品或箱码查询可用库存', available: false },
{ name: '库位查询', icon: '位', iconType: 'location', description: '扫描库位查看容量和在库明细', available: false },
{ name: '托盘查询', icon: '托', iconType: 'flag', description: '扫描托盘码查看绑定库存', available: false }
] }
]
+5 -1
View File
@@ -81,7 +81,11 @@
"vueVersion" : "3",
"h5" : {
"devServer" : {
"port" : 5176
"proxy": {
},
"port" : 5176,
"https": false
}
}
}
+2 -2
View File
@@ -8,8 +8,8 @@
<button v-for="operation in group.operations" :key="operation.name" class="home-operation" :class="{ 'home-operation-primary': operation.available, 'home-operation-disabled': !operation.available }" :disabled="!operation.available" @tap="openOperation(operation)">
<view class="home-operation-icon" aria-hidden="true"><uni-icons :type="operation.iconType" :color="operation.available ? '#135ee8' : '#9aa6b2'" size="28" /></view>
<text class="home-operation-name">{{ operation.name }}</text>
<text v-if="operation.available" class="home-operation-pending">{{ operation.badge }}</text>
<text v-else class="home-operation-unavailable">{{ operation.badge || '未开放' }}</text>
<text v-if="operation.available" class="home-operation-pending">可处理</text>
<text v-else class="home-operation-unavailable">暂未开放</text>
</button>
</view>
</view>
+2
View File
@@ -34,6 +34,8 @@
signInWithTokens(tokens)
uni.reLaunch({ url: '/pages/home/home' })
} catch (error) {
console.error('[Overseas PDA login] failed', { message: error.message, network: error.network === true,
code: error.code, requestId: error.requestId, traceId: error.traceId })
this.error = error.network ? '当前网络不可用,请联网后重试' : (error.message || '登录失败')
} finally {
this.submitting = false
+2 -2
View File
@@ -4,8 +4,8 @@
<view class="portal-content">
<view class="module-hero" :class="`module-hero-${currentModule.key}`"><view class="module-hero-icon">{{ currentModule.icon }}</view><view><text class="module-hero-title">{{ currentModule.name }}</text><text class="module-hero-desc">{{ currentModule.description }}</text></view></view>
<text class="group-title">业务功能</text>
<view class="operation-list"><button v-for="operation in currentModule.operations" :key="operation.name" class="operation-item" :class="{ 'operation-item-disabled': !operation.available }" :disabled="!operation.available" @tap="openOperation(operation)"><view class="operation-icon">{{ operation.icon }}</view><view class="operation-info"><text class="operation-name">{{ operation.name }}</text><text class="operation-desc">{{ operation.description }}</text></view><text class="operation-prototype">{{ operation.badge || '未开放' }}</text><text v-if="operation.available" class="operation-arrow">›</text></button></view>
<view class="module-empty-note">只有拆柜收货已接海外 PDA 真实接口;其余入口在专用接口、权限与仓库范围契约完成前保持禁用。</view>
<view class="operation-list"><button v-for="operation in currentModule.operations" :key="operation.name" class="operation-item" :class="{ 'operation-item-disabled': !operation.available }" :disabled="!operation.available" @tap="openOperation(operation)"><view class="operation-icon">{{ operation.icon }}</view><view class="operation-info"><text class="operation-name">{{ operation.name }}</text><text class="operation-desc">{{ operation.description }}</text></view><text v-if="operation.available" class="operation-arrow">›</text></button></view>
<view class="module-empty-note">当前仅开放拆柜收货,其他作业暂未开放。</view>
</view>
</view>
</template>
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -35,8 +35,8 @@ export function setCurrentReceiving(value) {
receiving.selectedInboundCode = (value?.inbounds || []).find((item) => Number(item.pendingPackages || 0) > 0)?.inboundCode || value?.inbounds?.[0]?.inboundCode || ''
}
receiving.photoSlots = RECEIVING_PHOTO_ROLES.map((role) => {
const photo = value?.photos?.find((item) => item.role === role)
return photo ? { ...photo, fileId: String(photo.fileId), role, uploaded: true } : null
const photo = (value?.photos || []).find((item) => item.role === role)
return photo ? { ...photo, fileId: String(photo.fileId), role, uploaded: true, status: 'success' } : null
})
}
+4 -1
View File
@@ -12,13 +12,15 @@ function ensureListening() {
if (typeof uni.onNetworkStatusChange === 'function') {
uni.onNetworkStatusChange((result) => {
online = Boolean(result.isConnected)
console.info('[Overseas PDA network] status change', { isConnected: result.isConnected, networkType: result.networkType })
notify()
})
}
if (typeof uni.getNetworkType === 'function') {
uni.getNetworkType({
success: (result) => {
success: (result) => {
online = result.networkType !== 'none'
console.info('[Overseas PDA network] initial status', { networkType: result.networkType, online })
notify()
}
})
@@ -31,6 +33,7 @@ export function isOnline() {
}
export function requireOnline(message = '当前网络不可用,请联网后刷新重试') {
console.info('[Overseas PDA network] preflight', { online })
if (!isOnline()) {
const error = new Error(message)
error.network = true