HarmonyOS开发:用户中心个人中心
HarmonyOS开发:用户中心个人中心
📌 核心要点:个人中心是用户的"大本营",页面设计、用户信息编辑、地址管理、设置与隐私四大模块,数据安全和隐私保护是底线。
背景与动机
你打开一个App,点"我的"——头像、昵称、订单、地址、设置……这就是个人中心。
看起来就是一堆入口?你试试。用户信息编辑要校验手机号、头像上传要裁剪压缩、地址管理要支持省市区三级联动、设置页要处理退出登录和清除缓存——每个功能都不难,但细节多到让人头秃。
更别提隐私合规。你的App收集了哪些用户数据?存在哪里?用户能删除吗?退出登录后数据怎么处理?这些问题不搞清楚,应用市场审核都过不了。
个人中心的核心不是"画页面",而是数据管理和隐私合规。每个功能都涉及用户敏感数据,处理不当就是安全事故。
核心原理
个人中心的核心是数据分层管理:用户基础信息、业务数据(订单/地址)、应用设置,三层数据独立管理、独立存储、独立更新。
数据存储策略
| 数据类型 | 存储位置 | 更新频率 | 安全等级 |
|---|---|---|---|
| 用户基础信息 | 服务端+本地缓存 | 低 | 高(敏感数据加密) |
| 收货地址 | 服务端+本地缓存 | 中 | 高(含手机号和地址) |
| 应用设置 | 本地Preferences | 高 | 低 |
| 登录凭证 | 本地加密存储 | 低 | 最高(Token加密存储) |
隐私合规要点
- 数据最小化:只收集必要的数据
- 知情同意:收集前告知用户并获取同意
- 数据可删除:用户有权删除自己的数据
- 退出登录:清除本地敏感数据,保留非敏感设置
代码实战
基础用法:个人中心页面
先搞定个人中心的主页面——用户信息展示和功能入口。
// UserCenterPage.ets — 个人中心页面
import { router } from '@kit.ArkUI'
// 用户信息
interface UserInfo {
userId: string
avatar: string
nickname: string
phone: string
gender: string
birthday: string
isVerified: boolean // 是否实名认证
}
// 功能入口项
interface MenuGroup {
title: string
items: MenuItem[]
}
interface MenuItem {
id: string
icon: Resource
title: string
subtitle?: string
badge?: number // 角标数字
routePath: string
showArrow: boolean
}
@Entry
@Component
struct UserCenterPage {
@State userInfo: UserInfo | null = null
@State orderBadges: Record<string, number> = {}
aboutToAppear() {
this.loadUserInfo()
}
build() {
Column() {
// 用户信息头部
this.UserInfoHeader()
// 订单入口
this.OrderEntry()
// 功能菜单
Scroll() {
Column() {
ForEach(this.getMenuGroups(), (group: MenuGroup) => {
this.MenuGroupSection(group)
}, (group: MenuGroup) => group.title)
}
}
.layoutWeight(1)
.scrollBar(BarState.Off)
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
// ========== 用户信息头部 ==========
@Builder
UserInfoHeader() {
Row() {
Image(this.userInfo?.avatar || $r('app.media.ic_avatar_default'))
.width(64)
.height(64)
.borderRadius(32)
.objectFit(ImageFit.Cover)
Column() {
Text(this.userInfo?.nickname || '未登录')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
if (this.userInfo?.phone) {
Text(this.maskPhone(this.userInfo.phone))
.fontSize(13)
.fontColor('#CCFFFFFF')
.margin({ top: 4 })
}
if (this.userInfo?.isVerified) {
Row() {
Image($r('app.media.ic_verified'))
.width(14)
.height(14)
.fillColor('#FFD700')
Text('已认证')
.fontSize(11)
.fontColor('#FFD700')
.margin({ left: 4 })
}
.margin({ top: 4 })
}
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 16 })
Blank()
Image($r('app.media.ic_arrow_right'))
.width(20)
.height(20)
.fillColor('#FFFFFF')
}
.width('100%')
.padding({ left: 20, right: 20, top: 24, bottom: 20 })
.linearGradient({
direction: GradientDirection.Right,
colors: [['#1DA1F2', 0], ['#0D7CC4', 1]]
})
.onClick(() => {
if (!this.userInfo) {
router.pushUrl({ url: 'pages/LoginPage' })
} else {
router.pushUrl({ url: 'pages/UserProfilePage' })
}
})
}
// ========== 订单入口 ==========
@Builder
OrderEntry() {
Column() {
Row() {
Text('我的订单')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
Blank()
Text('查看全部 >')
.fontSize(13)
.fontColor('#999999')
.onClick(() => {
router.pushUrl({ url: 'pages/OrderListPage' })
})
}
.width('100%')
Row() {
this.OrderTabItem($r('app.media.ic_order_pending_pay'), '待付款', this.orderBadges['PENDING_PAYMENT'] || 0, 'pages/OrderListPage')
this.OrderTabItem($r('app.media.ic_order_pending_ship'), '待发货', this.orderBadges['PENDING_SHIPMENT'] || 0, 'pages/OrderListPage')
this.OrderTabItem($r('app.media.ic_order_pending_receive'), '待收货', this.orderBadges['PENDING_RECEIPT'] || 0, 'pages/OrderListPage')
this.OrderTabItem($r('app.media.ic_order_completed'), '已完成', 0, 'pages/OrderListPage')
this.OrderTabItem($r('app.media.ic_order_return'), '退换货', this.orderBadges['RETURN'] || 0, 'pages/OrderListPage')
}
.width('100%')
.margin({ top: 16 })
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
.margin({ top: -16, left: 12, right: 12 })
}
@Builder
OrderTabItem(icon: Resource, title: string, badge: number, route: string) {
Column() {
Stack() {
Image(icon)
.width(28)
.height(28)
.fillColor('#333333')
if (badge > 0) {
Text(badge > 99 ? '99+' : `${badge}`)
.fontSize(9)
.fontColor(Color.White)
.backgroundColor('#FF4444')
.borderRadius(8)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
.position({ x: 16, y: -6 })
}
}
Text(title)
.fontSize(12)
.fontColor('#333333')
.margin({ top: 6 })
}
.layoutWeight(1)
.onClick(() => {
router.pushUrl({ url: route })
})
}
// ========== 菜单分组 ==========
@Builder
MenuGroupSection(group: MenuGroup) {
Column() {
ForEach(group.items, (item: MenuItem, index?: number) => {
Row() {
Image(item.icon)
.width(22)
.height(22)
.fillColor('#666666')
Column() {
Text(item.title)
.fontSize(15)
.fontColor('#333333')
if (item.subtitle) {
Text(item.subtitle)
.fontSize(12)
.fontColor('#999999')
.margin({ top: 2 })
}
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 12 })
Blank()
if (item.badge && item.badge > 0) {
Text(`${item.badge}`)
.fontSize(10)
.fontColor(Color.White)
.backgroundColor('#FF4444')
.borderRadius(8)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
}
if (item.showArrow) {
Image($r('app.media.ic_arrow_right'))
.width(16)
.height(16)
.fillColor('#CCCCCC')
.margin({ left: 4 })
}
}
.width('100%')
.height(52)
.padding({ left: 16, right: 16 })
.onClick(() => {
router.pushUrl({ url: item.routePath })
})
if ((index ?? 0) < group.items.length - 1) {
Divider().color('#F0F0F0').margin({ left: 50 })
}
}, (item: MenuItem) => item.id)
}
.width('100%')
.backgroundColor(Color.White)
.borderRadius(8)
.margin({ top: 8, left: 12, right: 12 })
}
// ========== 菜单数据 ==========
getMenuGroups(): MenuGroup[] {
return [
{
title: '服务',
items: [
{ id: 'coupon', icon: $r('app.media.ic_coupon'), title: '优惠券', subtitle: '3张可用', routePath: 'pages/CouponPage', showArrow: true },
{ id: 'favorite', icon: $r('app.media.ic_favorite'), title: '我的收藏', routePath: 'pages/FavoritePage', showArrow: true },
{ id: 'history', icon: $r('app.media.ic_history'), title: '浏览足迹', routePath: 'pages/BrowseHistoryPage', showArrow: true },
{ id: 'address', icon: $r('app.media.ic_address'), title: '收货地址', routePath: 'pages/AddressListPage', showArrow: true },
]
},
{
title: '设置',
items: [
{ id: 'notification', icon: $r('app.media.ic_notification'), title: '消息通知', routePath: 'pages/NotificationSettingPage', showArrow: true },
{ id: 'privacy', icon: $r('app.media.ic_privacy'), title: '隐私设置', routePath: 'pages/PrivacySettingPage', showArrow: true },
{ id: 'security', icon: $r('app.media.ic_security'), title: '账号安全', routePath: 'pages/AccountSecurityPage', showArrow: true },
{ id: 'about', icon: $r('app.media.ic_about'), title: '关于我们', subtitle: 'v1.0.0', routePath: 'pages/AboutPage', showArrow: true },
{ id: 'settings', icon: $r('app.media.ic_settings'), title: '设置', routePath: 'pages/SettingsPage', showArrow: true },
]
}
]
}
// ========== 辅助方法 ==========
maskPhone(phone: string): string {
if (phone.length === 11) {
return `${phone.substring(0, 3)}****${phone.substring(7)}`
}
return phone
}
loadUserInfo() {
this.userInfo = {
userId: 'user_001',
avatar: 'https://picsum.photos/128/128?random=1',
nickname: '鸿蒙开发者',
phone: '13800138000',
gender: '男',
birthday: '1995-01-15',
isVerified: true,
}
this.orderBadges = {
'PENDING_PAYMENT': 2,
'PENDING_SHIPMENT': 1,
'PENDING_RECEIPT': 3,
}
}
}
进阶用法:用户信息编辑与地址管理
用户信息编辑涉及头像上传、表单校验;地址管理涉及省市区联动、默认地址设置。
// UserProfilePage.ets — 用户信息编辑
import { router } from '@kit.ArkUI'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
@Entry
@Component
struct UserProfilePage {
@State avatar: string = ''
@State nickname: string = ''
@State phone: string = ''
@State gender: string = ''
@State birthday: string = ''
@State isVerified: boolean = false
@State showGenderPicker: boolean = false
@State showBirthdayPicker: boolean = false
aboutToAppear() {
this.loadUserProfile()
}
build() {
Column() {
// 顶部导航
Row() {
Image($r('app.media.ic_back')).width(24).height(24).fillColor('#333333')
.onClick(() => { router.back() })
Text('个人信息').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 12 })
Blank()
Text('保存').fontSize(16).fontColor('#1DA1F2').onClick(() => { this.saveProfile() })
}
.width('100%').height(48).padding({ left: 16, right: 16 }).backgroundColor(Color.White)
// 信息列表
List() {
// 头像
ListItem() {
Row() {
Text('头像').fontSize(15).fontColor('#333333')
Blank()
Image(this.avatar).width(48).height(48).borderRadius(24).objectFit(ImageFit.Cover)
Image($r('app.media.ic_arrow_right')).width(16).height(16).fillColor('#CCCCCC').margin({ left: 8 })
}
.width('100%').height(64).padding({ left: 16, right: 16 }).backgroundColor(Color.White)
.onClick(() => { this.changeAvatar() })
}
// 昵称
ListItem() {
Row() {
Text('昵称').fontSize(15).fontColor('#333333')
Blank()
Text(this.nickname).fontSize(15).fontColor('#999999')
Image($r('app.media.ic_arrow_right')).width(16).height(16).fillColor('#CCCCCC').margin({ left: 8 })
}
.width('100%').height(52).padding({ left: 16, right: 16 }).backgroundColor(Color.White)
.onClick(() => { router.pushUrl({ url: 'pages/EditNicknamePage' }) })
}
// 手机号
ListItem() {
Row() {
Text('手机号').fontSize(15).fontColor('#333333')
Blank()
Text(this.maskPhone(this.phone)).fontSize(15).fontColor('#999999')
Image($r('app.media.ic_arrow_right')).width(16).height(16).fillColor('#CCCCCC').margin({ left: 8 })
}
.width('100%').height(52).padding({ left: 16, right: 16 }).backgroundColor(Color.White)
}
// 性别
ListItem() {
Row() {
Text('性别').fontSize(15).fontColor('#333333')
Blank()
Text(this.gender || '未设置').fontSize(15).fontColor('#999999')
Image($r('app.media.ic_arrow_right')).width(16).height(16).fillColor('#CCCCCC').margin({ left: 8 })
}
.width('100%').height(52).padding({ left: 16, right: 16 }).backgroundColor(Color.White)
.onClick(() => { this.showGenderPicker = true })
}
.bindMenu($$this.showGenderPicker, ['男', '女', '保密'], (value: string) => {
this.gender = value
this.showGenderPicker = false
})
// 生日
ListItem() {
Row() {
Text('生日').fontSize(15).fontColor('#333333')
Blank()
Text(this.birthday || '未设置').fontSize(15).fontColor('#999999')
Image($r('app.media.ic_arrow_right')).width(16).height(16).fillColor('#CCCCCC').margin({ left: 8 })
}
.width('100%').height(52).padding({ left: 16, right: 16 }).backgroundColor(Color.White)
}
// 实名认证
ListItem() {
Row() {
Text('实名认证').fontSize(15).fontColor('#333333')
Blank()
Text(this.isVerified ? '已认证' : '未认证')
.fontSize(15).fontColor(this.isVerified ? '#2E7D32' : '#FF4444')
Image($r('app.media.ic_arrow_right')).width(16).height(16).fillColor('#CCCCCC').margin({ left: 8 })
}
.width('100%').height(52).padding({ left: 16, right: 16 }).backgroundColor(Color.White)
}
}
.layoutWeight(1)
.divider({ strokeWidth: 0.5, color: '#F0F0F0', startMargin: 16 })
.scrollBar(BarState.Off)
}
.width('100%').height('100%').backgroundColor('#F5F5F5')
}
async changeAvatar() {
try {
// 实际项目:调用系统图片选择器
// const picker = new photoAccessHelper.PhotoViewPicker()
// const result = await picker.select({ MIMEType: photoAccessHelper.PhotoViewMIMEType.IMAGE_TYPE, maxSelectNumber: 1 })
// 上传头像到云存储
// const url = await uploadAvatar(result.photoUris[0])
// this.avatar = url
} catch (error) {
console.error(`[UserProfile] 更换头像失败: ${JSON.stringify(error)}`)
}
}
async saveProfile() {
// 保存用户信息
console.info('[UserProfile] 保存用户信息')
router.back()
}
maskPhone(phone: string): string {
if (phone.length === 11) return `${phone.substring(0, 3)}****${phone.substring(7)}`
return phone
}
loadUserProfile() {
this.avatar = 'https://picsum.photos/128/128?random=1'
this.nickname = '鸿蒙开发者'
this.phone = '13800138000'
this.gender = '男'
this.birthday = '1995-01-15'
this.isVerified = true
}
}
完整示例:地址管理与设置页
把地址管理(增删改查、默认地址)和设置页(退出登录、清除缓存、隐私管理)串成完整链路。
// AddressListPage.ets — 收货地址管理
import { router } from '@kit.ArkUI'
import { promptAction } from '@kit.ArkUI'
// 地址数据
interface AddressItem {
id: string
name: string // 收货人
phone: string // 手机号
province: string // 省
city: string // 市
district: string // 区
detail: string // 详细地址
isDefault: boolean // 是否默认
tag?: string // 标签:家/公司/学校
}
@Entry
@Component
struct AddressListPage {
@State addressList: AddressItem[] = []
@State isSelectMode: boolean = false // 选择地址模式(从订单页进入)
aboutToAppear() {
const params = router.getParams() as Record<string, boolean>
this.isSelectMode = params?.selectMode || false
this.loadAddressList()
}
build() {
Column() {
// 顶部栏
Row() {
Image($r('app.media.ic_back')).width(24).height(24).fillColor('#333333')
.onClick(() => { router.back() })
Text('收货地址').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 12 })
Blank()
}
.width('100%').height(48).padding({ left: 16, right: 16 }).backgroundColor(Color.White)
if (this.addressList.length === 0) {
Column() {
Text('暂无收货地址')
.fontSize(16).fontColor('#999999')
Text('添加一个收货地址吧')
.fontSize(13).fontColor('#CCCCCC').margin({ top: 8 })
}
.width('100%').layoutWeight(1).justifyContent(FlexAlign.Center)
} else {
List() {
ForEach(this.addressList, (addr: AddressItem) => {
ListItem() {
this.AddressCard(addr)
}
.swipeAction({ end: this.AddressSwipeActions(addr) })
}, (addr: AddressItem) => addr.id)
}
.layoutWeight(1)
.scrollBar(BarState.Off)
.divider({ strokeWidth: 0.5, color: '#F0F0F0' })
}
// 新增地址按钮
Button('新增收货地址')
.width('90%').height(44)
.fontSize(16).fontColor(Color.White)
.backgroundColor('#1DA1F2').borderRadius(22)
.margin({ top: 12, bottom: 24 })
.onClick(() => {
router.pushUrl({ url: 'pages/EditAddressPage' })
})
}
.width('100%').height('100%').backgroundColor('#F5F5F5')
}
@Builder
AddressCard(addr: AddressItem) {
Column() {
Row() {
Text(addr.name).fontSize(16).fontWeight(FontWeight.Medium).fontColor('#333333')
Text(addr.phone).fontSize(14).fontColor('#999999').margin({ left: 12 })
if (addr.tag) {
Text(addr.tag)
.fontSize(11).fontColor('#1DA1F2').backgroundColor('#E3F2FD')
.borderRadius(2).padding({ left: 4, right: 4, top: 1, bottom: 1 })
.margin({ left: 8 })
}
if (addr.isDefault) {
Text('默认')
.fontSize(11).fontColor('#FF4444').backgroundColor('#FFF0F0')
.borderRadius(2).padding({ left: 4, right: 4, top: 1, bottom: 1 })
.margin({ left: 4 })
}
}
Text(`${addr.province}${addr.city}${addr.district} ${addr.detail}`)
.fontSize(14).fontColor('#666666').margin({ top: 8 })
.maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis })
// 底部操作
Row() {
Row() {
Checkbox()
.select($$addr.isDefault)
.onChange((checked: boolean) => {
this.setDefault(addr.id)
})
.width(16).height(16)
Text('默认地址').fontSize(12).fontColor('#999999').margin({ left: 4 })
}
Blank()
Text('编辑').fontSize(13).fontColor('#1DA1F2')
.onClick(() => {
router.pushUrl({ url: 'pages/EditAddressPage', params: { addressId: addr.id } })
})
Text('删除').fontSize(13).fontColor('#FF4444').margin({ left: 16 })
.onClick(() => { this.deleteAddress(addr.id) })
}
.width('100%').margin({ top: 12 })
}
.width('100%').padding(16).backgroundColor(Color.White)
.onClick(() => {
if (this.isSelectMode) {
// 选择地址后返回
router.back()
}
})
}
@Builder
AddressSwipeActions(addr: AddressItem) {
Button('删除')
.fontSize(14).fontColor(Color.White)
.backgroundColor('#FF4444').width(70).height('100%').borderRadius(0)
.onClick(() => { this.deleteAddress(addr.id) })
}
setDefault(addressId: string) {
this.addressList.forEach(a => a.isDefault = (a.id === addressId))
this.addressList = [...this.addressList]
}
deleteAddress(addressId: string) {
this.addressList = this.addressList.filter(a => a.id !== addressId)
}
loadAddressList() {
this.addressList = [
{ id: '1', name: '张三', phone: '13800138000', province: '北京市', city: '北京市', district: '朝阳区', detail: 'xxx街道xxx小区1号楼101', isDefault: true, tag: '家' },
{ id: '2', name: '张三', phone: '13800138000', province: '北京市', city: '北京市', district: '海淀区', detail: 'xxx大厦8层', isDefault: false, tag: '公司' },
]
}
}
// ========== 设置页 ==========
@Entry
@Component
struct SettingsPage {
@State cacheSize: string = '23.5MB'
@State isLogin: boolean = true
build() {
Column() {
Row() {
Image($r('app.media.ic_back')).width(24).height(24).fillColor('#333333')
.onClick(() => { router.back() })
Text('设置').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333').margin({ left: 12 })
}
.width('100%').height(48).padding({ left: 16 }).backgroundColor(Color.White)
Scroll() {
Column() {
// 通用设置
Column() {
this.SettingItem('消息通知', '', true)
Divider().color('#F0F0F0').margin({ left: 16 })
this.SettingItem('深色模式', '', true)
Divider().color('#F0F0F0').margin({ left: 16 })
this.SettingItem('语言', '简体中文', true)
}
.width('100%').backgroundColor(Color.White).borderRadius(8).margin({ top: 8, left: 12, right: 12 })
// 隐私与安全
Column() {
this.SettingItem('隐私权限', '', true)
Divider().color('#F0F0F0').margin({ left: 16 })
this.SettingItem('账号安全', '', true)
Divider().color('#F0F0F0').margin({ left: 16 })
this.SettingItem('个人信息收集清单', '', true)
Divider().color('#F0F0F0').margin({ left: 16 })
this.SettingItem('第三方信息共享清单', '', true)
}
.width('100%').backgroundColor(Color.White).borderRadius(8).margin({ top: 8, left: 12, right: 12 })
// 其他
Column() {
this.SettingItem('清除缓存', this.cacheSize, true)
Divider().color('#F0F0F0').margin({ left: 16 })
this.SettingItem('关于', 'v1.0.0', true)
}
.width('100%').backgroundColor(Color.White).borderRadius(8).margin({ top: 8, left: 12, right: 12 })
// 退出登录
if (this.isLogin) {
Button('退出登录')
.width('90%').height(44)
.fontSize(16).fontColor('#FF4444')
.backgroundColor(Color.White).borderRadius(8)
.margin({ top: 24 })
.onClick(() => {
this.logout()
})
}
}
}
.layoutWeight(1)
}
.width('100%').height('100%').backgroundColor('#F5F5F5')
}
@Builder
SettingItem(title: string, subtitle: string, showArrow: boolean) {
Row() {
Text(title).fontSize(15).fontColor('#333333')
Blank()
if (subtitle) {
Text(subtitle).fontSize(14).fontColor('#999999').margin({ right: 4 })
}
if (showArrow) {
Image($r('app.media.ic_arrow_right')).width(16).height(16).fillColor('#CCCCCC')
}
}
.width('100%').height(52).padding({ left: 16, right: 16 }).backgroundColor(Color.White)
}
async logout() {
// 1. 清除登录凭证
// 2. 清除本地敏感数据
// 3. 断开WebSocket
// 4. 跳转登录页
this.isLogin = false
router.replaceUrl({ url: 'pages/LoginPage' })
}
}
踩坑与注意事项
坑1:头像上传裁剪
用户选了一张4000x3000的照片当头像,直接上传?10MB的图片,上传慢、显示也慢。
解决方案:选图后先裁剪成正方形,再压缩到200x200,最后上传。裁剪可以用Canvas API,压缩用Image Kit的packing能力。
坑2:省市区三级联动数据
地址选择器的省市区数据从哪来?写死在代码里?那每次行政区划变更都要发版。
解决方案:省市区数据从服务端获取,本地缓存。数据格式用{code, name, children}的树形结构。
坑3:默认地址逻辑
用户设置了新默认地址,旧默认地址要取消。但如果两个地址同时设为默认呢?
解决方案:设默认地址时,服务端先取消所有默认,再设置新的默认。客户端同步更新。
坑4:退出登录数据清理
退出登录后,本地还存着用户头像、昵称、地址——下一个登录的用户能看到上一个用户的数据。
解决方案:退出登录时清除所有本地敏感数据,包括:
- 用户信息缓存
- 登录Token
- 购物车数据
- 地址数据
- 保留非敏感设置(如深色模式偏好)
坑5:手机号脱敏
手机号在页面上显示为138****8000,但传给服务端时必须是完整手机号。
解决方案:显示用脱敏版本,数据存储用完整版本。UI层做脱敏,数据层不做。
HarmonyOS 6适配说明
HarmonyOS 6对个人中心相关能力做了以下更新:
-
PhotoViewPicker增强:图片选择器新增裁剪功能,选择头像时可以直接裁剪成正方形,不需要额外的裁剪组件。
-
安全存储增强:
@kit.BasicServicesKit新增了HUKS(Universal KeyStore)的安全存储能力,Token等敏感数据可以用硬件级加密存储,即使手机被root也无法读取。 -
隐私合规API:新增
@kit.PrivacyKit,提供隐私合规检查能力。App可以一键检查收集了哪些数据、是否有用户授权、是否符合应用市场要求。 -
地址选择器组件:新增
AddressPicker组件,内置省市区三级联动数据,支持搜索和最近使用记录。 -
退出登录优化:
@kit.AccountKit新增了统一退出登录接口,一次调用清除所有账号相关数据,不需要逐个清理。
总结
个人中心的核心不是"画页面",而是数据管理和隐私合规。每个功能都涉及用户敏感数据,处理不当就是安全事故。
核心记住三点:
- 敏感数据加密存储,Token用HUKS硬件加密,手机号脱敏显示
- 退出登录必须清理数据,本地敏感数据全清,保留非敏感设置
- 隐私合规不是可选项,个人信息收集清单、第三方共享清单必须有
| 评估维度 | 说明 |
|---|---|
| 学习难度 | ⭐⭐⭐ 功能多但每个都不复杂,隐私合规需要关注 |
| 使用频率 | ⭐⭐⭐⭐⭐ 所有App都有个人中心 |
| 重要程度 | ⭐⭐⭐⭐ 隐私合规不过关,应用市场审核都过不了 |
用户退出登录后,下一个登录的人能看到上一个用户的地址——这不是bug,这是安全事故。
更多推荐



所有评论(0)