news 2026/6/16 13:07:54

HarmonyOS PC实战之登录页Column 居中布局的细节

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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: CenteralignItems: Center,内层卡片就自然居中。

完整实现:PC端登录页

完整示例:PcLoginPage.ets

import{router}from'@kit.ArkUI'@Entry@Componentstruct PcLoginPage{@Stateusername:string=''@Statepassword:string=''@StateshowPassword:boolean=false@StateisLoading:boolean=false@StateerrorMsg:string=''@StaterememberMe:boolean=false@StateactiveTab:'password'|'code'='password'@StateverifyCode:string=''@StatecodeSent:boolean=false@Statecountdown:number=0getcanSubmit():boolean{if(this.activeTab==='password'){returnthis.username.trim().length>0&&this.password.length>=6}returnthis.username.trim().length>0&&this.verifyCode.length===6}handleLogin(){if(!this.canSubmit){this.errorMsg=this.activeTab==='password'?'请输入账号和密码(至少6位)':'请输入完整的验证码'return}this.isLoading=truethis.errorMsg=''// 模拟登录请求setTimeout(()=>{this.isLoading=falseif(this.username==='admin'&&this.password==='123456'){// 登录成功}else{this.errorMsg='账号或密码错误,请重试'}},1500)}sendCode(){if(!this.username.trim()){this.errorMsg='请先输入手机号或邮箱'return}this.codeSent=truethis.countdown=60consttimer=setInterval(()=>{if(this.countdown<=0){clearInterval(timer)this.codeSent=false}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(tab==='password'?'密码登录':'验证码登录').fontSize(14).fontColor(this.activeTab===tab?'#3B82F6':'#6B7280').fontWeight(this.activeTab===tab?FontWeight.Medium:FontWeight.Normal).layoutWeight(1).textAlign(TextAlign.Center).padding({top:12,bottom:12}).border({width:{bottom:2},color:this.activeTab===tab?'#3B82F6':Color.Transparent}).onClick(()=>{this.activeTab=tabas'password'|'code'this.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.username=vthis.errorMsg=''})}// 密码输入(仅密码登录时显示)if(this.activeTab==='password'){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.password=vthis.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.activeTab==='code'){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.verifyCode=v)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.rememberMe=v)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: Center+alignItems: Center就能做出卡片居中的效果。细节上:TextInput 要给width('100%'),Label 行用 Row+Blank 做三段式,按钮禁用状态要同时改颜色和.enabled(false)。这几点处理好,登录页基本上就完整了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/16 13:06:54

如何免费使用ACE-Step UI:终极开源AI音乐创作工具完整指南

如何免费使用ACE-Step UI&#xff1a;终极开源AI音乐创作工具完整指南 【免费下载链接】ace-step-ui &#x1f3b5; The Ultimate Open Source Suno Alternative - Professional UI for ACE-Step 1.5 AI Music Generation. Free, local, unlimited. Stop paying for Suno! 项…

作者头像 李华
网站建设 2026/6/16 13:05:52

睿治Agent实战评测:智能数据治理的边界与落地条件

1. 为什么我们决定亲自测睿治Agent——不是因为厂商宣传&#xff0c;而是因为数据治理平台的“交付幻觉”太普遍“数据治理平台上线了&#xff0c;指标口径统一了&#xff0c;元数据自动采集了&#xff0c;血缘关系也画出来了。”——这是我在过去三年里&#xff0c;听客户在验…

作者头像 李华
网站建设 2026/6/16 13:02:59

Robix本文档展示了Robix工业系统的核心底层功能模块关闭/卸载代码片段(601-622段),主要特点包括: 系统保护机制全面解除 中断优先级、堆栈保护、事务回滚等安全机制被移除 各类补偿算

Robix工业绝密底层裸数据 601-800段 完整版带源码 本文档展示了Robix工业系统的核心底层功能模块关闭/卸载代码片段&#xff08;601-622段&#xff09;&#xff0c;主要特点包括&#xff1a; 系统保护机制全面解除 中断优先级、堆栈保护、事务回滚等安全机制被移除 各类补偿算法…

作者头像 李华
网站建设 2026/6/16 12:59:58

Tinymind架构解析:探索GitHub驱动的博客系统核心代码实现

Tinymind架构解析&#xff1a;探索GitHub驱动的博客系统核心代码实现 【免费下载链接】tinymind Tinymind - Write and sync your blog & thoughts with GitHub 项目地址: https://gitcode.com/gh_mirrors/ti/tinymind Tinymind 是一款创新的GitHub驱动博客系统&…

作者头像 李华
网站建设 2026/6/16 12:58:09

nixified.ai:终极AI项目Nix打包解决方案 - 一键运行70+AI工具

nixified.ai&#xff1a;终极AI项目Nix打包解决方案 - 一键运行70AI工具 【免费下载链接】flake A Nix flake for many AI projects 项目地址: https://gitcode.com/gh_mirrors/fl/flake nixified.ai 是一个革命性的开源项目&#xff0c;它通过 Nix 打包技术为 AI 开发者…

作者头像 李华