鸿蒙原生开发手记:徒步迹 - 验证码登录与忘记密码
·

鸿蒙原生开发手记:徒步迹 - 验证码登录与忘记密码
实现短信验证码登录和密码重置功能
一、前言
除了密码登录,短信验证码登录和忘记密码是移动 App 的标配功能。本文在已实现的登录页基础上,增加验证码登录和密码重置的能力。
二、验证码登录
2.1 页面实现
@Entry
@Component
struct CodeLoginPage {
@State private phone: string = '';
@State private code: string = '';
@State private countdown: number = 0;
@State private isLoading: boolean = false;
@State private phoneError: string = '';
@State private codeError: string = '';
build() {
Column() {
// 标题
Text('验证码登录')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.width('100%');
Text('输入手机号获取验证码')
.fontSize(14)
.fontColor('#666')
.width('100%')
.margin({ top: 8 });
// 手机号输入
TextInput({ placeholder: '请输入手机号', text: this.phone })
.width('100%').height(50)
.backgroundColor('#F5F5F5').borderRadius(12).padding({ left: 16 })
.type(InputType.PhoneNumber).maxLength(11)
.margin({ top: 30 })
.onChange((v) => { this.phone = v; this.phoneError = ''; });
// 手机号校验提示
if (this.phoneError) {
Text(this.phoneError).fontSize(12).fontColor('#F44336').width('100%').margin({ top: 4 });
}
// 验证码输入
Row() {
TextInput({ placeholder: '请输入验证码', text: this.code })
.layoutWeight(1)
.backgroundColor(Color.Transparent)
.type(InputType.Number).maxLength(6)
.onChange((v) => { this.code = v; this.codeError = ''; });
Button(this.countdown > 0 ? `${this.countdown}s后重发` : '获取验证码')
.backgroundColor(this.countdown > 0 ? '#999' : '#4CAF50')
.borderRadius(8).height(40).fontSize(14).fontColor(Color.White)
.enabled(this.countdown <= 0 && this.phone.length === 11)
.onClick(() => {
if (this.countdown <= 0) this.sendCode();
});
}
.width('100%').height(50)
.backgroundColor('#F5F5F5').borderRadius(12).padding({ left: 16 })
.margin({ top: 16 });
// 验证码提示
if (this.codeError) {
Text(this.codeError).fontSize(12).fontColor('#F44336').width('100%').margin({ top: 4 });
}
// 登录按钮
Button() {
if (this.isLoading) {
LoadingProgress().width(24).height(24).color(Color.White);
} else {
Text('登录').fontSize(16).fontColor(Color.White);
}
}
.width('100%').height(50)
.backgroundColor(this.code.length === 6 ? '#4CAF50' : '#CCC')
.borderRadius(25)
.margin({ top: 32 })
.enabled(this.code.length === 6 && !this.isLoading)
.onClick(() => this.handleCodeLogin());
// 切换到密码登录
Text('使用密码登录 >')
.fontSize(14)
.fontColor('#4CAF50')
.margin({ top: 20 })
.onClick(() => { router.back(); });
}
.width('100%').height('100%')
.padding({ left: 24, right: 24 })
.backgroundColor(Color.White);
}
sendCode(): void {
// 模拟发送验证码
this.countdown = 60;
const interval = setInterval(() => {
this.countdown--;
if (this.countdown <= 0) clearInterval(interval);
}, 1000);
console.log(`验证码已发送到 ${this.phone}`);
}
handleCodeLogin(): void {
this.isLoading = true;
setTimeout(() => {
this.isLoading = false;
AppStorage.setOrCreate('isLogged', true);
router.replaceUrl({ url: 'pages/HomePage' });
}, 1000);
}
}
2.2 验证码后端接口(参考)
// 发送验证码
async function sendSmsCode(phone: string): Promise<boolean> {
const response = await apiService.request('/api/auth/send-code', 'POST', {
phone: phone,
type: 'login', // login / reset
});
return response.code === 200;
}
// 校验验证码
async function verifyCode(phone: string, code: string): Promise<string> {
const response = await apiService.request('/api/auth/verify-code', 'POST', {
phone,
code,
});
if (response.code === 200) {
return response.data.token;
}
throw new Error('验证码错误');
}
三、忘记/重置密码
3.1 流程设计
输入手机号 → 发送验证码 → 验证身份 → 设置新密码 → 完成
3.2 实现
@Entry
@Component
struct ResetPasswordPage {
@State private step: number = 1; // 1-验证身份 2-设置密码 3-完成
@State private phone: string = '';
@State private code: string = '';
@State private newPassword: string = '';
@State private confirmPassword: string = '';
@State private countdown: number = 0;
@State private isLoading: boolean = false;
build() {
Column() {
// 步骤指示器
Row() {
this.StepDot(1, '验证身份');
Divider().width(40).height(2).color(this.step >= 2 ? '#4CAF50' : '#E0E0E0');
this.StepDot(2, '设置密码');
Divider().width(40).height(2).color(this.step >= 3 ? '#4CAF50' : '#E0E0E0');
this.StepDot(3, '完成');
}
.width('100%')
.padding({ top: 30, bottom: 40 })
.justifyContent(FlexAlign.Center);
// 步骤内容
if (this.step === 1) {
this.StepVerifyIdentity();
} else if (this.step === 2) {
this.StepSetPassword();
} else {
this.StepComplete();
}
}
.width('100%').height('100%')
.padding({ left: 24, right: 24 })
.backgroundColor(Color.White);
}
@Builder
StepDot(step: number, label: string) {
Column() {
Text(`${step}`)
.width(28).height(28)
.fontSize(14)
.fontColor(Color.White)
.backgroundColor(this.step >= step ? '#4CAF50' : '#E0E0E0')
.borderRadius(14)
.textAlign(TextAlign.Center);
Text(label)
.fontSize(12)
.fontColor(this.step >= step ? '#4CAF50' : '#999')
.margin({ top: 4 });
}
.alignItems(HorizontalAlign.Center);
}
@Builder
StepVerifyIdentity() {
Column() {
Text('验证身份').fontSize(20).fontWeight(FontWeight.Bold).width('100%');
Text('输入注册时使用的手机号').fontSize(14).fontColor('#666').width('100%').margin({ top: 6 });
TextInput({ placeholder: '手机号', text: this.phone })
.width('100%').height(50).backgroundColor('#F5F5F5').borderRadius(12)
.padding({ left: 16 }).type(InputType.PhoneNumber)
.margin({ top: 24 });
Row() {
TextInput({ placeholder: '验证码', text: this.code })
.layoutWeight(1).backgroundColor(Color.Transparent)
.type(InputType.Number).maxLength(6);
Button(this.countdown > 0 ? `${this.countdown}s` : '获取验证码')
.backgroundColor(this.countdown > 0 ? '#999' : '#4CAF50')
.borderRadius(8).height(36).fontSize(13).fontColor(Color.White)
.onClick(() => { if (this.countdown <= 0) this.sendCode(); });
}
.width('100%').height(50).backgroundColor('#F5F5F5').borderRadius(12)
.padding({ left: 16 }).margin({ top: 12 });
Button('下一步').width('100%').height(50).backgroundColor('#4CAF50')
.borderRadius(25).margin({ top: 30 })
.enabled(this.phone.length === 11 && this.code.length === 6)
.onClick(() => { this.step = 2; });
}
.width('100%');
}
@Builder
StepSetPassword() {
Column() {
Text('设置新密码').fontSize(20).fontWeight(FontWeight.Bold).width('100%');
TextInput({ placeholder: '新密码(至少6位)', text: this.newPassword })
.width('100%').height(50).backgroundColor('#F5F5F5').borderRadius(12)
.padding({ left: 16 }).type(InputType.Password).margin({ top: 24 });
TextInput({ placeholder: '确认新密码', text: this.confirmPassword })
.width('100%').height(50).backgroundColor('#F5F5F5').borderRadius(12)
.padding({ left: 16 }).type(InputType.Password).margin({ top: 12 });
Button('重置密码').width('100%').height(50).backgroundColor('#4CAF50')
.borderRadius(25).margin({ top: 30 })
.enabled(this.newPassword.length >= 6 && this.newPassword === this.confirmPassword)
.onClick(() => { this.step = 3; });
}
.width('100%');
}
@Builder
StepComplete() {
Column() {
Text('✅').fontSize(64);
Text('密码重置成功').fontSize(20).fontWeight(FontWeight.Bold).margin({ top: 16 });
Text('请使用新密码登录').fontSize(14).fontColor('#666').margin({ top: 8 });
Button('返回登录').width('100%').height(50).backgroundColor('#4CAF50')
.borderRadius(25).margin({ top: 40 })
.onClick(() => { router.back(); });
}
.width('100%')
.margin({ top: 60 })
.alignItems(HorizontalAlign.Center);
}
sendCode(): void {
this.countdown = 60;
const interval = setInterval(() => {
this.countdown--;
if (this.countdown <= 0) clearInterval(interval);
}, 1000);
}
}
四、安全最佳实践
- 验证码有效期:通常 5 分钟
- 限制发送频率:同一手机号 60 秒内不可重复发送
- 加密传输:密码使用 HTTPS + 加密传输
- 前端脱敏:手机号中间 4 位隐藏
138****0000
五、总结
本文实现了验证码登录和密码重置两大功能,采用多步骤流程设计,用户体验清晰流畅。这些功能与密码登录共同组成了“徒步迹“完整的用户认证体系。
下一篇文章我们将回到首页,实现完整的首页布局。
下一篇预告:鸿蒙原生开发手记:徒步迹 - 首页布局设计与Scroll滚动
更多推荐


所有评论(0)