9.1 KiB
9.1 KiB
登录模块 (App/Login)
📋 模块概述
用户登录功能模块,包括登录界面 UI 和登录业务逻辑,登录成功后切换到游戏状态。
🎯 核心特性
- ✅ 登录界面 UI
- ✅ 账号输入处理
- ✅ 登录按钮交互
- ✅ 网络登录请求
- ✅ 状态切换
- ✅ 错误处理
🗂️ 文件结构
App/Login/
└── UILogin.ts # 登录界面组件
📘 核心类详解
UILogin - 登录界面
职责: 显示登录 UI,处理用户输入,发送登录请求
@ccclass('UILogin')
class UILogin extends UIBase {
private _inputAccount: EditBox | null; // 账号输入框
private _btnLogin: Node | null; // 登录按钮
private _isLogging: boolean; // 是否正在登录中
// 获取 UI 资源路径
onGetUrl(): string {
return 'res/UI/Login/UILogin';
}
// UI 初始化
async onStart(params?: any): Promise<void>;
// 查找 UI 节点
private findNodes(): void;
// 绑定事件
private bindEvents(): void;
// 解绑事件
private unbindEvents(): void;
// 登录按钮点击
private async onLoginClick(): Promise<void>;
// 执行登录
private async login(account: string): Promise<void>;
// UI 清理
onEnd(): void;
}
🎨 UI 结构要求
资源路径
assets/res/UI/Login/UILogin.prefab
节点结构
UILogin (根节点)
├── mid/
│ └── input_account (EditBox) # 账号输入框
└── btn_login # 登录按钮
组件要求
| 节点路径 | 必需组件 | 说明 |
|---|---|---|
mid/input_account |
EditBox | 账号输入框 |
btn_login |
- | 登录按钮(监听点击事件) |
📝 使用指南
1. 创建 UI 预制体
- 在 Cocos Creator 中创建 UI 预制体
- 路径:
assets/res/UI/Login/UILogin.prefab - 添加必需节点和组件
- 保存预制体
2. 在登录状态中使用
import { UIMgr } from '../../Framework/UI/UIMgr';
import { UILogin } from '../Login/UILogin';
// 在 AppStatusLogin 中加载登录 UI
export class AppStatusLogin extends BaseState {
async onEnter(params?: any): Promise<void> {
// 加载并显示登录界面
await UIMgr.getInstance().load(UILogin);
}
onExit(): void {
// 隐藏登录界面
UIMgr.getInstance().hide(UILogin);
}
}
3. 登录流程
用户打开应用
↓
AppStatusLogin.onEnter()
↓
加载并显示 UILogin
↓
用户输入账号
↓
点击登录按钮
↓
onLoginClick()
↓
login(account)
↓
调用 NetManager.callApi('Login', ...)
↓
收到服务器响应
↓
登录成功?
├─ 是: 切换到 Game 状态
└─ 否: 显示错误提示
📡 网络协议
登录请求 (ReqLogin)
interface ReqLogin {
account: string; // 账号
password?: string; // 密码(可选)
}
登录响应 (ResLogin)
interface ResLogin {
success: boolean; // 是否成功
message?: string; // 消息
player?: { // 玩家信息
id: string;
name: string;
position: { x: number; y: number; z: number };
spawnPoint: { x: number; y: number; z: number };
hp: number;
maxHp: number;
isAlive: boolean;
createdAt: number;
lastLoginAt: number;
};
isNewPlayer?: boolean; // 是否新玩家
}
使用示例
import { NetManager } from '../../Framework/Net/NetManager';
import { ReqLogin, ResLogin } from '../../Framework/Net/LoginProtocol';
// 发送登录请求
const result = await NetManager.getInstance().callApi<ReqLogin, ResLogin>('Login', {
account: 'player123'
});
if (result && result.success) {
console.log('登录成功:', result.player);
// 切换到游戏状态
AppStatusManager.getInstance().changeState('Game', {
player: result.player,
isNewPlayer: result.isNewPlayer
});
}
🔄 完整登录流程
// UILogin.ts 中的登录流程
// 1. 用户点击登录按钮
private async onLoginClick(): Promise<void> {
// 防止重复点击
if (this._isLogging) {
return;
}
// 获取输入的账号
const account = this._inputAccount?.string?.trim();
if (!account) {
console.warn('[UILogin] 请输入账号');
return;
}
// 标记登录中
this._isLogging = true;
try {
// 执行登录
await this.login(account);
} catch (error) {
console.error('[UILogin] 登录失败:', error);
} finally {
this._isLogging = false;
}
}
// 2. 执行登录逻辑
private async login(account: string): Promise<void> {
// 调用登录 API
const result = await NetManager.getInstance()
.callApi<ReqLogin, ResLogin>('Login', { account });
if (result && result.success) {
// 登录成功,切换状态
AppStatusManager.getInstance().changeState('Game', {
player: result.player,
isNewPlayer: result.isNewPlayer
});
// 隐藏登录界面
this.hide();
} else {
// 登录失败,显示错误
console.error('[UILogin] 登录失败:', result?.message);
}
}
⚠️ 注意事项
- UI 资源准备: 必须在
assets/res/UI/Login/创建 UILogin 预制体 - 节点结构: 预制体必须包含
mid/input_account和btn_login节点 - EditBox 组件:
input_account节点必须挂载 EditBox 组件 - 协议同步: 运行
npm run sync-shared同步服务端协议后,替换临时协议定义 - 模块归属: 业务 UI 必须放在
App/对应的业务模块下 - 防重复点击: 登录过程中禁用按钮,防止重复请求
🔍 调试技巧
日志输出
// UILogin 包含详细日志
// [UILogin] 登录界面初始化
// [UILogin] 已绑定登录按钮事件
// [UILogin] 开始登录,账号: xxx
// [UILogin] 登录成功
// [UILogin] 登录界面清理
检查节点
// 在 onStart 中检查节点是否找到
private findNodes(): void {
const inputNode = this._node.getChildByPath('mid/input_account');
console.log('input_account 节点:', inputNode ? '找到' : '未找到');
const btnNode = this._node.getChildByPath('btn_login');
console.log('btn_login 节点:', btnNode ? '找到' : '未找到');
}
常见问题
问题1: 节点找不到
// 检查预制体中的节点路径是否正确
// 确保节点名称完全匹配(区分大小写)
问题2: EditBox 组件为 null
// 确保 input_account 节点挂载了 EditBox 组件
const editBox = inputNode.getComponent(EditBox);
if (!editBox) {
console.error('未挂载 EditBox 组件');
}
问题3: 登录请求无响应
// 检查网络是否已连接
// 检查服务器地址是否正确
// 检查协议是否已同步
💡 最佳实践
- 输入验证: 登录前验证账号格式
- 加载状态: 显示加载动画或禁用按钮
- 错误提示: 友好的错误提示 UI
- 记住账号: 可选的记住账号功能
- 自动登录: 可选的自动登录功能
- 超时处理: 设置合理的请求超时时间
🎯 扩展功能
添加密码输入
// 1. 在 UI 中添加密码输入框
private _inputPassword: EditBox | null = null;
// 2. 在 findNodes 中查找
this._inputPassword = this._node.getChildByPath('mid/input_password')
?.getComponent(EditBox) || null;
// 3. 登录时传递密码
const result = await NetManager.getInstance()
.callApi<ReqLogin, ResLogin>('Login', {
account: account,
password: password
});
添加记住账号功能
import { sys } from 'cc';
// 保存账号
private saveAccount(account: string): void {
sys.localStorage.setItem('last_account', account);
}
// 读取账号
private loadAccount(): string {
return sys.localStorage.getItem('last_account') || '';
}
// 在 onStart 中自动填充
async onStart(): Promise<void> {
// ... 其他初始化
// 自动填充上次的账号
const lastAccount = this.loadAccount();
if (this._inputAccount && lastAccount) {
this._inputAccount.string = lastAccount;
}
}
添加加载动画
private _loadingNode: Node | null = null;
private showLoading(): void {
if (this._loadingNode) {
this._loadingNode.active = true;
}
// 禁用登录按钮
if (this._btnLogin) {
this._btnLogin.getComponent(Button)!.interactable = false;
}
}
private hideLoading(): void {
if (this._loadingNode) {
this._loadingNode.active = false;
}
// 启用登录按钮
if (this._btnLogin) {
this._btnLogin.getComponent(Button)!.interactable = true;
}
}
📚 相关文档
- Framework/UI README - UI 系统
- Framework/Net README - 网络通信
- App/AppStatus README - 应用状态机