HarmonyOS 鸿蒙Next中如何实现优雅的表单验证?
HarmonyOS 鸿蒙Next中如何实现优雅的表单验证?
- 如何实现优雅的表单验证?
- 如何处理各类用户输入?
4 回复
代码实现
/**
* 表单验证工具类
*/
export class FormValidator {
/**
* 验证金额
*/
public static validateAmount(amount: string): { valid: boolean; message: string } {
if (!amount || amount.trim() === '') {
return { valid: false, message: '请输入金额' };
}
const num = parseFloat(amount);
if (isNaN(num)) {
return { valid: false, message: '请输入有效的数字' };
}
if (num <= 0) {
return { valid: false, message: '金额必须大于0' };
}
if (num > 999999) {
return { valid: false, message: '金额不能超过999999' };
}
// 检查小数位数
const decimalPart = amount.split('.')[1];
if (decimalPart && decimalPart.length > 2) {
return { valid: false, message: '最多支持2位小数' };
}
return { valid: true, message: '' };
}
/**
* 验证姓名
*/
public static validateName(name: string): { valid: boolean; message: string } {
if (!name || name.trim() === '') {
return { valid: false, message: '请输入姓名' };
}
if (name.trim().length < 2) {
return { valid: false, message: '姓名至少2个字符' };
}
if (name.trim().length > 20) {
return { valid: false, message: '姓名不能超过20个字符' };
}
// 只允许中文、英文、数字
const namePattern = /^[\u4e00-\u9fa5a-zA-Z0-9\s]+$/;
if (!namePattern.test(name.trim())) {
return { valid: false, message: '姓名只能包含中文、英文、数字' };
}
return { valid: true, message: '' };
}
/**
* 验证手机号
*/
public static validatePhone(phone: string): { valid: boolean; message: string } {
if (!phone || phone.trim() === '') {
return { valid: true, message: '' }; // 手机号可选
}
const phonePattern = /^1[3-9]\d{9}$/;
if (!phonePattern.test(phone.trim())) {
return { valid: false, message: '请输入正确的手机号' };
}
return { valid: true, message: '' };
}
/**
* 验证备注长度
*/
public static validateRemark(remark: string): { valid: boolean; message: string } {
if (remark && remark.length > 200) {
return { valid: false, message: '备注不能超过200字' };
}
return { valid: true, message: '' };
}
/**
* 格式化金额输入
*/
public static formatAmountInput(input: string): string {
// 只保留数字和小数点
let formatted = input.replace(/[^\d.]/g, '');
// 只保留第一个小数点
const parts = formatted.split('.');
if (parts.length > 2) {
formatted = parts[0] + '.' + parts.slice(1).join('');
}
// 限制小数位数为2位
if (parts.length === 2 && parts[1].length > 2) {
formatted = parts[0] + '.' + parts[1].substring(0, 2);
}
return formatted;
}
/**
* 格式化手机号输入
*/
public static formatPhoneInput(input: string): string {
// 只保留数字
let formatted = input.replace(/\D/g, '');
// 限制11位
if (formatted.length > 11) {
formatted = formatted.substring(0, 11);
}
return formatted;
}
}
/**
* 添加记录页面 - 完整表单验证示例
*/
@Entry
@Component
struct AddRecordPage {
// 表单字段
@State amount: string = '';
@State personName: string = '';
@State phone: string = '';
@State location: string = '';
@State remark: string = '';
// 验证错误信息
@State amountError: string = '';
@State nameError: string = '';
@State phoneError: string = '';
@State remarkError: string = '';
// 表单状态
@State formValid: boolean = false;
@State submitDisabled: boolean = true;
@State loading: boolean = false;
/**
* 金额输入变化
*/
private onAmountChange(value: string) {
// 格式化输入
const formatted = FormValidator.formatAmountInput(value);
this.amount = formatted;
// 实时验证
const result = FormValidator.validateAmount(formatted);
this.amountError = result.message;
// 更新表单状态
this.updateFormValidity();
}
/**
* 姓名输入变化
*/
private onNameChange(value: string) {
this.personName = value;
// 实时验证
const result = FormValidator.validateName(value);
this.nameError = result.message;
this.updateFormValidity();
}
/**
* 手机号输入变化
*/
private onPhoneChange(value: string) {
// 格式化输入
const formatted = FormValidator.formatPhoneInput(value);
this.phone = formatted;
// 实时验证
const result = FormValidator.validatePhone(formatted);
this.phoneError = result.message;
this.updateFormValidity();
}
/**
* 备注输入变化
*/
private onRemarkChange(value: string) {
this.remark = value;
// 实时验证
const result = FormValidator.validateRemark(value);
this.remarkError = result.message;
this.updateFormValidity();
}
/**
* 更新表单有效性
*/
private updateFormValidity() {
const amountValid = FormValidator.validateAmount(this.amount).valid;
const nameValid = FormValidator.validateName(this.personName).valid;
const phoneValid = FormValidator.validatePhone(this.phone).valid;
const remarkValid = FormValidator.validateRemark(this.remark).valid;
this.formValid = amountValid && nameValid && phoneValid && remarkValid;
this.submitDisabled = !this.formValid;
}
/**
* 提交表单
*/
private async submitForm() {
// 最终验证
if (!this.validateForm()) {
promptAction.showToast({
message: '请检查表单填写',
duration: 2000
});
return;
}
try {
this.loading = true;
// 保存数据逻辑
await this.saveRecord();
promptAction.showToast({
message: '保存成功',
duration: 2000
});
setTimeout(() => {
router.back();
}, 1000);
} catch (error) {
promptAction.showToast({
message: '保存失败',
duration: 2000
});
} finally {
this.loading = false;
}
}
/**
* 验证整个表单
*/
private validateForm(): boolean {
// 验证金额
const amountResult = FormValidator.validateAmount(this.amount);
if (!amountResult.valid) {
this.amountError = amountResult.message;
return false;
}
// 验证姓名
const nameResult = FormValidator.validateName(this.personName);
if (!nameResult.valid) {
this.nameError = nameResult.message;
return false;
}
// 验证手机号
const phoneResult = FormValidator.validatePhone(this.phone);
if (!phoneResult.valid) {
this.phoneError = phoneResult.message;
return false;
}
// 验证备注
const remarkResult = FormValidator.validateRemark(this.remark);
if (!remarkResult.valid) {
this.remarkError = remarkResult.message;
return false;
}
return true;
}
private async saveRecord() {
// 保存逻辑
}
build() {
Column() {
// 导航栏
this.buildHeader()
// 表单内容
Scroll() {
Column() {
// 金额输入
this.buildAmountInput()
// 姓名输入
this.buildNameInput()
// 手机号输入
this.buildPhoneInput()
// 地点输入
this.buildLocationInput()
// 备注输入
this.buildRemarkInput()
}
.padding(16)
}
.layoutWeight(1)
// 提交按钮
this.buildSubmitButton()
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
@Builder
buildHeader() {
Row() {
Image($r('app.media.ic_back'))
.width(24)
.height(24)
.onClick(() => router.back())
Text('添加记录')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ left: 16 })
}
.width('100%')
.height(60)
.padding({ left: 20, right: 20 })
.backgroundColor('#FA8C16')
}
/**
* 金额输入框
*/
@Builder
buildAmountInput() {
Column() {
Row() {
Text('金额')
.fontSize(16)
.fontColor('#262626')
Text('*')
.fontSize(16)
.fontColor('#FF4D4F')
.margin({ left: 4 })
}
.margin({ bottom: 8 })
TextInput({ text: this.amount, placeholder: '请输入金额' })
.type(InputType.Number)
.fontSize(16)
.placeholderColor('#BFBFBF')
.backgroundColor('#FFFFFF')
.borderRadius(8)
.padding({ left: 12, right: 12 })
.height(48)
.onChange((value: string) => {
this.onAmountChange(value);
})
.onSubmit(() => {
// 回车时验证
const result = FormValidator.validateAmount(this.amount);
this.amountError = result.message;
})
// 错误提示
if (this.amountError) {
Text(this.amountError)
.fontSize(12)
.fontColor('#FF4D4F')
.margin({ top: 4 })
}
// 快捷金额
this.buildQuickAmounts()
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.margin({ bottom: 16 })
}
/**
* 快捷金额选择
*/
@Builder
buildQuickAmounts() {
Row() {
ForEach([100, 200, 500, 1000], (amount: number) => {
Button(amount.toString())
.fontSize(14)
.fontColor('#595959')
.backgroundColor('#F5F5F5')
.borderRadius(16)
.padding({ left: 16, right: 16, top: 6, bottom: 6 })
.onClick(() => {
this.amount = amount.toString();
this.onAmountChange(this.amount);
})
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.margin({ top: 12 })
}
/**
* 姓名输入框
*/
@Builder
buildNameInput() {
Column() {
Row() {
Text('姓名')
.fontSize(16)
.fontColor('#262626')
Text('*')
.fontSize(16)
.fontColor('#FF4D4F')
.margin({ left: 4 })
}
.margin({ bottom: 8 })
TextInput({ text: this.personName, placeholder: '请输入姓名' })
.fontSize(16)
.placeholderColor('#BFBFBF')
.backgroundColor('#FFFFFF')
.borderRadius(8)
.padding({ left: 12, right: 12 })
.height(48)
.maxLength(20)
.onChange((value: string) => {
this.onNameChange(value);
})
if (this.nameError) {
Text(this.nameError)
.fontSize(12)
.fontColor('#FF4D4F')
.margin({ top: 4 })
}
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.margin({ bottom: 16 })
}
/**
* 手机号输入框
*/
@Builder
buildPhoneInput() {
Column() {
Text('手机号')
.fontSize(16)
.fontColor('#262626')
.margin({ bottom: 8 })
TextInput({ text: this.phone, placeholder: '请输入手机号(可选)' })
.type(InputType.PhoneNumber)
.fontSize(16)
.placeholderColor('#BFBFBF')
.backgroundColor('#FFFFFF')
.borderRadius(8)
.padding({ left: 12, right: 12 })
.height(48)
.maxLength(11)
.onChange((value: string) => {
this.onPhoneChange(value);
})
if (this.phoneError) {
Text(this.phoneError)
.fontSize(12)
.fontColor('#FF4D4F')
.margin({ top: 4 })
}
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.margin({ bottom: 16 })
}
/**
* 地点输入框
*/
@Builder
buildLocationInput() {
Column() {
Text('地点')
.fontSize(16)
.fontColor('#262626')
.margin({ bottom: 8 })
TextInput({ text: this.location, placeholder: '请输入地点(可选)' })
.fontSize(16)
.placeholderColor('#BFBFBF')
.backgroundColor('#FFFFFF')
.borderRadius(8)
.padding({ left: 12, right: 12 })
.height(48)
.onChange((value: string) => {
this.location = value;
})
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.margin({ bottom: 16 })
}
/**
* 备注输入框
*/
@Builder
buildRemarkInput() {
Column() {
Row() {
Text('备注')
.fontSize(16)
.fontColor('#262626')
Text(`${this.remark.length}/200`)
.fontSize(12)
.fontColor('#8C8C8C')
.margin({ left: 8 })
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.margin({ bottom: 8 })
TextArea({ text: this.remark, placeholder: '请输入备注(可选)' })
.fontSize(16)
.placeholderColor('#BFBFBF')
.backgroundColor('#FFFFFF')
.borderRadius(8)
.padding(12)
.height(100)
.maxLength(200)
.onChange((value: string) => {
this.onRemarkChange(value);
})
if (this.remarkError) {
Text(this.remarkError)
.fontSize(12)
.fontColor('#FF4D4F')
.margin({ top: 4 })
}
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.margin({ bottom: 16 })
}
/**
* 提交按钮
*/
@Builder
buildSubmitButton() {
Button(this.loading ? '保存中...' : '保存')
.width('90%')
.height(48)
.fontSize(16)
.fontColor('#FFFFFF')
.backgroundColor(this.submitDisabled ? '#D9D9D9' : '#FA8C16')
.borderRadius(24)
.margin({ bottom: 16 })
.enabled(!this.submitDisabled && !this.loading)
.onClick(() => {
this.submitForm();
})
}
}
核心技术点
1. TextInput类型
.type(InputType.Number) // 数字键盘
.type(InputType.PhoneNumber) // 电话键盘
.type(InputType.Email) // 邮箱键盘
2. 输入限制
.maxLength(20) // 最大长度
.onChange((value) => {}) // 实时监听
.onSubmit(() => {}) // 回车提交
3. 正则表达式验证
// 手机号
/^1[3-9]\d{9}$/
// 姓名(中英文数字)
/^[\u4e00-\u9fa5a-zA-Z0-9\s]+$/
// 金额(最多2位小数)
/^\d+(\.\d{1,2})?$/
最佳实践
1. 实时验证 + 提交验证
// 实时验证: 输入时提示
.onChange((value) => {
this.validate(value);
})
// 提交验证: 最终检查
submitForm() {
if (!this.validateForm()) {
return;
}
}
2. 输入格式化
// 自动格式化,提升用户体验
formatAmountInput(input: string): string {
return input.replace(/[^\d.]/g, '');
}
3. 错误提示
if (this.amountError) {
Text(this.amountError)
.fontSize(12)
.fontColor('#FF4D4F')
}
4. 按钮状态控制
.enabled(!this.submitDisabled && !this.loading)
.backgroundColor(this.submitDisabled ? '#D9D9D9' : '#FA8C16')
更多关于HarmonyOS 鸿蒙Next中如何实现优雅的表单验证?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS Next中,实现优雅的表单验证可以充分利用其声明式UI和状态管理能力,核心在于将验证逻辑与UI组件解耦,并实时反馈。
1. 核心思路:状态驱动与响应式
将每个表单字段的“值”和“错误信息”定义为状态变量(使用@State或@Link装饰器)。当值变化时,触发验证函数更新错误状态,UI自动刷新。
2. 实现方案示例:
- 定义状态:
@State username: string = '' @State usernameError: string = '' - 绑定与验证: 在文本输入框的
onChange事件中,同步值并调用验证器。
下方显示错误信息:TextInput({ placeholder: '请输入用户名' }) .value(this.username) .onChange((value: string) => { this.username = value this.validateUsername(value) // 验证函数 })if (this.usernameError) { Text(this.usernameError) .fontColor(Color.Red) } - 验证函数: 集中管理校验逻辑,返回错误提示。
validateUsername(value: string): void { if (!value) { this.usernameError = '用户名不能为空' } else if (value.length < 3) { this.usernameError = '用户名至少3位' } else { this.usernameError = '' // 验证通过,清空错误 } }
3. 处理各类输入:
- 文本/密码: 使用
TextInput,通过onChange或onEditChange捕获输入。 - 选择器(单选/多选): 使用
Radio、Checkbox或Select组件,绑定其选中状态到变量。 - 日期/时间: 使用
DatePicker、TimePicker,监听其onChange事件获取值。 - 表单提交: 在提交按钮的
onClick事件中,遍历执行所有字段的最终验证,只有所有error状态均为空时才提交数据。
4. 进阶优化:
- 封装验证器: 将正则校验、必填校验等抽离为纯函数工具类。
- 防抖验证: 对频繁触发的输入(如搜索框),使用
debounce函数避免过度验证。 - ArkUI组件组合: 可以将输入框、错误提示封装为一个自定义的可复用组件。
这种方式逻辑清晰,响应迅速,符合HarmonyOS Next的开发范式。


