R64 A1-A6:新员工入门引导
这个提交包含在:
@@ -9,7 +9,7 @@ import {
|
||||
useState,
|
||||
} from "react";
|
||||
import { configureHttpAuth } from "../api/http";
|
||||
import type { AuthState, LoginResponse, MeResponse } from "../api/types";
|
||||
import type { AuthState, LoginResponse, MeResponse, ProfileCompleteness } from "../api/types";
|
||||
import { changePassword as changePasswordApi } from "../api/auth";
|
||||
import { clearPwaRuntimeCaches } from "../pwa";
|
||||
import {
|
||||
@@ -53,6 +53,13 @@ const OFFLINE_AUTH_SNAPSHOT_VERSION = 1;
|
||||
const OFFLINE_AUTH_SNAPSHOT_TTL_MS = 24 * 60 * 60 * 1000;
|
||||
const PROACTIVE_REFRESH_RATIO = 0.8;
|
||||
const MIN_REFRESH_DELAY_MS = 1000;
|
||||
const DEFAULT_PROFILE_COMPLETENESS: ProfileCompleteness = {
|
||||
hasEmergencyContact: false,
|
||||
hasBankAccount: false,
|
||||
hasAvatar: false,
|
||||
hasIdNumber: false,
|
||||
score: 0,
|
||||
};
|
||||
|
||||
function isWindowAvailable() {
|
||||
return typeof window !== "undefined";
|
||||
@@ -69,6 +76,28 @@ function clearSessionHint() {
|
||||
document.cookie = "has_session=; path=/; max-age=0";
|
||||
}
|
||||
|
||||
function normalizeProfileCompleteness(value: unknown): ProfileCompleteness {
|
||||
if (!value || typeof value !== "object") {
|
||||
return DEFAULT_PROFILE_COMPLETENESS;
|
||||
}
|
||||
|
||||
const payload = value as Partial<ProfileCompleteness>;
|
||||
return {
|
||||
hasEmergencyContact: Boolean(payload.hasEmergencyContact),
|
||||
hasBankAccount: Boolean(payload.hasBankAccount),
|
||||
hasAvatar: Boolean(payload.hasAvatar),
|
||||
hasIdNumber: Boolean(payload.hasIdNumber),
|
||||
score: typeof payload.score === "number" && Number.isFinite(payload.score) ? payload.score : 0,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeMeResponse(value: MeResponse | Partial<MeResponse>) {
|
||||
return {
|
||||
...value,
|
||||
profileCompleteness: normalizeProfileCompleteness(value.profileCompleteness),
|
||||
} as MeResponse;
|
||||
}
|
||||
|
||||
function readMustChangePasswordFromStorage() {
|
||||
return isWindowAvailable() && window.localStorage.getItem(MUST_CHANGE_PASSWORD_STORAGE_KEY) === "1";
|
||||
}
|
||||
@@ -110,7 +139,7 @@ function readOfflineAuthSnapshot() {
|
||||
|
||||
return {
|
||||
version: OFFLINE_AUTH_SNAPSHOT_VERSION,
|
||||
me: parsed.me,
|
||||
me: normalizeMeResponse(parsed.me as Partial<MeResponse>),
|
||||
permissions: parsed.permissions.map((item) => String(item)),
|
||||
savedAt: savedAt.toISOString(),
|
||||
} satisfies OfflineAuthSnapshot;
|
||||
@@ -127,7 +156,7 @@ function writeOfflineAuthSnapshot(me: MeResponse, permissions: string[]) {
|
||||
|
||||
const snapshot: OfflineAuthSnapshot = {
|
||||
version: OFFLINE_AUTH_SNAPSHOT_VERSION,
|
||||
me,
|
||||
me: normalizeMeResponse(me),
|
||||
permissions,
|
||||
savedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
在新工单中引用
屏蔽一个用户