feat(admin,player,api): 玩家账号密码管理与代理上下分
新增玩家头像、可查密码与全局改密/改账号开关;玩家资料页合并账号密码展示;代理直属玩家列表支持自定义上下分。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,6 +9,8 @@ export interface PlayerHomeMatch {
|
||||
id: string;
|
||||
homeTeamName: string;
|
||||
awayTeamName: string;
|
||||
homeTeamCode?: string;
|
||||
awayTeamCode?: string;
|
||||
startTime: string;
|
||||
isHot?: boolean;
|
||||
}
|
||||
|
||||
143
apps/player/src/composables/usePlayerProfile.ts
Normal file
143
apps/player/src/composables/usePlayerProfile.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { ref, computed } from 'vue';
|
||||
import { isValidAvatarKey, playerAvatarUrl, randomAvatarKey } from '@thebet365/shared';
|
||||
import api from '../api';
|
||||
|
||||
type ProfileData = {
|
||||
id?: string | number;
|
||||
username?: string;
|
||||
locale?: string;
|
||||
preferences?: {
|
||||
phone?: string | null;
|
||||
email?: string | null;
|
||||
avatarKey?: string | null;
|
||||
allowPasswordChange?: boolean;
|
||||
allowUsernameChange?: boolean;
|
||||
viewablePassword?: string | null;
|
||||
};
|
||||
wallet?: { availableBalance: string; frozenBalance: string };
|
||||
};
|
||||
|
||||
const AVATAR_CACHE_PREFIX = 'player_avatar_key:';
|
||||
|
||||
const profileRaw = ref<ProfileData | null>(null);
|
||||
const loading = ref(false);
|
||||
let loadPromise: Promise<void> | null = null;
|
||||
let assigningDefault = false;
|
||||
|
||||
function profileSeed(profile: ProfileData | null): string {
|
||||
if (!profile) return '';
|
||||
return String(profile.id ?? profile.username ?? '');
|
||||
}
|
||||
|
||||
function readCachedAvatarKey(seed: string): string | null {
|
||||
if (!seed) return null;
|
||||
try {
|
||||
const key = localStorage.getItem(`${AVATAR_CACHE_PREFIX}${seed}`);
|
||||
return key && isValidAvatarKey(key) ? key : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeCachedAvatarKey(seed: string, key: string) {
|
||||
if (!seed || !key) return;
|
||||
try {
|
||||
localStorage.setItem(`${AVATAR_CACHE_PREFIX}${seed}`, key);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function applyAvatarKey(key: string | null) {
|
||||
if (!profileRaw.value) {
|
||||
profileRaw.value = { preferences: { avatarKey: key } };
|
||||
return;
|
||||
}
|
||||
profileRaw.value = {
|
||||
...profileRaw.value,
|
||||
preferences: {
|
||||
...profileRaw.value.preferences,
|
||||
avatarKey: key,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function ensureDefaultAvatar() {
|
||||
if (assigningDefault || !profileRaw.value) return;
|
||||
|
||||
const seed = profileSeed(profileRaw.value);
|
||||
const current =
|
||||
profileRaw.value.preferences?.avatarKey ?? readCachedAvatarKey(seed);
|
||||
if (current && isValidAvatarKey(current)) {
|
||||
applyAvatarKey(current);
|
||||
return;
|
||||
}
|
||||
|
||||
assigningDefault = true;
|
||||
const key = randomAvatarKey(seed);
|
||||
try {
|
||||
try {
|
||||
await api.patch('/player/profile', { avatarKey: key });
|
||||
} catch {
|
||||
/* 数据库未迁移等情况仍展示本地头像 */
|
||||
}
|
||||
applyAvatarKey(key);
|
||||
writeCachedAvatarKey(seed, key);
|
||||
} finally {
|
||||
assigningDefault = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProfile(force = false) {
|
||||
if (loadPromise) return loadPromise;
|
||||
|
||||
if (!force && profileRaw.value) {
|
||||
await ensureDefaultAvatar();
|
||||
return;
|
||||
}
|
||||
|
||||
loadPromise = (async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data } = await api.get('/player/profile');
|
||||
profileRaw.value = data.data ?? null;
|
||||
await ensureDefaultAvatar();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
loadPromise = null;
|
||||
}
|
||||
})();
|
||||
|
||||
return loadPromise;
|
||||
}
|
||||
|
||||
const avatarKey = computed(() => {
|
||||
const saved = profileRaw.value?.preferences?.avatarKey;
|
||||
if (saved && isValidAvatarKey(saved)) return saved;
|
||||
|
||||
const seed = profileSeed(profileRaw.value);
|
||||
if (!seed) return null;
|
||||
|
||||
const cached = readCachedAvatarKey(seed);
|
||||
if (cached) return cached;
|
||||
|
||||
return randomAvatarKey(seed);
|
||||
});
|
||||
const avatarUrl = computed(() => playerAvatarUrl(avatarKey.value));
|
||||
|
||||
function setAvatarKey(key: string | null) {
|
||||
applyAvatarKey(key);
|
||||
const seed = profileSeed(profileRaw.value);
|
||||
if (key && seed) writeCachedAvatarKey(seed, key);
|
||||
}
|
||||
|
||||
export function usePlayerProfile() {
|
||||
return {
|
||||
profileRaw,
|
||||
loading,
|
||||
avatarKey,
|
||||
avatarUrl,
|
||||
loadProfile,
|
||||
setAvatarKey,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user