前言
之前的 3D 拆解分析是完全开放的——打开就能用,不需要任何身份验证。这在 demo 阶段没问题,但一旦要接入网络功能(发现页、AI 分析、历史记录),就需要知道"当前用户是谁"。
这次迭代做了两件事:
- 接入 AuthService,未登录用户强制跳转登录页
- 全面引入毛玻璃(frosted glass)视觉风格——半透明背景 + 全局渐变
登录守卫
AuthService 接入
import{isLoggedIn}from'../services/AuthService'新增AuthService的isLoggedIn方法——返回当前用户是否已登录。
aboutToAppear 的守卫
aboutToAppear():void{if(!isLoggedIn()){router.pushUrl({url:'pages/LoginPage'})return}this.loadDiscover()}App 启动时先检查登录状态:
- 未登录 → 跳转 LoginPage,
return不执行后续逻辑 - 已登录 → 正常加载发现页数据
onPageShow 的二次检查
onPageShow():void{if(!isLoggedIn()){router.pushUrl({url:'pages/LoginPage'})}}除了启动时检查,每次页面重新显示时也检查。这防止了一种情况:用户在 App 运行期间退出登录(比如在设置页点了退出),回到首页时应该被踢到登录页。
为什么用 router.pushUrl 而不是 router.replaceUrl?
router.pushUrl({url:'pages/LoginPage'})pushUrl会把 LoginPage 压入导航栈。用户登录成功后,可以router.back()回到首页。
如果用replaceUrl,LoginPage 会替换首页。用户登录后无法返回——因为首页已经不在导航栈里了。
登录状态对功能的影响
@StateloggedIn:boolean=false虽然定义了loggedIn状态变量,但当前代码没有用它做条件渲染。所有功能(发现页、AI 分析)在登录后都可以使用。
后续如果要做"未登录只能看 3 个发现项,登录后看全部",可以用loggedIn控制。
全局渐变背景
旧版:纯色背景
.width('100%').height('100%').backgroundColor(BG)// BG = '#E6E0F0'新版:全局渐变
.width('100%').height('100%').linearGradient({angle:160,colors:[['#F0EDF8',0],['#E8F0FE',0.5],['#FFF5EE',1]]})三色渐变:
- 起点(0%):
#F0EDF8—— 淡紫 - 中间(50%):
#E8F0FE—— 淡蓝 - 终点(100%):
#FFF5EE—— 淡橙
角度 160°——从左上到右下,不是正上方到正下方。
为什么是 160° 而不是 180°?
180° 是正上方到正下方,渐变会显得"太规整"。160° 稍微偏一点,让渐变方向和用户阅读方向(从左到右)更一致,视觉上更自然。
三种颜色的搭配逻辑:
紫色(#F0EDF8)→ 蓝色(#E8F0FE)→ 橙色(#FFF5EE),是色轮上的相邻色。从冷色到暖色的过渡,给用户一种"从理性到感性"的心理暗示——左边是数据(紫色=科技),右边是体验(橙色=温暖)。
毛玻璃视觉体系
核心概念
毛玻璃(frosted glass)的本质是:半透明白色背景 + 极淡边框 + 微弱阴影。
// 统一的毛玻璃样式.backgroundColor('rgba(255,255,255,0.65)')// 65% 不透明度.border({width:1,color:'rgba(138,93,250,0.08)'})// 极淡紫色边框.shadow({radius:18,color:'rgba(138,93,250,0.06)',offsetY:3})// 紫色阴影各组件的毛玻璃实现
统计卡片:
// 旧版.backgroundColor(CW)// 纯白// 新版.backgroundColor('rgba(255,255,255,0.70)')// 70% 白控制面板:
// 旧版.backgroundColor(CW)// 新版.backgroundColor('rgba(255,255,255,0.80)').border({width:1,color:'rgba(138,93,250,0.06)'}).shadow({radius:10,color:'rgba(138,93,250,0.04)',offsetY:2})功能按钮:
// 旧版.backgroundColor(this.list?AL:'#F5F5F7')// 新版(FeatureBtn).backgroundColor(active?bgColor:'#F5F5F7')部件清单:
// 旧版.backgroundColor(CW)// 新版.backgroundColor('rgba(255,255,255,0.60)').border({width:0.5,color:'rgba(138,93,250,0.06)'})透明度的层次
不同组件用了不同的透明度:
| 组件 | 透明度 | 为什么 |
|---|---|---|
| 统计卡片 | 70% | 需要足够不透明才能看清文字 |
| 控制面板 | 80% | 滑块和按钮需要高对比度 |
| 工作区 | 65% | 需要透出背景渐变 |
| 部件清单 | 60% | 内容密度高,需要更多透明度减轻视觉重量 |
越需要"看清内容"的组件,透明度越高(越接近纯白)。越需要"融入背景"的组件,透明度越低(越透明)。
点阵网格回归
旧版的点阵
之前有过 DotData 的点阵模式,后来被粒子场替代。这次又加回来了——但实现方式不同。
新版的点阵
// 生成点阵数据constdlist:DotData[]=[]for(letr:number=0;r<12;r++){for(letc:number=0;c<8;c++){dlist.push({x:(c*12.5+6).toString()+'%',y:(r*8.3+4).toString()+'%'})}}this.dots=dlist8 列 × 12 行 = 96 个点,用百分比定位。
点阵的渲染
ForEach(this.dots,(d:DotData)=>{Circle({width:2,height:2}).fill('rgba(138,93,250,0.15)').position({x:d.x,y:d.y})})每个点是 2×2 的紫色圆形,15% 透明度。
为什么点阵比粒子场更适合?
- 性能:点阵是静态的,不消耗 CPU 做动画
- 可控:点的位置和密度可以精确控制
- 简洁:不会干扰用户对内容的注意力
粒子场虽然酷炫,但作为工作区背景太"吵"了——用户需要专注看爆炸效果,不需要背景在动。
FeatureBtn:功能按钮的统一
旧版:三个独立按钮
Row(){// AI 分析按钮Row(){Text('🤖').fontSize(13).margin({right:2})Text(this.aiLoading?'分析中...':'AI 分析').fontSize(11)...}.padding(...).backgroundColor(...).onClick(...)// 部件清单按钮Row(){Text('☰').fontSize(12).fontColor(...)Text(' 部件').fontSize(11)...}.padding(...).backgroundColor(...).onClick(...)Blank()// 重置按钮Row(){Text('🔄 重置').fontSize(11)...}.padding(...).backgroundColor(...).onClick(...)}每个按钮的样式都手写一遍,代码重复。
新版:FeatureBtn Builder
@BuilderFeatureBtn(icon:string,label:string,active:boolean,bgColor:string,action:()=>void){Row(){Text(icon).fontSize(12)Text(' '+label).fontSize(10).fontWeight(FontWeight.Medium).fontColor(active?A:T2)}.padding({left:10,right:10,top:6,bottom:6}).backgroundColor(active?bgColor:'#F5F5F7').borderRadius(12).layoutWeight(1).justifyContent(FlexAlign.Center).onClick(action)}调用方式:
Row({space:6}){this.FeatureBtn('🤖',this.aiLoading?'分析中':'AI 识别',this.aiLoading,AL,()=>{if(!this.aiLoading){this.runAiAnalysis()}})this.FeatureBtn('📋','部件清单',this.list,AL,()=>{this.list=!this.list})this.FeatureBtn('🔄','重置',false,'#F5F5F7',()=>{this.setV(0);this.cg(3,3)})}FeatureBtn 的设计:
layoutWeight(1)—— 三个按钮等宽active控制颜色——激活时用主题色背景,否则用灰色bgColor参数——不同按钮可以有不同的激活背景色
重置按钮的特殊处理
this.FeatureBtn('🔄','重置',false,'#F5F5F7',()=>{this.setV(0);this.cg(3,3)})重置按钮的active永远是false——它不会处于"激活"状态。bgColor传'#F5F5F7'(灰色),因为重置不需要强调色。
拆解页 Header 的变化
Tag 新增爆炸状态
Row({space:6}){this.Tag('#8A5DFA','#F4F1FC',this.count+'部件')this.Tag('#00B894','#E8F8F5',this.cols+'×'+this.rows)if(this.on){this.Tag('#FF6B6B','#FFF0F0',Math.round(this.val*100)+'%')}}爆炸时新增第三个 Tag:红色背景 + 百分比数字。用户一眼就能看到"当前爆炸力度是多少"。
空状态的数字放大
// 旧版Text(this.count.toString()).fontSize(44)// 新版Text(this.count.toString()).fontSize(48)从 44px 增大到 48px——空状态的数字是视觉焦点,大一点更有冲击力。
网格预览的样式微调
// 旧版.backgroundColor('rgba(138,93,250,0.08)').border({width:1.5,color:'rgba(138,93,250,0.15)'})// 新版.backgroundColor('rgba(138,93,250,0.06)').border({width:1.5,color:'rgba(138,93,250,0.12)'})背景和边框都降低了约 25% 的不透明度——点阵网格已经提供了背景纹理,网格预览不需要那么显眼。
踩坑记录
1. isLoggedIn 的调用时机
aboutToAppear():void{if(!isLoggedIn()){router.pushUrl({url:'pages/LoginPage'})return}this.loadDiscover()}isLoggedIn()是同步调用——它读取本地存储的登录状态。如果后续改成异步(比如需要验证 token 有效性),需要用async/await:
aboutToAppear():asyncvoid{constloggedIn=awaitisLoggedIn()if(!loggedIn){router.pushUrl({url:'pages/LoginPage'})return}this.loadDiscover()}2. onPageShow 的重复检查
onPageShow在每次页面显示时都会调用。如果用户快速切换 Tab,isLoggedIn()会被频繁调用。
优化方案:在aboutToAppear中缓存登录状态,onPageShow只在状态变化时检查。
3. 毛玻璃在低端设备上的性能
半透明背景(rgba(255,255,255,0.65))需要 GPU 做 alpha 混合。在低端设备上,如果同时有多个半透明层叠,可能导致掉帧。
建议:在性能敏感的场景(比如爆炸动画进行中),可以把工作区背景改成纯白,减少 alpha 混合的层数。
4. 点阵的 ForEach 性能
96 个点用ForEach渲染——每个点都是一个独立的Circle组件。如果点阵更密(比如 20×20 = 400 个点),可能影响渲染性能。
优化方案:把点阵画在一张 Canvas 上,而不是用 96 个独立组件。
写在最后
登录守卫让 App 从"公开工具"变成了"用户产品"。用户需要注册才能使用,这意味着后续可以做个性化功能——保存分析历史、分享到发现页、AI 分析的调用次数限制。
毛玻璃视觉体系让整个 App 的视觉语言统一了。从首页的统计卡片到拆解页的控制面板,所有组件都用同样的半透明白色 + 极淡紫色边框。用户在不同页面之间切换时,视觉感受是一致的。
点阵网格的回归说明了一个道理:酷炫的动效(粒子场)不一定比静态的纹理(点阵)好。在需要用户专注的场景,安静的背景比吵闹的背景更合适。