feat(admin,player,api): 玩家账号密码管理与代理上下分
新增玩家头像、可查密码与全局改密/改账号开关;玩家资料页合并账号密码展示;代理直属玩家列表支持自定义上下分。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
"test:cov": "jest --coverage",
|
||||
"db:generate": "prisma generate",
|
||||
"db:migrate": "prisma migrate dev",
|
||||
"db:migrate:deploy": "prisma migrate deploy",
|
||||
"db:migrate:deploy": "prisma migrate deploy && prisma generate",
|
||||
"db:seed": "ts-node prisma/seed.ts",
|
||||
"db:studio": "prisma studio"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable: user_preferences 增加头像(内置球员 key)
|
||||
ALTER TABLE "user_preferences" ADD COLUMN IF NOT EXISTS "avatar_key" VARCHAR(128);
|
||||
@@ -0,0 +1,4 @@
|
||||
-- AlterTable: 玩家账号权限与后台可查密码
|
||||
ALTER TABLE "user_preferences" ADD COLUMN IF NOT EXISTS "allow_password_change" BOOLEAN NOT NULL DEFAULT true;
|
||||
ALTER TABLE "user_preferences" ADD COLUMN IF NOT EXISTS "allow_username_change" BOOLEAN NOT NULL DEFAULT false;
|
||||
ALTER TABLE "user_preferences" ADD COLUMN IF NOT EXISTS "managed_password" VARCHAR(128);
|
||||
@@ -52,13 +52,17 @@ model UserAuth {
|
||||
}
|
||||
|
||||
model UserPreference {
|
||||
id BigInt @id @default(autoincrement())
|
||||
userId BigInt @unique @map("user_id")
|
||||
locale String @default("en-US") @db.VarChar(10)
|
||||
phone String? @db.VarChar(32)
|
||||
email String? @db.VarChar(128)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
id BigInt @id @default(autoincrement())
|
||||
userId BigInt @unique @map("user_id")
|
||||
locale String @default("en-US") @db.VarChar(10)
|
||||
phone String? @db.VarChar(32)
|
||||
email String? @db.VarChar(128)
|
||||
avatarKey String? @map("avatar_key") @db.VarChar(128)
|
||||
allowPasswordChange Boolean @default(true) @map("allow_password_change")
|
||||
allowUsernameChange Boolean @default(false) @map("allow_username_change")
|
||||
managedPassword String? @map("managed_password") @db.VarChar(128)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
|
||||
@@ -579,7 +579,7 @@ async function main() {
|
||||
parentId: agent1.id,
|
||||
auth: { create: { passwordHash: playerHash } },
|
||||
wallet: { create: { availableBalance: 1000 } },
|
||||
preferences: { create: { locale: 'zh-CN' } },
|
||||
preferences: { create: { locale: 'zh-CN', managedPassword: 'Player@123' } },
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { APP_GUARD } from '@nestjs/core';
|
||||
import { JwtAuthGuard } from './domains/identity/guards';
|
||||
import { PrismaModule } from './shared/prisma/prisma.module';
|
||||
import { SystemConfigModule } from './shared/config/system-config.module';
|
||||
import { IdentityModule } from './domains/identity/identity.module';
|
||||
import { AgentsModule } from './domains/agent/agents.module';
|
||||
import { WalletModule } from './domains/ledger/wallet.module';
|
||||
@@ -21,6 +22,7 @@ import { AgentPortalModule } from './applications/agent/agent-portal.module';
|
||||
ConfigModule.forRoot({ isGlobal: true }),
|
||||
ScheduleModule.forRoot(),
|
||||
PrismaModule,
|
||||
SystemConfigModule,
|
||||
IdentityModule,
|
||||
AgentsModule,
|
||||
WalletModule,
|
||||
|
||||
@@ -29,6 +29,7 @@ import { AuditService } from '../../domains/operations/audit/audit.service';
|
||||
import { BetsService } from '../../domains/betting/bets.service';
|
||||
import { PrismaService } from '../../shared/prisma/prisma.service';
|
||||
import { AdminDashboardService } from './admin-dashboard.service';
|
||||
import { SystemConfigService } from '../../shared/config/system-config.service';
|
||||
import {
|
||||
IsString,
|
||||
IsNumber,
|
||||
@@ -127,6 +128,25 @@ class UpdatePlayerAdminDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
parentId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
username?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(8)
|
||||
password?: string;
|
||||
}
|
||||
|
||||
class PlayerAccountSettingsDto {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
allowPasswordChange?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
allowUsernameChange?: boolean;
|
||||
}
|
||||
|
||||
class CreateAgentAdminDto {
|
||||
@@ -442,6 +462,7 @@ export class AdminController {
|
||||
private bets: BetsService,
|
||||
private prisma: PrismaService,
|
||||
private readonly dashboardService: AdminDashboardService,
|
||||
private systemConfig: SystemConfigService,
|
||||
) {}
|
||||
|
||||
@Get('dashboard')
|
||||
@@ -450,6 +471,28 @@ export class AdminController {
|
||||
return jsonResponse(overview);
|
||||
}
|
||||
|
||||
@Get('users/settings/account')
|
||||
async getPlayerAccountSettings() {
|
||||
const settings = await this.systemConfig.getPlayerAccountSettings();
|
||||
return jsonResponse(settings);
|
||||
}
|
||||
|
||||
@Put('users/settings/account')
|
||||
async updatePlayerAccountSettings(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Body() dto: PlayerAccountSettingsDto,
|
||||
) {
|
||||
const settings = await this.systemConfig.updatePlayerAccountSettings(dto);
|
||||
await this.audit.log({
|
||||
operatorId,
|
||||
operatorType: 'ADMIN',
|
||||
action: 'UPDATE_PLAYER_ACCOUNT_SETTINGS',
|
||||
module: 'USERS',
|
||||
afterData: JSON.stringify(settings),
|
||||
});
|
||||
return jsonResponse(settings);
|
||||
}
|
||||
|
||||
@Get('users')
|
||||
async listUsers(
|
||||
@Query('page') page?: string,
|
||||
|
||||
@@ -13,6 +13,7 @@ import { JwtAuthGuard, PlayerGuard } from '../../domains/identity/guards';
|
||||
import { CurrentUser } from '../../shared/common/decorators';
|
||||
import { jsonResponse } from '../../shared/common/filters';
|
||||
import { UsersService } from '../../domains/identity/users.service';
|
||||
import { SystemConfigService } from '../../shared/config/system-config.service';
|
||||
import { WalletService } from '../../domains/ledger/wallet.service';
|
||||
import { MatchesService } from '../../domains/catalog/matches.service';
|
||||
import { OutrightService } from '../../domains/catalog/outright.service';
|
||||
@@ -72,6 +73,14 @@ class UpdateProfileDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
email?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
avatarKey?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
username?: string;
|
||||
}
|
||||
|
||||
@ApiTags('Player')
|
||||
@@ -87,12 +96,39 @@ export class PlayerController {
|
||||
private bets: BetsService,
|
||||
private content: ContentService,
|
||||
private cashback: CashbackService,
|
||||
private systemConfig: SystemConfigService,
|
||||
) {}
|
||||
|
||||
private async formatPlayerProfile(user: NonNullable<Awaited<ReturnType<UsersService['findById']>>>) {
|
||||
const accountSettings = await this.systemConfig.getPlayerAccountSettings();
|
||||
const prefs = user.preferences;
|
||||
const viewablePassword = prefs?.managedPassword ?? null;
|
||||
const safePrefs = prefs
|
||||
? (({
|
||||
managedPassword: _m,
|
||||
allowPasswordChange: _a,
|
||||
allowUsernameChange: _b,
|
||||
...rest
|
||||
}) => rest)(prefs)
|
||||
: {};
|
||||
return {
|
||||
...user,
|
||||
id: user.id.toString(),
|
||||
parentId: user.parentId?.toString() ?? null,
|
||||
preferences: {
|
||||
...safePrefs,
|
||||
viewablePassword,
|
||||
allowPasswordChange: accountSettings.allowPasswordChange,
|
||||
allowUsernameChange: accountSettings.allowUsernameChange,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@Get('profile')
|
||||
async profile(@CurrentUser('id') userId: bigint) {
|
||||
const user = await this.users.findById(userId);
|
||||
return jsonResponse(user);
|
||||
if (!user) return jsonResponse(null);
|
||||
return jsonResponse(await this.formatPlayerProfile(user));
|
||||
}
|
||||
|
||||
@Post('language')
|
||||
@@ -104,7 +140,8 @@ export class PlayerController {
|
||||
@Patch('profile')
|
||||
async updateProfile(@CurrentUser('id') userId: bigint, @Body() dto: UpdateProfileDto) {
|
||||
const user = await this.users.updateProfile(userId, dto);
|
||||
return jsonResponse(user);
|
||||
if (!user) return jsonResponse(null);
|
||||
return jsonResponse(await this.formatPlayerProfile(user));
|
||||
}
|
||||
|
||||
@Get('home')
|
||||
|
||||
@@ -626,6 +626,7 @@ export class AgentsService {
|
||||
locale,
|
||||
phone: data.phone?.trim() || null,
|
||||
email: data.email?.trim() || null,
|
||||
managedPassword: data.password,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { JwtService } from '@nestjs/jwt';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { PrismaService } from '../../shared/prisma/prisma.service';
|
||||
import { SystemConfigService } from '../../shared/config/system-config.service';
|
||||
|
||||
const MAX_LOGIN_FAILS = 5;
|
||||
const LOCK_DURATION_MS = 15 * 60 * 1000;
|
||||
@@ -20,6 +21,7 @@ export class AuthService {
|
||||
private prisma: PrismaService,
|
||||
private jwt: JwtService,
|
||||
private config: ConfigService,
|
||||
private systemConfig: SystemConfigService,
|
||||
) {}
|
||||
|
||||
/** 平台管理员 / 代理统一登录(按 userType 签发对应 JWT) */
|
||||
@@ -107,6 +109,11 @@ export class AuthService {
|
||||
const auth = await this.prisma.userAuth.findUnique({ where: { userId } });
|
||||
if (!auth) throw new UnauthorizedException('User not found');
|
||||
|
||||
const settings = await this.systemConfig.getPlayerAccountSettings();
|
||||
if (!settings.allowPasswordChange) {
|
||||
throw new ForbiddenException('当前平台未开放玩家自行修改密码');
|
||||
}
|
||||
|
||||
const valid = await bcrypt.compare(oldPassword, auth.passwordHash);
|
||||
if (!valid) throw new UnauthorizedException('Invalid old password');
|
||||
|
||||
@@ -115,6 +122,10 @@ export class AuthService {
|
||||
where: { userId },
|
||||
data: { passwordHash: hash },
|
||||
});
|
||||
await this.prisma.userPreference.updateMany({
|
||||
where: { userId },
|
||||
data: { managedPassword: null },
|
||||
});
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { SUPPORTED_LOCALES } from '@thebet365/shared';
|
||||
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { SUPPORTED_LOCALES, isValidAvatarKey } from '@thebet365/shared';
|
||||
import { PrismaService } from '../../shared/prisma/prisma.service';
|
||||
import { SystemConfigService } from '../../shared/config/system-config.service';
|
||||
import { AgentsService } from '../agent/agents.service';
|
||||
|
||||
export type PlayerListFilters = {
|
||||
@@ -14,6 +16,7 @@ export class UsersService {
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private agents: AgentsService,
|
||||
private systemConfig: SystemConfigService,
|
||||
) {}
|
||||
|
||||
private formatPlayerRow(
|
||||
@@ -26,7 +29,11 @@ export class UsersService {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
wallet?: { availableBalance: { toString(): string }; frozenBalance: { toString(): string } } | null;
|
||||
preferences?: { phone: string | null; email: string | null } | null;
|
||||
preferences?: {
|
||||
phone: string | null;
|
||||
email: string | null;
|
||||
managedPassword?: string | null;
|
||||
} | null;
|
||||
parent?: { username: string } | null;
|
||||
auth?: { lastLoginAt: Date | null } | null;
|
||||
},
|
||||
@@ -41,6 +48,7 @@ export class UsersService {
|
||||
parentUsername: u.parent?.username ?? null,
|
||||
phone: u.preferences?.phone ?? null,
|
||||
email: u.preferences?.email ?? null,
|
||||
managedPassword: u.preferences?.managedPassword ?? null,
|
||||
availableBalance: u.wallet?.availableBalance?.toString() ?? '0',
|
||||
frozenBalance: u.wallet?.frozenBalance?.toString() ?? '0',
|
||||
lastLoginAt: u.auth?.lastLoginAt ?? null,
|
||||
@@ -81,13 +89,61 @@ export class UsersService {
|
||||
});
|
||||
}
|
||||
|
||||
async updateProfile(userId: bigint, data: { phone?: string; email?: string }) {
|
||||
const phone = data.phone?.trim() || null;
|
||||
const email = data.email?.trim() || null;
|
||||
async updateProfile(
|
||||
userId: bigint,
|
||||
data: { phone?: string; email?: string; avatarKey?: string | null; username?: string },
|
||||
) {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
include: { preferences: true },
|
||||
});
|
||||
if (!user) throw new NotFoundException('User not found');
|
||||
|
||||
if (data.username !== undefined) {
|
||||
const nextUsername = data.username.trim();
|
||||
if (!nextUsername) throw new BadRequestException('账号名称不能为空');
|
||||
const settings = await this.systemConfig.getPlayerAccountSettings();
|
||||
if (!settings.allowUsernameChange) {
|
||||
throw new ForbiddenException('当前平台未开放玩家自行修改账号名称');
|
||||
}
|
||||
if (nextUsername !== user.username) {
|
||||
const taken = await this.prisma.user.findUnique({ where: { username: nextUsername } });
|
||||
if (taken) throw new BadRequestException('账号名称已被占用');
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { username: nextUsername },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const phone = data.phone !== undefined ? data.phone.trim() || null : undefined;
|
||||
const email = data.email !== undefined ? data.email.trim() || null : undefined;
|
||||
let avatarKey: string | null | undefined;
|
||||
if (data.avatarKey !== undefined) {
|
||||
avatarKey = data.avatarKey?.trim() || null;
|
||||
if (avatarKey && !isValidAvatarKey(avatarKey)) {
|
||||
throw new BadRequestException('无效头像');
|
||||
}
|
||||
}
|
||||
|
||||
const existing = await this.prisma.userPreference.findUnique({ where: { userId } });
|
||||
if (!existing && phone === undefined && email === undefined && avatarKey === undefined) {
|
||||
return this.findById(userId);
|
||||
}
|
||||
|
||||
await this.prisma.userPreference.upsert({
|
||||
where: { userId },
|
||||
create: { userId, phone, email },
|
||||
update: { phone, email },
|
||||
create: {
|
||||
userId,
|
||||
phone: phone ?? null,
|
||||
email: email ?? null,
|
||||
...(avatarKey !== undefined ? { avatarKey } : {}),
|
||||
},
|
||||
update: {
|
||||
...(phone !== undefined ? { phone } : {}),
|
||||
...(email !== undefined ? { email } : {}),
|
||||
...(avatarKey !== undefined ? { avatarKey } : {}),
|
||||
},
|
||||
});
|
||||
return this.findById(userId);
|
||||
}
|
||||
@@ -195,10 +251,13 @@ export class UsersService {
|
||||
phone?: string;
|
||||
email?: string;
|
||||
parentId?: string | null;
|
||||
username?: string;
|
||||
password?: string;
|
||||
},
|
||||
) {
|
||||
const user = await this.prisma.user.findFirst({
|
||||
where: { id: playerId, userType: 'PLAYER', deletedAt: null },
|
||||
include: { auth: true },
|
||||
});
|
||||
if (!user) throw new NotFoundException('玩家不存在');
|
||||
|
||||
@@ -206,6 +265,35 @@ export class UsersService {
|
||||
throw new BadRequestException('无效状态');
|
||||
}
|
||||
|
||||
if (data.username !== undefined) {
|
||||
const nextUsername = data.username.trim();
|
||||
if (!nextUsername) throw new BadRequestException('账号名称不能为空');
|
||||
if (nextUsername !== user.username) {
|
||||
const taken = await this.prisma.user.findUnique({ where: { username: nextUsername } });
|
||||
if (taken) throw new BadRequestException('账号名称已被占用');
|
||||
await this.prisma.user.update({
|
||||
where: { id: playerId },
|
||||
data: { username: nextUsername },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (data.password !== undefined) {
|
||||
const nextPassword = data.password;
|
||||
if (nextPassword.length < 8) throw new BadRequestException('密码至少 8 位');
|
||||
if (!user.auth) throw new BadRequestException('账号认证信息缺失');
|
||||
const hash = await bcrypt.hash(nextPassword, 10);
|
||||
await this.prisma.userAuth.update({
|
||||
where: { userId: playerId },
|
||||
data: { passwordHash: hash, loginFailCount: 0, lockedUntil: null },
|
||||
});
|
||||
await this.prisma.userPreference.upsert({
|
||||
where: { userId: playerId },
|
||||
create: { userId: playerId, managedPassword: nextPassword },
|
||||
update: { managedPassword: nextPassword },
|
||||
});
|
||||
}
|
||||
|
||||
if (data.status) {
|
||||
await this.prisma.user.update({
|
||||
where: { id: playerId },
|
||||
@@ -253,25 +341,43 @@ export class UsersService {
|
||||
});
|
||||
}
|
||||
|
||||
if (data.phone !== undefined || data.email !== undefined || data.locale) {
|
||||
const phone = data.phone !== undefined ? data.phone?.trim() || null : undefined;
|
||||
const email = data.email !== undefined ? data.email?.trim() || null : undefined;
|
||||
const prefPatch: {
|
||||
locale?: string;
|
||||
phone?: string | null;
|
||||
email?: string | null;
|
||||
} = {};
|
||||
|
||||
if (data.locale) prefPatch.locale = data.locale;
|
||||
if (data.phone !== undefined) prefPatch.phone = data.phone.trim() || null;
|
||||
if (data.email !== undefined) prefPatch.email = data.email.trim() || null;
|
||||
|
||||
if (Object.keys(prefPatch).length > 0) {
|
||||
await this.prisma.userPreference.upsert({
|
||||
where: { userId: playerId },
|
||||
create: {
|
||||
userId: playerId,
|
||||
locale: data.locale ?? user.locale,
|
||||
phone: phone ?? null,
|
||||
email: email ?? null,
|
||||
},
|
||||
update: {
|
||||
...(data.locale ? { locale: data.locale } : {}),
|
||||
...(phone !== undefined ? { phone } : {}),
|
||||
...(email !== undefined ? { email } : {}),
|
||||
phone: prefPatch.phone ?? null,
|
||||
email: prefPatch.email ?? null,
|
||||
},
|
||||
update: prefPatch,
|
||||
});
|
||||
}
|
||||
|
||||
return this.getPlayerAdminDetail(playerId);
|
||||
}
|
||||
|
||||
async getPlayerAccountPermissions() {
|
||||
return this.systemConfig.getPlayerAccountSettings();
|
||||
}
|
||||
|
||||
async clearManagedPassword(userId: bigint) {
|
||||
const pref = await this.prisma.userPreference.findUnique({ where: { userId } });
|
||||
if (pref?.managedPassword) {
|
||||
await this.prisma.userPreference.update({
|
||||
where: { userId },
|
||||
data: { managedPassword: null },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
9
apps/api/src/shared/config/system-config.module.ts
Normal file
9
apps/api/src/shared/config/system-config.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { SystemConfigService } from './system-config.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [SystemConfigService],
|
||||
exports: [SystemConfigService],
|
||||
})
|
||||
export class SystemConfigModule {}
|
||||
59
apps/api/src/shared/config/system-config.service.ts
Normal file
59
apps/api/src/shared/config/system-config.service.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
export const PLAYER_ALLOW_PASSWORD_CHANGE = 'player.allow_password_change';
|
||||
export const PLAYER_ALLOW_USERNAME_CHANGE = 'player.allow_username_change';
|
||||
|
||||
export type PlayerAccountSettings = {
|
||||
allowPasswordChange: boolean;
|
||||
allowUsernameChange: boolean;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class SystemConfigService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async getBoolean(key: string, defaultValue: boolean): Promise<boolean> {
|
||||
const row = await this.prisma.systemConfig.findUnique({ where: { configKey: key } });
|
||||
if (!row) return defaultValue;
|
||||
return row.configValue === 'true' || row.configValue === '1';
|
||||
}
|
||||
|
||||
async setBoolean(key: string, value: boolean, description?: string) {
|
||||
await this.prisma.systemConfig.upsert({
|
||||
where: { configKey: key },
|
||||
create: {
|
||||
configKey: key,
|
||||
configValue: value ? 'true' : 'false',
|
||||
description,
|
||||
},
|
||||
update: { configValue: value ? 'true' : 'false' },
|
||||
});
|
||||
}
|
||||
|
||||
async getPlayerAccountSettings(): Promise<PlayerAccountSettings> {
|
||||
const [allowPasswordChange, allowUsernameChange] = await Promise.all([
|
||||
this.getBoolean(PLAYER_ALLOW_PASSWORD_CHANGE, true),
|
||||
this.getBoolean(PLAYER_ALLOW_USERNAME_CHANGE, false),
|
||||
]);
|
||||
return { allowPasswordChange, allowUsernameChange };
|
||||
}
|
||||
|
||||
async updatePlayerAccountSettings(data: Partial<PlayerAccountSettings>) {
|
||||
if (data.allowPasswordChange !== undefined) {
|
||||
await this.setBoolean(
|
||||
PLAYER_ALLOW_PASSWORD_CHANGE,
|
||||
data.allowPasswordChange,
|
||||
'玩家是否可在客户端修改密码',
|
||||
);
|
||||
}
|
||||
if (data.allowUsernameChange !== undefined) {
|
||||
await this.setBoolean(
|
||||
PLAYER_ALLOW_USERNAME_CHANGE,
|
||||
data.allowUsernameChange,
|
||||
'玩家是否可在客户端修改登录账号名',
|
||||
);
|
||||
}
|
||||
return this.getPlayerAccountSettings();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user