优化
This commit is contained in:
+8
-2
@@ -1,5 +1,11 @@
|
||||
VITE_AUTH_API_BASE_URL=/api
|
||||
VITE_OVERSEAS_API_BASE_URL=/api/overseas
|
||||
VITE_AUTH_API_BASE_URL=http://127.0.0.1:9000/api
|
||||
VITE_OVERSEAS_API_BASE_URL=http://127.0.0.1:9300/api
|
||||
# H5 同源前缀,由 Vite 代理转发;App 构建使用上面的绝对域名直连。
|
||||
VITE_H5_AUTH_API_BASE_URL=/api
|
||||
VITE_H5_OVERSEAS_API_BASE_URL=/overseas-api
|
||||
# H5 开发代理上游;正式 App 构建请在本机环境文件中改为可访问的绝对 API 根地址。
|
||||
API_PROXY_TARGET=http://127.0.0.1:9000
|
||||
OVERSEAS_API_PROXY_TARGET=http://127.0.0.1:9300
|
||||
# 仅开发服务器自动预填;请在不提交的 .env.local 中填写真实开发账号。
|
||||
VITE_PDA_DEV_USERNAME=
|
||||
VITE_PDA_DEV_PASSWORD=
|
||||
|
||||
@@ -25,7 +25,9 @@ export function normalizeReceivingCandidate(value = {}) {
|
||||
|
||||
export function normalizeReceivingSession(value) {
|
||||
if (!value) return null
|
||||
const photos = Array.isArray(value.photos) ? value.photos : []
|
||||
const photos = Array.isArray(value.photos)
|
||||
? value.photos.map((photo) => ({ ...photo, fileId: String(photo.fileId) }))
|
||||
: []
|
||||
return {
|
||||
...value,
|
||||
photos: RECEIVING_PHOTO_ROLES.map((role) => photos.find((photo) => photo?.role === role)).filter(Boolean),
|
||||
|
||||
+11
-2
@@ -3,8 +3,17 @@ const configuredAuthApiRoot = runtimeEnvironment.VITE_AUTH_API_BASE_URL || ''
|
||||
const configuredOverseasApiRoot = runtimeEnvironment.VITE_OVERSEAS_API_BASE_URL || ''
|
||||
const development = Boolean(runtimeEnvironment.DEV)
|
||||
|
||||
export const API_ROOT = String(configuredAuthApiRoot || '/api').replace(/\/$/, '')
|
||||
export const OVERSEAS_API_ROOT = String(configuredOverseasApiRoot || '/api/overseas').replace(/\/$/, '')
|
||||
let selectedAuthApiRoot = configuredAuthApiRoot || '/api'
|
||||
let selectedOverseasApiRoot = configuredOverseasApiRoot || '/overseas-api'
|
||||
|
||||
// H5 must stay same-origin and use Vite's CORS-safe proxy; App builds keep the absolute API roots above.
|
||||
// #ifdef H5
|
||||
selectedAuthApiRoot = runtimeEnvironment.VITE_H5_AUTH_API_BASE_URL || '/api'
|
||||
selectedOverseasApiRoot = runtimeEnvironment.VITE_H5_OVERSEAS_API_BASE_URL || '/overseas-api'
|
||||
// #endif
|
||||
|
||||
export const API_ROOT = String(selectedAuthApiRoot).replace(/\/$/, '')
|
||||
export const OVERSEAS_API_ROOT = String(selectedOverseasApiRoot).replace(/\/$/, '')
|
||||
export const DEV_LOGIN = Object.freeze({
|
||||
username: development ? String(runtimeEnvironment.VITE_PDA_DEV_USERNAME || '') : '',
|
||||
password: development ? String(runtimeEnvironment.VITE_PDA_DEV_PASSWORD || '') : ''
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
cancelDetail() { this.detailOpen = false; receiving.selected = null },
|
||||
async prepareContainer() { if (!this.selected?.containerNo) return; const key = idempotencyKey('prepare', { containerNo: this.selected.containerNo }); try { const value = await this.run('选择当前柜', () => prepareReceivingContainer(this.selected.containerNo, { idempotencyKey: key.value })); releaseIdempotencyKey(key.signature); setCurrentReceiving(value); this.setScanTargetForCurrent(value); this.detailOpen = false } catch (error) { await this.recoverCurrentAfterConflict(error) } },
|
||||
choosePhoto(index) { if (this.busy) return; uni.chooseImage({ count: 1, sourceType: ['camera', 'album'], success: async ({ tempFilePaths }) => { const path = tempFilePaths?.[0]; if (!path) return; try { const file = await this.run('上传签收照片', () => uploadCurrentUnloadPhoto(path)); receiving.photoSlots.splice(index, 1, { ...file, fileId: String(file.fileId), role: this.photoSteps[index].role, localPath: path, uploaded: true }) } catch (_) {} } }) },
|
||||
async signContainer() { if (!this.photosReady) return; const photos = this.photoSlots.map((item, index) => ({ role: this.photoSteps[index].role, fileId: Number(item.fileId) })); const key = idempotencyKey('sign', { containerNo: this.current.containerNo, photos }); try { const value = await this.run('签收当前柜', () => signCurrentReceiving({ photos, idempotencyKey: key.value })); releaseIdempotencyKey(key.signature); setCurrentReceiving(value); receiving.activeScanTarget = selectedInbound(value)?.destinationType === 'OVERSEAS' ? 'carton' : 'sku' } catch (_) {} },
|
||||
async signContainer() { if (!this.photosReady) return; const photos = this.photoSlots.map((item, index) => ({ role: this.photoSteps[index].role, fileId: String(item.fileId) })); const key = idempotencyKey('sign', { containerNo: this.current.containerNo, photos }); try { const value = await this.run('签收当前柜', () => signCurrentReceiving({ photos, idempotencyKey: key.value })); releaseIdempotencyKey(key.signature); setCurrentReceiving(value); receiving.activeScanTarget = selectedInbound(value)?.destinationType === 'OVERSEAS' ? 'carton' : 'sku' } catch (_) {} },
|
||||
chooseInbound(item) { receiving.selectedInboundCode = item.inboundCode; receiving.resolvedCarton = null; receiving.resolvedSku = null; receiving.resolvedPallet = null; this.cartonInput = ''; this.skuInput = ''; this.palletInput = ''; this.packageCount = Math.max(1, Number(item.pendingPackages || 1)); receiving.activeScanTarget = item.destinationType === 'OVERSEAS' ? 'carton' : 'sku' },
|
||||
async resolveCarton(raw) { const cartonMark = String(raw?.detail?.value ?? raw ?? this.cartonInput).trim(); if (!cartonMark || this.busy) return; try { const value = await this.run('解析箱唛', () => resolveCurrentCarton(cartonMark)); receiving.resolvedCarton = value; receiving.selectedInboundCode = value.inboundCode; receiving.resolvedPallet = null; this.cartonInput = value.cartonMark; this.palletInput = ''; receiving.activeScanTarget = 'pallet'; playSuccessSound() } catch (_) {} },
|
||||
async resolveSku(raw) { const skuCode = String(raw?.detail?.value ?? raw ?? this.skuInput).trim(); if (!skuCode || this.busy) return; try { const value = await this.run('解析 SKU', () => resolveCurrentSku(skuCode)); receiving.resolvedSku = value; receiving.selectedInboundCode = value.inboundCode; receiving.resolvedPallet = null; this.skuInput = value.skuCode; this.palletInput = ''; receiving.activeScanTarget = 'pallet'; playSuccessSound() } catch (_) {} },
|
||||
|
||||
@@ -142,7 +142,7 @@ export function restoreSession() {
|
||||
const pair = validatePdaTokenPair(saved.domesticToken, saved.token)
|
||||
Object.assign(session, saved, {
|
||||
currentUser: userFromClaims(pair), tenantId: claimId(pair.domestic, 'tenant_id'),
|
||||
apiBase: saved.apiBase || OVERSEAS_API_ROOT
|
||||
apiBase: OVERSEAS_API_ROOT
|
||||
})
|
||||
return true
|
||||
} catch (_) {
|
||||
|
||||
@@ -5,14 +5,18 @@ import { readFile } from 'node:fs/promises'
|
||||
const read = (path) => readFile(new URL(path, import.meta.url), 'utf8')
|
||||
|
||||
test('overseas PDA is an independent Vue 3 app with separate packaging identity', async () => {
|
||||
const [manifest, pages, pkg] = await Promise.all([
|
||||
read('../src/manifest.json'), read('../src/pages.json'), read('../package.json')
|
||||
const [manifest, pages, pkg, vite] = await Promise.all([
|
||||
read('../src/manifest.json'), read('../src/pages.json'), read('../package.json'), read('../vite.config.js')
|
||||
])
|
||||
assert.match(manifest, /"name"\s*:\s*"柏悦物流\s*PDA\s*海外版"/)
|
||||
assert.match(manifest, /"packagename"\s*:\s*"com\.baiyuelogistics\.pda\.overseas"/)
|
||||
assert.match(manifest, /"vueVersion"\s*:\s*"3"/)
|
||||
assert.doesNotMatch(pages, /pages\/picking|pages\/tasks/)
|
||||
assert.match(pkg, /"build:app"\s*:\s*"uni build -p app"/)
|
||||
assert.match(vite, /API_PROXY_TARGET/)
|
||||
assert.match(vite, /OVERSEAS_API_PROXY_TARGET/)
|
||||
assert.match(vite, /\/overseas-api/)
|
||||
assert.match(vite, /replace\(\/\^\\\/overseas-api\//)
|
||||
})
|
||||
|
||||
test('overseas workbench contains only confirmed inbound and in-warehouse domains', async () => {
|
||||
@@ -34,6 +38,9 @@ test('login has no committed demo credentials and inbound operation implements t
|
||||
assert.doesNotMatch(login, /ChangeMe|platform_admin/)
|
||||
assert.match(login, /DEV_LOGIN\.username|DEV_LOGIN\.password/)
|
||||
assert.match(runtime, /runtimeEnvironment\.DEV|VITE_PDA_DEV_USERNAME|VITE_PDA_DEV_PASSWORD/)
|
||||
assert.match(runtime, /'\/overseas-api'/)
|
||||
assert.match(runtime, /#ifdef H5[\s\S]*?VITE_H5_AUTH_API_BASE_URL/)
|
||||
assert.match(runtime, /#ifdef H5[\s\S]*?VITE_H5_OVERSEAS_API_BASE_URL/)
|
||||
assert.match(operation, /startScanBroadcast/)
|
||||
for (const action of ['prepareReceivingContainer', 'getCurrentReceiving', 'resolveCurrentSku', 'resolveCurrentCarton', 'resolveCurrentPallet', 'receiveCurrentBulk', 'receiveCurrentCarton', 'completeCurrentReceiving']) assert.match(operation, new RegExp(action))
|
||||
for (const path of ['/candidates', '/prepare', '/unload-photos/files', '/sign', '/skus/resolve', '/cartons/resolve', '/pallets/resolve', '/receipts/bulk', '/receipts/carton', '/complete']) assert.match(inbound, new RegExp(path.replaceAll('/', '\\/')))
|
||||
|
||||
@@ -3,12 +3,14 @@ import assert from 'node:assert/strict'
|
||||
import { isNoCurrentReceiving, isReceivingConflict, normalizeReceivingCandidate, normalizeReceivingSession, RECEIVING_PHOTO_ROLES } from '../src/api/receiving-adapter.js'
|
||||
|
||||
test('receiving adapter preserves the three signed photo roles in workflow order', () => {
|
||||
const snowflakeFileId = '2092476334483931138'
|
||||
const session = normalizeReceivingSession({
|
||||
status: 'WAIT_SIGN', totalPackages: 8, receivedPackages: 2, pendingPackages: 6,
|
||||
photos: [{ role: 'DOOR_OPEN', fileId: 3 }, { role: 'LICENSE_PLATE', fileId: 1 }, { role: 'CONTAINER_SEAL', fileId: 2 }],
|
||||
photos: [{ role: 'DOOR_OPEN', fileId: '3' }, { role: 'LICENSE_PLATE', fileId: snowflakeFileId }, { role: 'CONTAINER_SEAL', fileId: '2' }],
|
||||
inbounds: [{ inboundCode: 'IN-1', plannedPalletCount: 2, totalPackages: 8, receivedPackages: 2, pendingPackages: 6 }]
|
||||
})
|
||||
assert.deepEqual(session.photos.map(({ role }) => role), RECEIVING_PHOTO_ROLES)
|
||||
assert.equal(session.photos[0].fileId, snowflakeFileId)
|
||||
assert.equal(session.inbounds[0].plannedPalletCount, 2)
|
||||
})
|
||||
|
||||
|
||||
+17
-2
@@ -1,8 +1,22 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import uni from '@dcloudio/vite-plugin-uni'
|
||||
|
||||
export default defineConfig({
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '')
|
||||
const domesticTarget = env.API_PROXY_TARGET || 'http://127.0.0.1:9000'
|
||||
const overseasTarget = env.OVERSEAS_API_PROXY_TARGET || 'http://127.0.0.1:9300'
|
||||
return {
|
||||
plugins: [uni()],
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': { target: domesticTarget, changeOrigin: true },
|
||||
'/overseas-api': {
|
||||
target: overseasTarget,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/overseas-api/, '/api')
|
||||
}
|
||||
}
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
@@ -18,4 +32,5 @@ export default defineConfig({
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user