Merge branch 'main' into dev
This commit is contained in:
@@ -6,6 +6,16 @@ const ADMIN_DISPLAY_NAME_KEY = 'order-site-admin-display-name'
|
||||
const ADMIN_ROLE_KEY = 'order-site-admin-role'
|
||||
const ADMIN_PERMISSIONS_KEY = 'order-site-admin-permissions'
|
||||
|
||||
const ADMIN_SESSION_STORAGE_KEYS = new Set([
|
||||
ADMIN_TOKEN_KEY,
|
||||
ADMIN_EXPIRES_AT_KEY,
|
||||
ADMIN_USER_ID_KEY,
|
||||
ADMIN_USERNAME_KEY,
|
||||
ADMIN_DISPLAY_NAME_KEY,
|
||||
ADMIN_ROLE_KEY,
|
||||
ADMIN_PERMISSIONS_KEY,
|
||||
])
|
||||
|
||||
const ADMIN_ROLE_LEVEL: Record<'support' | 'operator' | 'admin' | 'finance', number> = {
|
||||
support: 1,
|
||||
operator: 2,
|
||||
@@ -143,6 +153,37 @@ export function clearAdminSession() {
|
||||
localStorage.removeItem(ADMIN_PERMISSIONS_KEY)
|
||||
}
|
||||
|
||||
/** 滑动续期:只更新本地空闲到期时间,Token 本身不重签(exp 是绝对上限)。 */
|
||||
export function setAdminSessionExpiresAt(expiresAt: string) {
|
||||
const normalized = String(expiresAt || '').trim()
|
||||
if (!normalized || normalized === getAdminTokenExpiresAt()) return
|
||||
localStorage.setItem(ADMIN_EXPIRES_AT_KEY, normalized)
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new Event('admin-session-renewed'))
|
||||
}
|
||||
}
|
||||
|
||||
/** 清理失效会话并通知当前标签页,避免并发请求各自重复跳转。 */
|
||||
export function expireAdminSession() {
|
||||
const hadSession = Boolean(getAdminToken() || getAdminTokenExpiresAt())
|
||||
clearAdminSession()
|
||||
if (hadSession && typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new Event('admin-session-expired'))
|
||||
}
|
||||
return hadSession
|
||||
}
|
||||
|
||||
export function isAdminSessionStorageKey(key: string | null) {
|
||||
return Boolean(key && ADMIN_SESSION_STORAGE_KEYS.has(key))
|
||||
}
|
||||
|
||||
export function getAdminSessionExpiryDelay(expiresAt: string, now = Date.now()) {
|
||||
if (!expiresAt) return null
|
||||
const expiresAtMs = Date.parse(expiresAt)
|
||||
if (!Number.isFinite(expiresAtMs)) return null
|
||||
return Math.max(0, expiresAtMs - now)
|
||||
}
|
||||
|
||||
export function hasAdminSession() {
|
||||
const token = getAdminToken()
|
||||
const expiresAt = getAdminTokenExpiresAt()
|
||||
|
||||
@@ -73,6 +73,16 @@ export function clearWorkerSession() {
|
||||
localStorage.removeItem(WORKER_STATUS_KEY)
|
||||
}
|
||||
|
||||
/** 滑动续期:只更新本地空闲到期时间,Token 本身不重签(exp 是绝对上限)。 */
|
||||
export function setWorkerSessionExpiresAt(expiresAt: string) {
|
||||
const normalized = String(expiresAt || '').trim()
|
||||
if (!normalized || normalized === getWorkerTokenExpiresAt()) return
|
||||
localStorage.setItem(WORKER_EXPIRES_AT_KEY, normalized)
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new Event('worker-session-renewed'))
|
||||
}
|
||||
}
|
||||
|
||||
/** 清理失效会话并通知当前标签页,避免并发请求各自重复跳转。 */
|
||||
export function expireWorkerSession() {
|
||||
const hadSession = Boolean(getWorkerToken() || getWorkerTokenExpiresAt())
|
||||
@@ -87,6 +97,10 @@ export function isWorkerSessionStorageKey(key: string | null) {
|
||||
return Boolean(key && WORKER_SESSION_STORAGE_KEYS.has(key))
|
||||
}
|
||||
|
||||
export function isWorkerSessionExpiryKey(key: string | null) {
|
||||
return key === WORKER_EXPIRES_AT_KEY
|
||||
}
|
||||
|
||||
export function getWorkerSessionExpiryDelay(expiresAt: string, now = Date.now()) {
|
||||
if (!expiresAt) return null
|
||||
const expiresAtMs = Date.parse(expiresAt)
|
||||
|
||||
@@ -163,17 +163,63 @@ export function patchWorkerWorkOrderListEnvelope(
|
||||
}
|
||||
}
|
||||
|
||||
/** 无限下拉(大厅):第 1 页允许插入,其余页只做就地更新。 */
|
||||
function readWorkOrderId(item: unknown): number {
|
||||
return isRecord(item) ? toNumber(item.workOrderId) : 0
|
||||
}
|
||||
|
||||
function readPinnedAtMs(item: unknown): number | null {
|
||||
if (!isRecord(item)) return null
|
||||
const pinnedAt = String(item.pinnedAt || '').trim()
|
||||
if (!pinnedAt) return null
|
||||
const parsed = Date.parse(pinnedAt)
|
||||
// 有置顶标记但时间不可解析时仍算置顶,宁可排前面也不能被普通单挤下去。
|
||||
return Number.isFinite(parsed) ? parsed : 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 大厅排序必须与服务端一致:置顶单永远在最前(pinned_at DESC),其余按 id DESC。
|
||||
* 实时事件把新单插入第 1 页后必须重排,否则新单会插到置顶单上面。
|
||||
*/
|
||||
export function sortWorkerHallItems(items: unknown[]): unknown[] {
|
||||
return items.slice().sort((left, right) => {
|
||||
const leftPinnedAt = readPinnedAtMs(left)
|
||||
const rightPinnedAt = readPinnedAtMs(right)
|
||||
if (leftPinnedAt !== null && rightPinnedAt === null) return -1
|
||||
if (leftPinnedAt === null && rightPinnedAt !== null) return 1
|
||||
if (leftPinnedAt !== null && rightPinnedAt !== null && leftPinnedAt !== rightPinnedAt) {
|
||||
return rightPinnedAt - leftPinnedAt
|
||||
}
|
||||
return readWorkOrderId(right) - readWorkOrderId(left)
|
||||
})
|
||||
}
|
||||
|
||||
/** 无限下拉(大厅):第 1 页允许插入,其余页只做就地更新;最终按置顶优先重排。 */
|
||||
export function patchWorkerWorkOrderInfiniteEnvelope(
|
||||
current: unknown,
|
||||
event: WorkerListEvent,
|
||||
insert = false,
|
||||
): unknown {
|
||||
if (!isRecord(current) || !Array.isArray(current.pages)) return current
|
||||
return {
|
||||
...current,
|
||||
pages: current.pages.map((page: unknown, index: number) =>
|
||||
patchWorkerWorkOrderListEnvelope(page, event, insert && index === 0),
|
||||
),
|
||||
const pages = current.pages.map((page: unknown, index: number) =>
|
||||
patchWorkerWorkOrderListEnvelope(page, event, insert && index === 0),
|
||||
)
|
||||
|
||||
const lists: unknown[][] = []
|
||||
for (const page of pages) {
|
||||
if (!isRecord(page) || !isRecord(page.data) || !Array.isArray(page.data.items)) {
|
||||
return { ...current, pages }
|
||||
}
|
||||
lists.push(page.data.items)
|
||||
}
|
||||
|
||||
// 第 2 页之后被置顶的单也要提到第 1 页最前,所以跨页统一排序再按原页长度切回。
|
||||
const sorted = sortWorkerHallItems(lists.flat())
|
||||
let cursor = 0
|
||||
const sortedPages = pages.map((page, index) => {
|
||||
const length = lists[index]?.length || 0
|
||||
const items = sorted.slice(cursor, cursor + length)
|
||||
cursor += length
|
||||
return isRecord(page) && isRecord(page.data) ? { ...page, data: { ...page.data, items } } : page
|
||||
})
|
||||
return { ...current, pages: sortedPages }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user