fix(pda): leave expired sessions and clarify dispatch navigation

This commit is contained in:
2026-10-03 06:49:54 +08:00
parent 28a046c37f
commit 3990074352
5 changed files with 133 additions and 17 deletions
+40 -13
View File
@@ -1,7 +1,18 @@
import { refresh } from './auth.js'
import { session, replaceTokens, signOut } from '../stores/session.js'
import { session, replaceTokens, signOut, getSessionRevision } from '../stores/session.js'
let refreshPromise = null
let refreshRevision = -1
function requireCurrentSession(revision) {
if (revision !== getSessionRevision()) throw new Error('登录会话已变更,请重新操作')
}
function expireSession(revision) {
if (revision !== getSessionRevision()) return
signOut()
uni.reLaunch({ url: '/pages/login/login' })
}
function errorFrom(response, fallback) {
const body = response?.data || {}
@@ -21,23 +32,27 @@ function successful(response, body) {
return response?.statusCode === 200 && ['00000', '200'].includes(String(body?.code))
}
async function refreshOverseasToken() {
async function refreshOverseasToken(revision) {
requireCurrentSession(revision)
if (!session.refreshToken) {
signOut()
expireSession(revision)
throw new Error('登录状态已过期,请重新登录')
}
if (!refreshPromise) {
refreshPromise = refresh(session.refreshToken)
.then((tokens) => { replaceTokens(tokens); return session.token })
.catch((error) => { signOut(); throw error })
.finally(() => { refreshPromise = null })
if (!refreshPromise || refreshRevision !== revision) {
refreshRevision = revision
const pending = refresh(session.refreshToken)
.then((tokens) => { requireCurrentSession(revision); replaceTokens(tokens); return session.token })
.catch((error) => { expireSession(revision); throw error })
.finally(() => { if (refreshPromise === pending) refreshPromise = null })
refreshPromise = pending
}
return refreshPromise
}
async function tokenAfterUnauthorized(failedToken) {
async function tokenAfterUnauthorized(failedToken, revision) {
requireCurrentSession(revision)
if (session.token && session.token !== failedToken) return session.token
return refreshOverseasToken()
return refreshOverseasToken(revision)
}
function overseasUrl(path) {
@@ -70,13 +85,17 @@ function rawRequest(method, path, data, token) {
}
export async function overseasRequest(method, path, data, retry = true) {
const revision = getSessionRevision()
console.info('[Overseas PDA API] request', { method, path })
const attemptedToken = session.token
let response = await rawRequest(method, path, data, attemptedToken)
requireCurrentSession(revision)
if (retry && unauthorized(response)) {
const replacementToken = await tokenAfterUnauthorized(attemptedToken)
const replacementToken = await tokenAfterUnauthorized(attemptedToken, revision)
response = await rawRequest(method, path, data, replacementToken)
}
requireCurrentSession(revision)
if (unauthorized(response)) expireSession(revision)
const body = response?.data || {}
console.info('[Overseas PDA API] response', { method, path, statusCode: response?.statusCode, code: body?.code,
message: body?.message, requestId: body?.requestId || response?.header?.['x-request-id'] || response?.header?.['X-Request-Id'],
@@ -149,27 +168,35 @@ function rawDownload(path, token) {
}
export async function overseasUpload(path, filePath, name = 'file', retry = true) {
const revision = getSessionRevision()
const attemptedToken = session.token
let response = await rawUpload(path, filePath, name, attemptedToken)
requireCurrentSession(revision)
let body = null
try { body = JSON.parse(response.data || '{}') } catch (_) { /* handled below */ }
if (retry && unauthorized({ ...response, data: body })) {
const replacementToken = await tokenAfterUnauthorized(attemptedToken)
const replacementToken = await tokenAfterUnauthorized(attemptedToken, revision)
response = await rawUpload(path, filePath, name, replacementToken)
try { body = JSON.parse(response.data || '{}') } catch (_) { body = null }
}
requireCurrentSession(revision)
if (unauthorized({ ...response, data: body })) expireSession(revision)
if (!successful(response, body)) throw errorFrom({ ...response, data: body }, '海外作业文件上传失败')
return body.data
}
export async function overseasDownload(path, options = {}) {
const revision = getSessionRevision()
const { retry = true, expectedContentTypePrefix = '' } = options
const attemptedToken = session.token
let response = await rawDownload(path, attemptedToken)
requireCurrentSession(revision)
if (retry && unauthorized(response)) {
const replacementToken = await tokenAfterUnauthorized(attemptedToken)
const replacementToken = await tokenAfterUnauthorized(attemptedToken, revision)
response = await rawDownload(path, replacementToken)
}
requireCurrentSession(revision)
if (unauthorized(response)) expireSession(revision)
if (response?.statusCode !== 200 || !response?.tempFilePath) {
throw errorFrom({ ...response, data: {} }, '签收照片下载失败')
}
+2 -2
View File
@@ -26,12 +26,12 @@
</view>
<template v-if="!loading && !current">
<view class="list-toolbar"><view><text class="list-title">待处理排车任务</text><text class="list-count">{{ tasks.length }} 个任务</text></view><button :disabled="loading" @tap="loadTasks">刷新</button></view>
<view class="list-toolbar"><view><text class="list-title">排车任务</text><text class="list-count">{{ tasks.length }} 个任务</text></view><button :disabled="loading" @tap="loadTasks">刷新</button></view>
<view v-if="tasks.length" class="task-list">
<button v-for="task in tasks" :key="task.dispatchNo" class="task-card" @tap="openTask(task.dispatchNo)">
<view class="task-head"><view class="task-code-row"><text class="business-code">{{ task.dispatchNo || '—' }}</text><text v-if="task.dispatchNo === recentTaskNo" class="recent-badge">上次作业</text></view><text class="status-badge">{{ statusLabel(task.status) }}</text></view>
<text class="task-meta">任务号 {{ task.taskNo || '—' }} · 车牌 {{ task.vehiclePlate || '—' }}</text>
<text class="task-enter">查看并开始拣货 ›</text>
<text class="task-enter">查看任务详情 ›</text>
</button>
</view>
<view v-else-if="!error" class="fulfillment-state"><text class="state-title">暂无排车拣货任务</text><text>可刷新列表,或直接扫描排车单号恢复作业。</text></view>
+5
View File
@@ -5,6 +5,8 @@ import { resetFulfillmentSession } from './fulfillment.js'
import { resetWarehouseWork } from './warehouse-work.js'
const SESSION_KEY = 'overseas-pda-auth-session'
let sessionRevision = 0
export function getSessionRevision() { return sessionRevision }
export const PDA_ROLE = 'OVERSEAS_WAREHOUSE_PDA_OPERATOR'
export const PDA_AUTHORITY = 'lastleg:pda:operate'
@@ -108,6 +110,7 @@ function persistSession() {
}
export function signIn(account, warehouse, options = {}) {
sessionRevision += 1
session.currentUser = { id: String(account.id), name: account.name, role: account.role, roles: account.roles || [], warehouse,
warehouseId: account.warehouseId || null, warehouseCode: account.warehouseCode || '', warehouseName: account.warehouseName || '' }
session.token = options.token || account.token || ''
@@ -184,6 +187,7 @@ export function restoreSession() {
currentUser: userFromClaims(pair, saved.currentUser), tenantId: claimId(pair.claims, 'tenant_id'),
apiBase: API_ROOT, authorization: pair.authorization
})
sessionRevision += 1
return true
} catch (_) {
signOut()
@@ -192,6 +196,7 @@ export function restoreSession() {
}
export function signOut() {
sessionRevision += 1
session.currentUser = null
session.token = ''
session.refreshToken = ''