一、项目概述
本项目是一款基于鸿蒙 ArkTS(ETS)开发的用户登录页面,集成了图文验证码功能,旨在为应用提供安全、便捷的用户身份验证入口。项目采用现代化 UI 设计,兼顾用户体验与安全性,适用于多种需要用户登录的场景。
二、功能特点
- 图文验证码防护
内置自定义验证码组件,支持随机字符、颜色、旋转、干扰线和干扰点,极大提升了防机器自动化攻击的能力。 - 动态刷新机制
用户可点击验证码区域一键刷新,验证码内容与样式实时变化,提升安全性和交互体验。 - 表单校验与提示
登录表单支持用户名、密码、验证码的完整性校验,输入不完整或验证码错误时,自动弹窗友好提示。 - 简洁美观的界面
采用现代化配色和布局,界面简洁、操作直观,适配多种终端分辨率。 - 易于集成与扩展
验证码组件高度解耦,可灵活集成到其他页面或项目中,支持自定义扩展。
三、技术架构
- 开发语言:ArkTS(ETS)
- UI 框架:HarmonyOS ArkUI
- 组件化设计:采用@Entry、@Component 等装饰器实现页面与组件的解耦
- 状态管理:利用@State、@Prop 等特性实现数据驱动视图
- 随机算法:内置字符、颜色、旋转、干扰线点等多重随机生成算法
- 事件响应:支持 onClick、onChange 等事件,提升交互体验
关键技术实现
- 验证码组件
VerifyCodeCanvas
通过多组随机数生成干扰线、干扰点和字符样式,极大增强验证码的安全性和不可预测性。 - 登录逻辑中对用户输入进行严格校验,确保信息完整且验证码正确后才允许登录。
四、使用场景
- 企业/组织内部系统登录
适用于 OA、ERP、CRM 等需要身份验证的企业级应用,提升系统安全性。 - 移动 App 用户注册/登录
可集成到各类移动应用的注册、登录、找回密码等流程中,防止恶意注册和暴力破解。 - Web 管理后台
适合各类管理后台、控制台等需要登录验证的 Web 端项目。 - IoT 设备管理
用于智能设备的远程管理登录,保障设备安全接入。
完整源码
// 定义点位置的接口
interface PointPosition {
x: number;
y: number;
}
// 验证码组件
@Component
struct VerifyCodeCanvas {
@Prop code: string
@Prop refreshFlag: number
// 用于存储随机生成的数据
@State private linePoints1: number[][] = []
@State private linePoints2: number[][] = []
@State private dotPositions: PointPosition[] = []
@State private charColors: string[] = []
@State private charRotations: number[] = []
aboutToAppear() {
this.generateRandomData()
}
// 生成所有随机数据
private generateRandomData() {
// 生成干扰线点
this.linePoints1 = [
[Math.random() * 100, Math.random() * 40],
[Math.random() * 100, Math.random() * 40],
[Math.random() * 100, Math.random() * 40],
[Math.random() * 100, Math.random() * 40]
]
this.linePoints2 = [
[Math.random() * 100, Math.random() * 40],
[Math.random() * 100, Math.random() * 40],
[Math.random() * 100, Math.random() * 40]
]
// 生成干扰点位置
this.dotPositions = [
{ x: Math.random() * 100, y: Math.random() * 40 },
{ x: Math.random() * 100, y: Math.random() * 40 },
{ x: Math.random() * 100, y: Math.random() * 40 },
{ x: Math.random() * 100, y: Math.random() * 40 }
]
// 生成字符颜色和旋转角度
this.charColors = [
this.getRandomColor(),
this.getRandomColor(),
this.getRandomColor(),
this.getRandomColor()
]
this.charRotations = [
this.getRandomRotation(),
this.getRandomRotation(),
this.getRandomRotation(),
this.getRandomRotation()
]
}
// 生成随机颜色
private getRandomColor(): string {
const colors = ['#0D5BC3', '#2979FF', '#1565C0', '#0277BD']
return colors[Math.floor(Math.random() * colors.length)]
}
// 生成随机旋转角度
private getRandomRotation(): number {
return (Math.random() - 0.5) * 30
}
// 生成随机位置偏移
private getRandomOffset(): number {
return (Math.random() - 0.5) * 6
}
// 监听refreshFlag变化
onPropertyChange(propName: string, newValue: number): void {
if (propName === 'refreshFlag') {
this.generateRandomData()
}
}
build() {
Stack({ alignContent: Alignment.Center }) {
// 背景
Rect()
.width('100%')
.height('100%')
.fill('#E8F0FE')
// 使用Polyline代替多条Line
Polyline()
.width('100%')
.height('100%')
.points(this.linePoints1)
.stroke(this.getRandomColor())
.strokeWidth(1)
.fillOpacity(0)
// 再添加一条Polyline增加干扰
Polyline()
.width('100%')
.height('100%')
.points(this.linePoints2)
.stroke(this.getRandomColor())
.strokeWidth(1)
.fillOpacity(0)
// 添加几个固定的干扰点
Circle()
.width(2)
.height(2)
.position({ x: this.dotPositions[0].x, y: this.dotPositions[0].y })
.fill(this.getRandomColor())
Circle()
.width(2)
.height(2)
.position({ x: this.dotPositions[1].x, y: this.dotPositions[1].y })
.fill(this.getRandomColor())
Circle()
.width(2)
.height(2)
.position({ x: this.dotPositions[2].x, y: this.dotPositions[2].y })
.fill(this.getRandomColor())
Circle()
.width(2)
.height(2)
.position({ x: this.dotPositions[3].x, y: this.dotPositions[3].y })
.fill(this.getRandomColor())
// 验证码文字
Flex({ justifyContent: FlexAlign.SpaceEvenly, alignItems: ItemAlign.Center }) {
Text(this.code[0])
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor(this.charColors[0])
.rotate({ z: 1, angle: this.charRotations[0] })
Text(this.code[1])
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor(this.charColors[1])
.rotate({ z: 1, angle: this.charRotations[1] })
Text(this.code[2])
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor(this.charColors[2])
.rotate({ z: 1, angle: this.charRotations[2] })
Text(this.code[3])
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor(this.charColors[3])
.rotate({ z: 1, angle: this.charRotations[3] })
}
.width('100%')
.height('100%')
}
.width(100)
.height(40)
.margin({ right: 10 })
}
}
@Entry
@Component
struct Index {
@State username: string = ''
@State password: string = ''
@State verifyCode: string = ''
@State inputCode: string = ''
@State refreshFlag: number = 0 // 用于触发验证码刷新
// 生成随机验证码
private generateVerifyCode(): void {
const characters = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789'
let code = ''
for (let i = 0; i < 4; i++) {
code += characters.charAt(Math.floor(Math.random() * characters.length))
}
this.verifyCode = code
this.refreshFlag++ // 增加刷新标志,触发重绘
}
aboutToAppear() {
this.generateVerifyCode()
}
build() {
Column() {
// 标题
Text('用户登录')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.margin({ top: 80, bottom: 50 })
.fontColor('#0D5BC3')
// 用户名输入框
TextInput({ placeholder: '请输入用户名' })
.width('80%')
.height(50)
.margin({ bottom: 20 })
.backgroundColor('#F5F5F5')
.onChange((value: string) => {
this.username = value
})
// 密码输入框
TextInput({ placeholder: '请输入密码' })
.width('80%')
.height(50)
.margin({ bottom: 20 })
.backgroundColor('#F5F5F5')
.type(InputType.Password)
.onChange((value: string) => {
this.password = value
})
// 验证码区域
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
TextInput({ placeholder: '请输入验证码' })
.width('60%')
.height(50)
.backgroundColor('#F5F5F5')
.onChange((value: string) => {
this.inputCode = value
})
Flex({ justifyContent: FlexAlign.Center }) {
// 图文验证码组件
VerifyCodeCanvas({
code: this.verifyCode,
refreshFlag: this.refreshFlag
})
.onClick(() => {
this.generateVerifyCode()
})
}
.width('35%')
.height(50)
.backgroundColor('#E8F0FE')
}
.width('80%')
.margin({ bottom: 40 })
// 登录按钮
Button('登录')
.width('80%')
.height(50)
.backgroundColor('#0D5BC3')
.fontColor(Color.White)
.onClick(() => {
if (!this.username || !this.password || !this.inputCode) {
AlertDialog.show({
title: '提示',
message: '请填写完整信息',
confirm: {
value: '确定',
action: () => {
console.info('用户确认')
}
}
})
return
}
if (this.inputCode.toLowerCase() !== this.verifyCode.toLowerCase()) {
AlertDialog.show({
title: '提示',
message: '验证码错误',
confirm: {
value: '确定',
action: () => {
this.generateVerifyCode()
}
}
})
return
}
// TODO: 实现登录逻辑
console.info('登录信息:', this.username, this.password)
})
}
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')
}
}
原文链接:https://code.ifrontend.net/archives/655,转载请注明出处。
评论0