feat(admin,api,player): 代理层级管理、额度上下分与玩家钱包详情

新增代理管理器与二级代理体系,完善信用额度/上下分上下文与冻结策略;代理端玩家与子代理管理增强;玩家端新增钱包详情页与交易筛选优化。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-08 15:34:12 +08:00
parent b2216abd0c
commit 414998ce36
54 changed files with 6641 additions and 481 deletions

View File

@@ -8,15 +8,43 @@ export interface StaffUser {
userType: StaffUserType;
locale?: string;
role?: string;
agentLevel?: number | null;
}
const TOKEN_KEY = 'manage_token';
const USER_KEY = 'manage_user';
function decodeJwtStaffClaims(rawToken: string): Partial<StaffUser> | null {
try {
const segment = rawToken.split('.')[1];
if (!segment) return null;
const padded = segment.replace(/-/g, '+').replace(/_/g, '/');
const payload = JSON.parse(atob(padded)) as {
sub?: string;
username?: string;
userType?: string;
role?: string;
};
if (payload.userType !== 'ADMIN' && payload.userType !== 'AGENT') return null;
if (!payload.sub || !payload.username) return null;
return {
id: payload.sub,
username: payload.username,
userType: payload.userType as StaffUserType,
role: payload.role,
};
} catch {
return null;
}
}
function loadUser(): StaffUser | null {
try {
const raw = localStorage.getItem(USER_KEY);
return raw ? (JSON.parse(raw) as StaffUser) : null;
if (!raw) return null;
const parsed = JSON.parse(raw) as Partial<StaffUser>;
if (!parsed.id || !parsed.username || !parsed.userType) return null;
return parsed as StaffUser;
} catch {
return null;
}
@@ -28,14 +56,14 @@ function migrateLegacyTokens() {
const legacyAgent = localStorage.getItem('agent_token');
if (legacyAdmin) {
localStorage.setItem(TOKEN_KEY, legacyAdmin);
localStorage.setItem(USER_KEY, JSON.stringify({ userType: 'ADMIN' }));
localStorage.removeItem('admin_token');
localStorage.removeItem(USER_KEY);
return;
}
if (legacyAgent) {
localStorage.setItem(TOKEN_KEY, legacyAgent);
localStorage.setItem(USER_KEY, JSON.stringify({ userType: 'AGENT' }));
localStorage.removeItem('agent_token');
localStorage.removeItem(USER_KEY);
}
}
@@ -44,6 +72,42 @@ migrateLegacyTokens();
const token = ref(localStorage.getItem(TOKEN_KEY) || '');
const user = ref<StaffUser | null>(loadUser());
/** Align manage_user.userType with JWT when localStorage is stale (common after account switch). */
export function reconcileStaffSessionFromToken(): boolean {
if (!token.value) return false;
const claims = decodeJwtStaffClaims(token.value);
if (!claims?.userType || !claims.id || !claims.username) return false;
if (
user.value?.id === claims.id &&
user.value.username === claims.username &&
user.value.userType === claims.userType
) {
return true;
}
const next: StaffUser = {
id: claims.id,
username: claims.username,
userType: claims.userType,
locale: user.value?.locale,
role: claims.role ?? user.value?.role,
};
user.value = next;
localStorage.setItem(USER_KEY, JSON.stringify(next));
return true;
}
reconcileStaffSessionFromToken();
if (typeof window !== 'undefined') {
window.addEventListener('storage', () => {
token.value = localStorage.getItem(TOKEN_KEY) || '';
user.value = loadUser();
reconcileStaffSessionFromToken();
});
}
export function clearStaffSession() {
token.value = '';
user.value = null;
@@ -51,11 +115,18 @@ export function clearStaffSession() {
localStorage.removeItem(USER_KEY);
localStorage.removeItem('admin_token');
localStorage.removeItem('agent_token');
void import('../utils/session-hydrate').then((m) => m.resetStaffSessionHydration());
}
function resolveUserType(): StaffUserType | null {
return user.value?.userType ?? decodeJwtStaffClaims(token.value)?.userType ?? null;
}
export function useAuthStore() {
const isAdmin = computed(() => user.value?.userType === 'ADMIN');
const isAgent = computed(() => user.value?.userType === 'AGENT');
const isAdmin = computed(() => resolveUserType() === 'ADMIN');
const isAgent = computed(() => resolveUserType() === 'AGENT');
const isTier1Agent = computed(() => isAgent.value && user.value?.agentLevel === 1);
const isTier2Agent = computed(() => isAgent.value && user.value?.agentLevel === 2);
const portalLabel = computed(() => (isAdmin.value ? '平台后台' : '代理后台'));
function setSession(newToken: string, newUser: StaffUser) {
@@ -76,9 +147,12 @@ export function useAuthStore() {
user,
isAdmin,
isAgent,
isTier1Agent,
isTier2Agent,
portalLabel,
setSession,
logout,
clearStaffSession,
reconcileStaffSessionFromToken,
};
}