/** * Go UserAccount JSON 使用 snake_case(role_id / employee_id / must_change_password / created_at…)。 * 前端 UserAccount / UserProfile 使用 camelCase。在 API 边界统一映射,兼容偶发旧驼峰字段。 */ import type { AccountStatus, LoginResult, MeResult, UserAccount } from '../types/auth'; function pickString(raw: Record, snake: string, camel: string): string { const a = raw[snake]; const b = raw[camel]; if (typeof a === 'string' && a.length > 0) return a; if (typeof b === 'string') return b; if (typeof a === 'string') return a; return ''; } function pickBool(raw: Record, snake: string, camel: string): boolean { if (typeof raw[snake] === 'boolean') return raw[snake] as boolean; if (typeof raw[camel] === 'boolean') return raw[camel] as boolean; return false; } function pickNumber(raw: Record, key: string): number { const v = raw[key]; if (typeof v === 'number' && Number.isFinite(v)) return v; if (typeof v === 'string' && v.trim() !== '' && !Number.isNaN(Number(v))) return Number(v); return 0; } /** 将服务端(或混合)用户对象规范为前端 camelCase UserAccount。 */ export function mapUserAccount(raw: unknown): UserAccount { if (!raw || typeof raw !== 'object') { throw new Error('无效的用户账户响应'); } const o = raw as Record; const statusRaw = pickString(o, 'status', 'status') || 'active'; const status = statusRaw as AccountStatus; return { id: pickNumber(o, 'id'), username: pickString(o, 'username', 'username'), name: pickString(o, 'name', 'name'), status, mustChangePassword: pickBool(o, 'must_change_password', 'mustChangePassword'), roleId: pickString(o, 'role_id', 'roleId'), position: pickString(o, 'position', 'position'), employeeId: pickString(o, 'employee_id', 'employeeId'), department: pickString(o, 'department', 'department'), email: pickString(o, 'email', 'email') || undefined, avatar: pickString(o, 'avatar', 'avatar') || undefined, createdAt: pickString(o, 'created_at', 'createdAt') || undefined, updatedAt: pickString(o, 'updated_at', 'updatedAt') || undefined, }; } export function mapUserAccountList(raw: unknown): UserAccount[] { if (!Array.isArray(raw)) return []; return raw.map((item) => mapUserAccount(item)); } /** * Top-level must_change_password is the sole authority when present. * Never OR-merge with nested user.mustChangePassword — that revived a cleared * flag after FirstLoginPasswordModal only patched the top-level field. */ function resolveMustChangePassword( o: Record, nestedUser: { mustChangePassword?: boolean }, ): boolean { if (typeof o.must_change_password === 'boolean') return o.must_change_password; if (typeof o.mustChangePassword === 'boolean') return o.mustChangePassword as boolean; return !!nestedUser.mustChangePassword; } /** 映射 /auth/login 响应(token + user + must_change_password + env)。 */ export function mapLoginResult(raw: unknown): LoginResult { if (!raw || typeof raw !== 'object') { throw new Error('无效的登录响应'); } const o = raw as Record; const user = mapUserAccount(o.user); const mustChangePassword = resolveMustChangePassword(o, user); const capabilities = Array.isArray(o.capabilities) ? (o.capabilities as unknown[]).map(String) : undefined; return { token: pickString(o, 'token', 'token'), user: { ...user, mustChangePassword }, mustChangePassword, env: pickString(o, 'env', 'env') || 'dev', capabilities, }; } /** 映射 /auth/me 响应。 */ export function mapMeResult(raw: unknown): MeResult { if (!raw || typeof raw !== 'object') { throw new Error('无效的会话响应'); } const o = raw as Record; const user = mapUserAccount(o.user); const mustChangePassword = resolveMustChangePassword(o, user); return { user: { ...user, mustChangePassword }, mustChangePassword, env: pickString(o, 'env', 'env') || 'dev', capabilities: Array.isArray(o.capabilities) ? (o.capabilities as unknown[]).map(String) : [], }; }