HarmonyOS PC实战之登录页Column 居中布局的细节
文章目录前言页面基本结构完整实现PC端登录页TextInput 要显式设 width(100%)忘记密码的 alignSelf写在最后前言登录页是 Column 居中布局最典型的场景Logo、标题、输入框、按钮从上往下堆叠整体居中显示。但真做起来会发现不少细节问题为什么 TextInput 加了width(100%)之后反而更对忘记密码为什么要单独alignSelf(End)按钮组的左右分布怎么做这篇文章拿 PC 端登录页做案例把这些细节一一解决。页面基本结构PC 端登录页通常不是全屏表单而是一个白色卡片居中显示在深色或浅灰背景上。外层是全屏 Column 垂直居中内层是卡片 Column 包裹表单元素。// 外层全屏垂直居中Column(){// 内层表单卡片Column({space:20}){// Logo、标题、输入框、按钮...}.width(400).padding(40).backgroundColor(Color.White).borderRadius(16).shadow({radius:20,color:rgba(0,0,0,0.08),offsetY:4})}.width(100%).height(100%).justifyContent(FlexAlign.Center)// 垂直居中.alignItems(HorizontalAlign.Center)// 水平居中外层 Column 同时设justifyContent: Center和alignItems: Center内层卡片就自然居中。完整实现PC端登录页完整示例PcLoginPage.etsimport{router}fromkit.ArkUIEntryComponentstruct PcLoginPage{Stateusername:stringStatepassword:stringStateshowPassword:booleanfalseStateisLoading:booleanfalseStateerrorMsg:stringStaterememberMe:booleanfalseStateactiveTab:password|codepasswordStateverifyCode:stringStatecodeSent:booleanfalseStatecountdown:number0getcanSubmit():boolean{if(this.activeTabpassword){returnthis.username.trim().length0this.password.length6}returnthis.username.trim().length0this.verifyCode.length6}handleLogin(){if(!this.canSubmit){this.errorMsgthis.activeTabpassword?请输入账号和密码至少6位:请输入完整的验证码return}this.isLoadingtruethis.errorMsg// 模拟登录请求setTimeout((){this.isLoadingfalseif(this.usernameadminthis.password123456){// 登录成功}else{this.errorMsg账号或密码错误请重试}},1500)}sendCode(){if(!this.username.trim()){this.errorMsg请先输入手机号或邮箱return}this.codeSenttruethis.countdown60consttimersetInterval((){if(this.countdown0){clearInterval(timer)this.codeSentfalse}else{this.countdown-1}},1000)}build(){Column(){// 表单卡片Column({space:0}){// Logo 区Column({space:10}){Stack(){Circle({width:56,height:56}).fill(#1D4ED8)Text(H).fontSize(24).fontColor(Color.White).fontWeight(FontWeight.Bold)}Text(HarmonyOS 开发者平台).fontSize(18).fontColor(#111827).fontWeight(FontWeight.Bold)Text(登录您的开发者账号以继续).fontSize(13).fontColor(#6B7280)}.alignItems(HorizontalAlign.Center).padding({bottom:28}).width(100%).border({width:{bottom:1},color:#F3F4F6})Column({space:20}){// 登录方式 TabRow({space:0}){ForEach([password,code]asstring[],(tab:string){Text(tabpassword?密码登录:验证码登录).fontSize(14).fontColor(this.activeTabtab?#3B82F6:#6B7280).fontWeight(this.activeTabtab?FontWeight.Medium:FontWeight.Normal).layoutWeight(1).textAlign(TextAlign.Center).padding({top:12,bottom:12}).border({width:{bottom:2},color:this.activeTabtab?#3B82F6:Color.Transparent}).onClick((){this.activeTabtabaspassword|codethis.errorMsg})})}.width(100%).border({width:{bottom:1},color:#F3F4F6})// 账号输入Column({space:6}){Text(账号).fontSize(13).fontColor(#374151).fontWeight(FontWeight.Medium).alignSelf(ItemAlign.Start)TextInput({placeholder:手机号 / 邮箱 / 用户名,text:this.username}).width(100%).height(44).fontSize(14).backgroundColor(#F9FAFB).borderRadius(8).border({width:1,color:#E5E7EB}).padding({left:12,right:12}).onChange((v){this.usernamevthis.errorMsg})}// 密码输入仅密码登录时显示if(this.activeTabpassword){Column({space:6}){Row(){Text(密码).fontSize(13).fontColor(#374151).fontWeight(FontWeight.Medium)Blank()Text(忘记密码).fontSize(12).fontColor(#3B82F6).onClick((){})}.width(100%)Row(){TextInput({placeholder:请输入密码,text:this.password}).type(this.showPassword?InputType.Normal:InputType.Password).layoutWeight(1).height(42).fontSize(14).backgroundColor(Color.Transparent).padding({left:12}).onChange((v){this.passwordvthis.errorMsg})Text(this.showPassword?:).fontSize(16).padding({right:12}).onClick(()this.showPassword!this.showPassword)}.width(100%).height(44).backgroundColor(#F9FAFB).borderRadius(8).border({width:1,color:#E5E7EB})}}// 验证码输入仅验证码登录时显示if(this.activeTabcode){Column({space:6}){Text(验证码).fontSize(13).fontColor(#374151).fontWeight(FontWeight.Medium).alignSelf(ItemAlign.Start)Row({space:10}){TextInput({placeholder:请输入6位验证码,text:this.verifyCode}).layoutWeight(1).height(44).fontSize(14).backgroundColor(#F9FAFB).borderRadius(8).border({width:1,color:#E5E7EB}).padding({left:12}).onChange((v)this.verifyCodev)Button(this.codeSent?${this.countdown}s:发送验证码).fontSize(13).height(44).padding({left:16,right:16}).backgroundColor(this.codeSent?#F3F4F6:#3B82F6).fontColor(this.codeSent?#9CA3AF:Color.White).borderRadius(8).enabled(!this.codeSent).onClick(()this.sendCode())}}}// 记住我Row({space:8}){Toggle({type:ToggleType.Checkbox,isOn:this.rememberMe}).width(16).height(16).onChange((v)this.rememberMev)Text(记住我).fontSize(13).fontColor(#374151)}.alignSelf(ItemAlign.Start)// 错误提示if(this.errorMsg){Row({space:6}){Text(⚠️).fontSize(13)Text(this.errorMsg).fontSize(13).fontColor(#EF4444)}.width(100%).padding({left:10,right:10,top:8,bottom:8}).backgroundColor(#FEF2F2).borderRadius(6)}// 登录按钮Button(this.isLoading?登录中...:登录).width(100%).height(44).fontSize(15).fontWeight(FontWeight.Medium).backgroundColor(this.canSubmit?#3B82F6:#E5E7EB).fontColor(this.canSubmit?Color.White:#9CA3AF).borderRadius(8).enabled(!this.isLoading).onClick(()this.handleLogin())// 注册提示Row({space:4}){Text(还没有账号).fontSize(13).fontColor(#6B7280)Text(立即注册).fontSize(13).fontColor(#3B82F6).fontWeight(FontWeight.Medium)}.justifyContent(FlexAlign.Center).width(100%)}.padding({top:24})}.width(400).padding({left:40,right:40,top:40,bottom:40}).backgroundColor(Color.White).borderRadius(16).shadow({radius:24,color:rgba(0,0,0,0.08),offsetY:8})}.width(100%).height(100%).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).linearGradient({direction:GradientDirection.RightBottom,colors:[[#EFF6FF,0],[#F5F3FF,1]]})}}TextInput 要显式设 width(‘100%’)Column 默认alignItems: Center但 TextInput 不会因此自动变成父容器宽度的一半——它有自己的默认宽度通常是 300vp 左右。要让它填满父容器必须显式写width(100%)。这是很多新手踩的坑以为 Column 内子项会自动居中并填满但实际上子项的宽度是独立的只有对齐位置受alignItems控制。忘记密码的 alignSelf密码行的 Label 区用 Row 做三段式——左边密码文字右边忘记密码。这是最直接的方式Row(){Text(密码).fontSize(13).fontColor(#374151)Blank()// 撑开中间空间Text(忘记密码).fontSize(12).fontColor(#3B82F6)}.width(100%)Blank()把忘记密码推到右边不需要alignSelf。写在最后登录页没什么特别复杂的布局Column justifyContent: CenteralignItems: Center就能做出卡片居中的效果。细节上TextInput 要给width(100%)Label 行用 RowBlank 做三段式按钮禁用状态要同时改颜色和.enabled(false)。这几点处理好登录页基本上就完整了。