HarmonyOS 3D 拆解分析:登录守卫与毛玻璃视觉体系
前言
之前的 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 会替换首页。用户登录后无法返回——因为首页已经不在导航栈里了。
登录状态对功能的影响
@State loggedIn: 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 的点阵模式,后来被粒子场替代。这次又加回来了——但实现方式不同。
新版的点阵
// 生成点阵数据
const dlist: DotData[] = []
for (let r: number = 0; r < 12; r++) {
for (let c: number = 0; c < 8; c++) {
dlist.push({
x: (c * 12.5 + 6).toString() + '%',
y: (r * 8.3 + 4).toString() + '%'
})
}
}
this.dots = dlist
8 列 × 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
@Builder FeatureBtn(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(): async void {
const loggedIn = await isLoggedIn()
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 的视觉语言统一了。从首页的统计卡片到拆解页的控制面板,所有组件都用同样的半透明白色 + 极淡紫色边框。用户在不同页面之间切换时,视觉感受是一致的。
点阵网格的回归说明了一个道理:酷炫的动效(粒子场)不一定比静态的纹理(点阵)好。在需要用户专注的场景,安静的背景比吵闹的背景更合适。
更多推荐



所有评论(0)