引言

在这里插入图片描述

随着智能汽车产业的蓬勃发展,智驾硬件升级已成为车主购车后的核心刚需。从行车记录仪到激光雷达,从HUD抬显到域控制器,消费者需要一个集中化的平台来浏览、对比和选购各类智驾装备。本应用正是基于HarmonyOS ArkTS声明式UI框架构建的"智能驾驶升级馆",采用单文件页面架构,集成了智驾商城、行车记录、HUD抬显、智驾硬件、用车社区、消息中心和个人中心共七个核心Tab模块,同时配套五个功能弹框,覆盖了从商品浏览到预约安装的完整业务闭环。

在技术架构层面,本应用充分利用了ArkTS的状态驱动渲染机制。通过@Entry@Component装饰器定义入口组件,使用@State进行细粒度的响应式状态管理,当currentTabshowAddModal等状态变量发生变化时,框架会自动触发对应UI区域的差异化更新。此外,@Builder装饰器被大量用于构建可复用的UI片段,包括静态头部、底部Tab栏、七个Tab内容区以及五个功能弹框,使得近一千四百行代码在逻辑上保持了清晰的模块化分层。整个应用不依赖任何外部UI库,纯靠ArkTS原生组件(Column、Row、Scroll、Flex、Stack、ForEach等)完成了所有视觉呈现。

在业务设计层面,应用模拟了一个完整的汽车智驾硬件电商生态。智驾商城Tab展示了24件精选装备,涵盖记录仪、HUD、激光雷达、域控制器等多品类;行车记录Tab以万像素柱状图和双列网格呈现12款记录仪;HUD抬显Tab通过横向滚动卡片和亮度对比图展示10款HUD设备;智驾硬件Tab以TOPS算力柱状图为视觉锚点展示10款高性能域控平台;用车社区Tab展示10条真实车主的智驾体验帖;消息中心Tab聚合了OTA推送、安装顾问、社区互动等12条结构化消息;个人中心Tab则提供了用户等级、行驶里程和功能菜单入口。五个弹框分别处理发布装备、升级套餐选择、预约安装、删除确认和商品详情等关键交互流程,构成了完整的用户操作链路。

数据模型与状态管理

在这里插入图片描述

商品数据接口定义

应用首先定义了GoodsItem接口作为所有商品、帖子和消息的统一数据模型。这个接口包含了名称、价格、描述、评分、热度数值、标签和热门标记七个字段,通过单一数据结构适配了商城装备、社区帖子和系统消息等多种业务场景。

interface GoodsItem {
  name: string
  price: number
  desc: string
  score: number
  num: number
  tag: string
  hot: string
}

@Entry
@Component
struct Index {
  @State currentTab: number = 0
  @State showAddModal: boolean = false
  @State showPackModal: boolean = false
  @State showShopModal: boolean = false
  @State showDeleteModal: boolean = false
  @State showDetailModal: boolean = false
  @State selectedItem: GoodsItem | null = null
  @State chipA: number = 0
  @State chipB: number = 0
  @State stepNum: number = 1
  @State stepNumB: number = 1
  @State addDesc: string = ''
}

在上述代码中,@State装饰器声明的变量构成了应用的核心状态树。currentTab控制当前激活的Tab页面索引,五个show*Modal布尔值分别控制五个弹框的显隐,selectedItem保存当前被点击的商品对象,chipAchipB追踪弹框内Chip选择器的选中索引,stepNumstepNumB管理数量步进器的当前值,addDesc则绑定文本输入框的内容。这些状态变量的任何变更都会触发ArkUI框架的精确差分更新,确保UI与数据始终保持同步。

Tab与Chip配置数据

在这里插入图片描述

应用通过私有数组预先配置了所有Tab标签、Chip选项和统计数据,这些静态数据在组件初始化时即被加载,避免了运行时网络请求的复杂性。

  // ---------- 顶部 Tab ----------
  private tabs: string[] = ['智驾商城', '行车记录', 'HUD抬显', '智驾硬件', '用车社区', '消息', '我的']

  // ---------- 各类 chips ----------
  private kindChips: string[] = ['行车记录仪', 'HUD', '激光雷达', '域控制器', '传感器']
  private carChips: string[] = ['特斯拉', '蔚来', '小鹏', '理想', '比亚迪', '极氪']
  private packChips: string[] = ['基础辅助', '高阶智驾', '全栈智享', '旗舰领航']
  private packPrices: number[] = [3999, 12999, 25999, 39999]
  private shopChips: string[] = ['中关村店', '望京店', '亦庄店', '顺义店']
  private timeChips: string[] = ['上午', '下午', '晚间']
  private topicChips: string[] = ['城区领航实测', 'HUD改装日记', '记录仪夜视', '激光雷达解析', 'OTA体验', '续航分享', '高速NOA']

  // ---------- 统计卡数据 ----------
  private statLabels: string[] = ['在售装备', '本月安装', '好评率']
  private statNums: string[] = ['1,286', '3,452', '98.6%']
  private statColors: string[] = ['#2962FF', '#00B8D4', '#E53935']
  private myStatLabels: string[] = ['我的装备', 'OTA记录', '预约安装']
  private myStatNums: string[] = ['8', '23', '2']

这里的设计思路是将所有配置项集中管理,tabs数组定义了底部导航栏的七个入口标签,kindChipscarChips分别为发布装备弹框提供品类和车型选项,packChipspackPrices为升级套餐弹框提供方案名称与对应价格,shopChipstimeChips为预约安装弹框提供门店和时段选择。统计卡数据则通过statLabelsstatNumsstatColors三个平行数组的方式组织,配合ForEach的index参数实现数据与样式的动态绑定。

商品列表数据源

在这里插入图片描述

应用的核心数据源是mainList数组,包含24条精心编排的智驾装备数据,每条数据都包含了完整的商品信息。此外还有dashListhudListhwListpostListmsgList五个分类数组,分别服务于不同的Tab页面。

  private mainList: GoodsItem[] = [
    { name: '70迈 4K 智能记录仪 Pro', price: 899, desc: '4K超清 · 语音控制 · 电子狗', score: 96, num: 460, tag: '热销', hot: '爆' },
    { name: '盯盯拍 Mini5 记录仪', price: 699, desc: '5K超清 · 语音抓拍 · 缩时录影', score: 94, num: 380, tag: '热销', hot: '荐' },
    { name: '海康威视 N6+ 记录仪', price: 749, desc: '2K星光 · 停车监控 · 语音声控', score: 92, num: 300, tag: '新品', hot: '新' },
    { name: '华为智选 70寸 HUD', price: 1699, desc: '70寸投影 · 自动亮度 · 导航投射', score: 95, num: 410, tag: '热销', hot: '爆' },
    { name: '路畅 6G 毫米波雷达', price: 2599, desc: '前向感知 · 盲区补全 · 全天候', score: 90, num: 260, tag: '新品', hot: '荐' },
    { name: '禾赛 XT32 激光雷达', price: 4999, desc: '32线 · 200米量程 · 车规级', score: 97, num: 480, tag: '旗舰', hot: '爆' },
    { name: '速腾 M1 激光雷达', price: 4599, desc: 'MEMS · 点云成像 · 150米', score: 95, num: 440, tag: '旗舰', hot: '爆' },
    { name: '地平线 征程5 域控制器', price: 8999, desc: '128TOPS · 深度学习加速', score: 93, num: 330, tag: '高算力', hot: '荐' },
    { name: '黑芝麻 A1000 域控制器', price: 9999, desc: '116TOPS · 车规级双芯', score: 92, num: 290, tag: '高算力', hot: '荐' },
    { name: 'Mobileye 6L 辅助驾驶盒', price: 12999, desc: '单目视觉 · 预警算法大师', score: 96, num: 350, tag: '旗舰', hot: '爆' },
    { name: '经纬恒润 L2 辅助套件', price: 6999, desc: 'ACC · LKA · 一键升级', score: 94, num: 320, tag: '高算力', hot: '爆' }
  ]

每条商品数据的设计都非常贴近真实电商场景。score字段既用于好评进度条的宽度计算(直接作为百分比),又通过selStars()方法转换为五星评分展示。num字段在不同Tab中承载不同语义——在商城中代表热度值,在行车记录中代表万像素,在HUD中代表流明,在智驾硬件中代表TOPS算力,在社区中代表点赞数,在消息中代表未读条数。这种一字段多语义的设计减少了接口定义的冗余,但要求开发者在渲染时根据Tab上下文正确解读数据含义。

辅助方法与选中项读取

在这里插入图片描述

应用定义了一组sel*辅助方法用于安全地读取当前选中商品的各个字段,这些方法在selectedItem为null时返回默认值,避免了空指针异常。

  selName(): string {
    if (this.selectedItem !== null) { return this.selectedItem.name }
    return ''
  }
  selPrice(): number {
    if (this.selectedItem !== null) { return this.selectedItem.price }
    return 0
  }
  selScore(): number {
    if (this.selectedItem !== null) { return this.selectedItem.score }
    return 0
  }
  selStars(): string {
    if (this.selectedItem === null) { return '' }
    let filled: number = Math.floor(this.selectedItem.score / 20)
    let res: string = ''
    for (let i = 0; i < 5; i++) {
      if (i < filled) { res += '★' } else { res += '☆' }
    }
    return res
  }
  maxNumOf(list: GoodsItem[]): number {
    let m: number = 1
    for (let i = 0; i < list.length; i++) {
      if (list[i].num > m) { m = list[i].num }
    }
    return m
  }
  packTotal(): number {
    return Math.floor(this.packPrices[this.chipA] * this.stepNum)
  }

selStars()方法将评分百分比值转换为五星字符串,通过Math.floor(score / 20)计算填充星星数量(满分100分对应5颗星)。maxNumOf()方法遍历列表找到最大num值,用于柱状图的高度归一化计算——每个柱子的高度等于item.num / maxNum * 基准高度,确保所有柱子按比例填充图表区域。packTotal()方法根据当前选中的套餐索引和数量步进值计算总价,体现了状态联动计算的典型模式。

静态头部与底部Tab栏构建

在这里插入图片描述

头部Builder的设计

头部区域使用@Builder装饰器定义为headerBuilder,包含了应用标题、OTA图标、消息角标、搜索胶囊和信息条四个功能区块,整体采用极客蓝#2962FF背景配合白色文字的配色方案。

  @Builder
  headerBuilder() {
    Column({ space: 10 }) {
      Row() {
        Column() {
          Text('智能驾驶升级馆').fontSize(20).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
        }.alignItems(HorizontalAlign.Start)
        Column().layoutWeight(1)

        Row({ space: 12 }) {
          Text('📡').fontSize(22)
          Stack({ alignContent: Alignment.TopEnd }) {
            Text('🔔').fontSize(22)
            Text('6').fontSize(8).fontColor('#FFFFFF').backgroundColor('#E53935')
              .borderRadius(7).padding({ left: 4, right: 4, top: 1, bottom: 1 })
              .constraintSize({ minWidth: 14 })
          }.width(30).height(30)
        }.alignItems(VerticalAlign.Center)
      }.width('100%').alignItems(VerticalAlign.Center)

      Row({ space: 8 }) {
        Text('🛰').fontSize(16)
        Text('搜智驾硬件/记录仪').fontSize(13).fontColor('#90A4AE')
        Column().layoutWeight(1)
        Text('🔍').fontSize(14)
      }.width('100%').height(36).padding({ left: 14, right: 14 })
      .backgroundColor('#E8EAF6').borderRadius(18)
      .alignItems(VerticalAlign.Center)

      Row({ space: 6 }) {
        Text('🚀').fontSize(13)
        Text('本周 OTA 推送:新增城区领航 · 覆盖 38 万台车')
          .fontSize(11).fontColor('#FFFFFF')
      }.width('100%').padding({ left: 12, right: 12, top: 6, bottom: 6 })
      .backgroundColor('rgba(255,255,255,0.2)').borderRadius(8)
      .alignItems(VerticalAlign.Center)
    }.width('100%').padding({ left: 14, right: 14, top: 10, bottom: 10 })
    .backgroundColor('#2962FF').alignItems(HorizontalAlign.Start)
  }

头部Builder的核心设计在于消息角标的实现。使用Stack容器配合alignContent: Alignment.TopEnd将红色未读数字角标定位到铃铛图标的右上角,通过constraintSize({ minWidth: 14 })确保角标在单数字和双数字情况下都能保持最小宽度。搜索胶囊采用浅蓝底#E8EAF6配合圆角18的胶囊造型,内部使用layoutWeight(1)的空Column实现搜索图标与放大镜图标的左右分离布局。信息条使用半透明白色背景rgba(255,255,255,0.2)叠加在蓝色底色上,营造出层次感的通告效果。

底部Tab栏的实现

在这里插入图片描述

底部Tab栏通过ForEach遍历tabs数组动态生成七个Tab项,每个Tab项包含文字标签和底部指示条,指示条的颜色和宽度根据当前选中状态动态变化。

  @Builder
  bottomBuilder() {
    Row() {
      ForEach(this.tabs, (tab: string, index: number) => {
        Column({ space: 2 }) {
          Text(tab).fontSize(10).fontColor(this.currentTab === index ? '#2962FF' : '#999999')
          Column().width(18).height(3).borderRadius(2)
            .backgroundColor(this.currentTab === index ? '#2962FF' : '#FFFFFF00')
        }.onClick(() => { this.currentTab = index })
      }, (tab: string) => tab)
    }.width('100%').height(56).backgroundColor('#FFFFFF')
    .justifyContent(FlexAlign.SpaceAround).alignItems(VerticalAlign.Center)
  }

底部Tab栏的交互逻辑非常简洁:点击任意Tab项时,将currentTab状态变量设置为对应索引值,框架随即触发页面内容区的条件渲染切换。未选中的Tab文字使用#999999灰色,选中时变为#2962FF极客蓝,同时底部出现一条18像素宽的蓝色指示条。未选中状态的指示条使用透明色#FFFFFF00占位,保持了布局高度的一致性,避免选中切换时的跳动。整个Tab栏通过justifyContent(FlexAlign.SpaceAround)实现七个Tab项的均匀分布。

Tab1智驾商城:统计卡与装备列表

三格统计卡与快捷入口

智驾商城Tab以三格统计卡开场,展示在售装备数、本月安装量和好评率三项关键运营指标,并通过快捷操作入口提供发布装备、升级套餐和预约安装三个功能入口。

  @Builder
  tab0() {
    Column({ space: 12 }) {
      Row({ space: 8 }) {
        ForEach(this.statLabels, (label: string, index: number) => {
          Column({ space: 4 }) {
            Text(this.statNums[index]).fontSize(18)
              .fontColor(this.statColors[index]).fontWeight(FontWeight.Bold)
            Text(label).fontSize(11).fontColor('#90A4AE')
          }.layoutWeight(1).padding({ top: 10, bottom: 10 })
          .backgroundColor('#FFFFFF').borderRadius(10).alignItems(HorizontalAlign.Center)
          .scale(index === 2 ? { x: 1.05, y: 1.05 } : { x: 1, y: 1 })
          .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
        }, (label: string) => label)
      }.width('100%').alignItems(VerticalAlign.Center)

      Row({ space: 8 }) {
        Text('📤 发布装备').fontSize(12).fontColor('#2962FF').padding(8)
          .backgroundColor('#E8EAF6').borderRadius(14)
          .onClick(() => { this.showAddModal = true })
        Text('⚡ 升级套餐').fontSize(12).fontColor('#00B8D4').padding(8)
          .backgroundColor('#E0F7FA').borderRadius(14)
          .onClick(() => { this.showPackModal = true })
        Text('🔧 预约安装').fontSize(12).fontColor('#E53935').padding(8)
          .backgroundColor('#FDECEA').borderRadius(14)
          .onClick(() => { this.showShopModal = true })
      }.width('100%').justifyContent(FlexAlign.SpaceBetween)

统计卡的核心技术点在于第三个卡片(好评率)的脉冲动画效果。通过scale属性设置{ x: 1.05, y: 1.05 }的放大比例,并链式调用.animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })实现无限循环的呼吸式缩放动画,iterations: -1表示无限重复,Curve.EaseInOut使动画在放大和缩小时都呈现平滑的缓入缓出效果。快捷入口按钮采用justifyContent(FlexAlign.SpaceBetween)实现三等分均匀分布,每个按钮的点击事件直接设置对应的弹框显示状态为true

24条装备行式列表渲染

装备列表采用ForEach遍历mainList数组,每行包含左侧圆形首字图标、中部商品信息和右侧热度数值三个区域,通过Row的space和Column的layoutWeight实现弹性布局。

      ForEach(this.mainList, (item: GoodsItem, index: number) => {
        Row({ space: 10 }) {
          Column() {
            Text(item.name.substring(0, 1)).fontSize(16).fontColor('#FFFFFF')
              .fontWeight(FontWeight.Bold)
          }.width(46).height(46).borderRadius(23)
          .backgroundColor(index % 3 === 0 ? '#2962FF' : (index % 3 === 1 ? '#00B8D4' : '#263238'))
          .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)

          Column({ space: 5 }) {
            Text(item.name).fontSize(14).fontColor('#263238')
              .fontWeight(FontWeight.Medium).maxLines(1)
            Text(item.desc).fontSize(11).fontColor('#90A4AE').maxLines(1)
            Row() {
              Column().width(item.score + '%').height(6)
                .backgroundColor('#00B8D4').borderRadius(3)
            }.width('100%').height(6).backgroundColor('#E8EAF6').borderRadius(3)

            Row({ space: 6 }) {
              Text('¥' + Math.floor(item.price)).fontSize(15).fontColor('#E53935')
                .fontWeight(FontWeight.Bold)
              Text(item.tag).fontSize(9).fontColor('#2962FF')
                .padding({ left: 5, right: 5, top: 2, bottom: 2 })
                .backgroundColor('#E8EAF6').borderRadius(4)
            }.alignItems(VerticalAlign.Center)
          }.layoutWeight(1).alignItems(HorizontalAlign.Start)

          Column({ space: 6 }) {
            Text(item.hot).fontSize(12).fontColor('#E53935').fontWeight(FontWeight.Bold)
              .scale({ x: 1.05, y: 1.05 })
              .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            Text(item.num + '').fontSize(11).fontColor('#263238')
            Text('热度').fontSize(9).fontColor('#B0BEC5')
          }.alignItems(HorizontalAlign.Center)
        }.width('100%').padding(12).backgroundColor('#FFFFFF').borderRadius(10)
        .alignItems(VerticalAlign.Center)
        .onClick(() => {
          this.selectedItem = item
          this.showDetailModal = true
        })
      }, (item: GoodsItem) => item.name)

列表项的左侧图标采用item.name.substring(0, 1)提取商品名首字作为图标内容,背景色通过index % 3取模运算在极客蓝、荧光青和深空灰三种颜色间循环切换,营造视觉节奏感。好评进度条通过Column().width(item.score + '%')动态设置填充宽度,外层Row设置固定的浅蓝底色作为轨道,内层Column叠加荧光青填充条。右侧热度区域的热门标记文字同样应用了脉冲缩放动画,与统计卡形成统一的动效语言。整行的点击事件将商品对象赋值给selectedItem并触发详情弹框显示。

Tab2行车记录:柱状图与双列网格

像素柱状图实现

行车记录Tab的特色在于以万像素为单位的柱状图可视化,通过ForEach遍历dashList数组,每个柱子的高度根据item.num / maxNumOf(list) * 120的归一化公式动态计算。

  @Builder
  tab1() {
    Column({ space: 12 }) {
      Text('万像素像素量分布').fontSize(15).fontColor('#263238').fontWeight(FontWeight.Bold)
      Row({ space: 6 }) {
        ForEach(this.dashList, (item: GoodsItem, index: number) => {
          Column() {
            Column()
              .width(14)
              .height(item.num / this.maxNumOf(this.dashList) * 120)
              .backgroundColor(index % 2 === 0 ? '#2962FF' : '#00B8D4')
              .borderRadius({ topLeft: 4, topRight: 4 })
          }.alignItems(HorizontalAlign.Center)
        }, (item: GoodsItem) => item.name)
      }.width('100%').height(130).backgroundColor('#FFFFFF').borderRadius(10)
      .padding({ left: 8, right: 8 })
      .justifyContent(FlexAlign.SpaceBetween).alignItems(VerticalAlign.End)

柱状图的实现原理是利用Column组件作为柱子容器,内层Column设置固定宽度14像素和动态计算的高度值。maxNumOf()方法先遍历整个列表找到最大像素值作为归一化基准,然后每个柱子的高度等于当前值 / 最大值 * 120,确保最高的柱子达到120像素的上限。外层Row通过justifyContent(FlexAlign.SpaceBetween)alignItems(VerticalAlign.End)实现柱子的均匀分布和底部对齐,模拟出标准柱状图的视觉效果。柱子颜色通过index % 2在极客蓝和荧光青间交替,顶部的圆角borderRadius({ topLeft: 4, topRight: 4 })使柱子更加精致。

记录仪双列网格布局

在柱状图下方,12款记录仪以双列网格的形式展示,使用Flex的wrap属性实现自动换行,每个卡片包含名称、万像素数值、描述、价格和夜视标签等信息。

      Text('记录仪机型一览').fontSize(15).fontColor('#263238').fontWeight(FontWeight.Bold)
      Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
        ForEach(this.dashList, (item: GoodsItem, index: number) => {
          Column({ space: 6 }) {
            Row() {
              Text(item.name).fontSize(13).fontColor('#263238')
                .fontWeight(FontWeight.Medium).maxLines(1)
              Column().layoutWeight(1)
              Text(item.num + '万').fontSize(12).fontColor('#2962FF')
                .fontWeight(FontWeight.Bold)
            }.width('100%').alignItems(VerticalAlign.Center)

            Text(item.desc).fontSize(10).fontColor('#90A4AE').maxLines(1)

            Row() {
              Text('¥' + Math.floor(item.price)).fontSize(13).fontColor('#E53935')
                .fontWeight(FontWeight.Bold)
              Column().layoutWeight(1)
              Text(item.tag).fontSize(9).fontColor('#FFFFFF').padding(3)
                .backgroundColor('#00B8D4').borderRadius(4)
                .scale({ x: 1.05, y: 1.05 })
                .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            }.width('100%').alignItems(VerticalAlign.Center)

            Row() {
              Column().width(item.score + '%').height(4)
                .backgroundColor('#2962FF').borderRadius(2)
            }.width('100%').height(4).backgroundColor('#E8EAF6').borderRadius(2)
          }.width('48%').padding(10).backgroundColor('#FFFFFF').borderRadius(10)
          .alignItems(HorizontalAlign.Start)
          .margin({ bottom: 10 })
          .onClick(() => {
            this.selectedItem = item
            this.showDetailModal = true
          })
        }, (item: GoodsItem) => item.name)
      }.width('100%')

双列网格通过Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween })实现,每个卡片宽度设为48%,两个卡片之间通过SpaceBetween自动分配间距。夜视标签item.tag同样应用了脉冲动画效果,与全局动效风格保持一致。好评进度条在此处采用4像素高度和2像素圆角的更紧凑样式,适配双列卡片的较小尺寸。点击卡片同样触发详情弹框的显示,保持了与商城列表一致的交互模式。

Tab3 HUD抬显:横向滚动与亮度对比

深色统计卡与亮度柱状图

HUD Tab采用深色主题统计卡作为开场,展示最大投影尺寸、峰值亮度和数据刷新延迟三项关键参数,随后以亮度柱状图直观对比各机型的流明数值。

  @Builder
  tab2() {
    Column({ space: 12 }) {
      Row({ space: 8 }) {
        Column({ space: 4 }) {
          Text('70寸').fontSize(20).fontColor('#00B8D4').fontWeight(FontWeight.Bold)
          Text('最大投影尺寸').fontSize(10).fontColor('#B0BEC5')
        }.layoutWeight(1).alignItems(HorizontalAlign.Center)

        Column().width(1).height(32).backgroundColor('#455A64')

        Column({ space: 4 }) {
          Text('12000').fontSize(20).fontColor('#00B8D4').fontWeight(FontWeight.Bold)
            .scale({ x: 1.05, y: 1.05 })
            .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
          Text('峰值亮度 cd/m2').fontSize(10).fontColor('#B0BEC5')
        }.layoutWeight(1).alignItems(HorizontalAlign.Center)

        Column().width(1).height(32).backgroundColor('#455A64')

        Column({ space: 4 }) {
          Text('0.1s').fontSize(20).fontColor('#00B8D4').fontWeight(FontWeight.Bold)
          Text('数据刷新延迟').fontSize(10).fontColor('#B0BEC5')
        }.layoutWeight(1).alignItems(HorizontalAlign.Center)
      }.width('100%').padding({ top: 14, bottom: 14 })
      .backgroundColor('#263238').borderRadius(12)
      .alignItems(VerticalAlign.Center)

深色统计卡采用#263238深空灰背景配合#00B8D4荧光青数值文字,形成科技感强烈的暗色视觉风格。三格之间通过1像素宽、32像素高的#455A64色Column作为分隔线,实现了简洁的分隔效果。峰值亮度数值"12000"应用了脉冲缩放动画,引导用户关注这一核心卖点参数。亮度柱状图与像素柱状图实现原理一致,但柱子颜色在荧光青和深空灰间交替,与深色统计卡的主题色呼应。

横向滚动HUD卡片

HUD设备展示采用横向滚动布局,通过Scroll组件的scrollable(ScrollDirection.Horizontal)属性实现水平滑动,每张卡片以大号投影尺寸数字为视觉焦点。

      Scroll() {
        Row({ space: 12 }) {
          ForEach(this.hudList, (item: GoodsItem, index: number) => {
            Column({ space: 8 }) {
              Column({ space: 4 }) {
                Text(item.num + '寸').fontSize(26).fontColor('#FFFFFF')
                  .fontWeight(FontWeight.Bold)
                Text(item.name).fontSize(11).fontColor('#E0F7FA')
              }.width('100%').height(90).justifyContent(FlexAlign.Center)
              .backgroundColor(index % 2 === 0 ? '#2962FF' : '#263238')
              .borderRadius(10).alignItems(HorizontalAlign.Center)

              Text('¥' + Math.floor(item.price)).fontSize(14).fontColor('#E53935')
                .fontWeight(FontWeight.Bold)
              Text(item.desc).fontSize(10).fontColor('#90A4AE').maxLines(1)
              Text('查看详情').fontSize(10).fontColor('#FFFFFF').padding({
                left: 14, right: 14, top: 5, bottom: 5
              }).backgroundColor('#00B8D4').borderRadius(12)
            }.width(140).padding(10).backgroundColor('#FFFFFF').borderRadius(12)
            .alignItems(HorizontalAlign.Center)
            .onClick(() => {
              this.selectedItem = item
              this.showDetailModal = true
            })
          }, (item: GoodsItem) => item.name)
        }.padding({ left: 12, right: 12 })
      }.scrollable(ScrollDirection.Horizontal).width('100%')

横向滚动卡片的核心设计在于每张卡片顶部的大号投影尺寸展示区。item.num + '寸'以26号字体白色粗体显示在蓝灰交替的背景色块上,高度90像素并居中对齐,成为视觉焦点。卡片宽度固定为140像素,通过Row({ space: 12 })控制卡片间距。Scroll组件的scrollable(ScrollDirection.Horizontal)属性使内容可以水平滑动浏览,用户可以通过手势快速浏览所有10款HUD设备,这种交互模式在展示同品类多商品时比垂直列表更加高效。

Tab4智驾硬件:算力对比与列表

TOPS算力柱状图

智驾硬件Tab以TOPS算力柱状图为视觉核心,直观展示各域控平台的算力差异,柱子颜色采用三色循环(极客蓝、荧光青、警示红)以区分不同算力等级。

  @Builder
  tab3() {
    Column({ space: 12 }) {
      Text('平台算力对比 TOPS').fontSize(15).fontColor('#263238').fontWeight(FontWeight.Bold)
      Row({ space: 8 }) {
        ForEach(this.hwList, (item: GoodsItem, index: number) => {
          Column() {
            Column()
              .width(16)
              .height(item.num / this.maxNumOf(this.hwList) * 120)
              .backgroundColor(index % 3 === 0 ? '#2962FF' : (index % 3 === 1 ? '#00B8D4' : '#E53935'))
              .borderRadius({ topLeft: 4, topRight: 4 })
          }.alignItems(HorizontalAlign.Center)
        }, (item: GoodsItem) => item.name)
      }.width('100%').height(130).backgroundColor('#FFFFFF').borderRadius(10)
      .padding({ left: 8, right: 8 })
      .justifyContent(FlexAlign.SpaceBetween).alignItems(VerticalAlign.End)

算力柱状图的实现与前述像素和亮度柱状图采用相同的归一化算法,但柱子颜色扩展为三色循环:index % 3 === 0为极客蓝代表旗舰级算力,index % 3 === 1为荧光青代表高算力,index % 3 === 2为警示红代表传感器类。这种颜色编码使用户能够一眼区分不同类型的硬件产品。柱子宽度为16像素,比前两个柱状图的14像素略宽,因为TOPS数值通常更大,需要更宽的柱子来承载视觉重量。

硬件列表与OTA进度条

硬件列表采用行式布局,每行左侧以彩条标识硬件类型,中部展示名称、标签、描述和OTA进度条,右侧以脉冲动画展示算力数值。

      ForEach(this.hwList, (item: GoodsItem, index: number) => {
        Row({ space: 10 }) {
          Column().width(4).height(56).borderRadius(2)
            .backgroundColor(index % 3 === 0 ? '#2962FF' : (index % 3 === 1 ? '#00B8D4' : '#E53935'))

          Column({ space: 5 }) {
            Row({ space: 6 }) {
              Text(item.name).fontSize(14).fontColor('#263238')
                .fontWeight(FontWeight.Medium).maxLines(1)
              Text(item.tag).fontSize(9).fontColor('#2962FF')
                .padding({ left: 5, right: 5, top: 2, bottom: 2 })
                .backgroundColor('#E8EAF6').borderRadius(4)
            }.alignItems(VerticalAlign.Center)

            Text(item.desc).fontSize(11).fontColor('#90A4AE').maxLines(1)

            Row({ space: 6 }) {
              Row() {
                Column().width(item.score + '%').height(5)
                  .backgroundColor('#2962FF').borderRadius(3)
              }.layoutWeight(1).height(5).backgroundColor('#E8EAF6').borderRadius(3)

              Text('OTA ' + item.score + '%').fontSize(9).fontColor('#2962FF')
            }.width('100%').alignItems(VerticalAlign.Center)
          }.layoutWeight(1).alignItems(HorizontalAlign.Start)

          Column({ space: 3 }) {
            Text(item.num + '').fontSize(16).fontColor('#263238').fontWeight(FontWeight.Bold)
              .scale({ x: 1.05, y: 1.05 })
              .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            Text('TOPS').fontSize(9).fontColor('#90A4AE')
          }.alignItems(HorizontalAlign.Center)
        }.width('100%').padding(12).backgroundColor('#FFFFFF').borderRadius(10)
        .alignItems(VerticalAlign.Center)
      }, (item: GoodsItem) => item.name)

硬件列表的特色在于左侧4像素宽的彩条标识,通过index % 3取模在三种颜色间循环,与柱状图的颜色编码保持一致。OTA进度条使用layoutWeight(1)使进度条自适应剩余空间,右侧紧跟"OTA xx%"的文字标注,进度条颜色采用极客蓝,与标签颜色呼应。右侧算力数值以16号粗体字显示,配合脉冲缩放动画强调算力这一核心参数。整个布局通过Row的space: 10和Column的layoutWeight实现了左彩条、中信息、右数值的三栏弹性结构。

Tab5用车社区与Tab6消息中心

社区话题与帖子展示

用车社区Tab以话题Chip标签开场,展示10条车主真实智驾体验帖子,每条帖子包含圆形头像、用户名、话题标签、正文内容和点赞评论数据。

  @Builder
  tab4() {
    Column({ space: 12 }) {
      Flex({ wrap: FlexWrap.Wrap }) {
        ForEach(this.topicChips, (topic: string, index: number) => {
          Text('# ' + topic).fontSize(11)
            .fontColor(index % 2 === 0 ? '#2962FF' : '#00B8D4')
            .padding({ left: 10, right: 10, top: 5, bottom: 5 })
            .backgroundColor('#E8EAF6').borderRadius(12)
            .margin({ right: 8, bottom: 8 })
        }, (topic: string) => topic)
      }.width('100%')

      ForEach(this.postList, (item: GoodsItem, index: number) => {
        Row({ space: 10 }) {
          Column() {
            Text(item.name.substring(0, 1)).fontSize(16).fontColor('#FFFFFF')
              .fontWeight(FontWeight.Bold)
          }.width(42).height(42).borderRadius(21)
          .backgroundColor(index % 3 === 0 ? '#2962FF' : (index % 3 === 1 ? '#00B8D4' : '#E53935'))
          .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)

          Column({ space: 5 }) {
            Row({ space: 6 }) {
              Text(item.name).fontSize(13).fontColor('#263238')
                .fontWeight(FontWeight.Medium)
              Text('# ' + item.tag).fontSize(9).fontColor('#00B8D4')
            }.alignItems(VerticalAlign.Center)

            Text(item.desc).fontSize(11).fontColor('#607D8B').maxLines(2)

            Row({ space: 12 }) {
              Row({ space: 4 }) {
                Text('👍').fontSize(12)
                Text(item.num + '').fontSize(11).fontColor('#E53935')
                  .fontWeight(FontWeight.Bold)
                  .scale({ x: 1.05, y: 1.05 })
                  .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
              }.alignItems(VerticalAlign.Center)

              Row({ space: 4 }) {
                Text('💬').fontSize(12)
                Text(Math.floor(item.num / 5) + '').fontSize(11).fontColor('#90A4AE')
              }.alignItems(VerticalAlign.Center)

              Column().layoutWeight(1)

              if (item.hot !== '') {
                Text(item.hot).fontSize(9).fontColor('#E53935')
                  .padding({ left: 5, right: 5, top: 2, bottom: 2 })
                  .border({ width: 1, color: '#E53935' }).borderRadius(4)
              }
            }.width('100%').alignItems(VerticalAlign.Center)
          }.layoutWeight(1).alignItems(HorizontalAlign.Start)
        }.width('100%').padding(12).backgroundColor('#FFFFFF').borderRadius(10)
        .alignItems(VerticalAlign.Top)
      }, (item: GoodsItem) => item.name)

社区帖子的技术亮点在于点赞数的脉冲动画。点赞数字item.num以红色粗体显示并应用缩放动画,吸引用户关注热门帖子。评论数通过Math.floor(item.num / 5)从点赞数推算,模拟了点赞与评论的比例关系。帖子正文使用maxLines(2)限制为两行显示,避免长文本撑开布局。右下角的item.hot热门标记仅在非空时显示,使用border属性创建空心边框标签效果,与实心背景标签形成视觉差异。

消息中心结构化列表

消息中心Tab展示12条结构化消息,每条消息包含发送方头像、名称、内容摘要、时间戳和未读条数角标,未读红点以脉冲动画提示用户注意。

  @Builder
  tab5() {
    Column({ space: 10 }) {
      Row() {
        Text('消息中心').fontSize(15).fontColor('#263238').fontWeight(FontWeight.Bold)
        Column().layoutWeight(1)
        Text('未读 24').fontSize(11).fontColor('#E53935')
      }.width('100%').alignItems(VerticalAlign.Center)

      ForEach(this.msgList, (item: GoodsItem, index: number) => {
        Row({ space: 10 }) {
          Stack({ alignContent: Alignment.TopEnd }) {
            Column() {
              Text(item.hot).fontSize(15).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
            }.width(44).height(44).borderRadius(22)
            .backgroundColor(index % 4 === 0 ? '#2962FF' : (index % 4 === 1 ? '#00B8D4' : (index % 4 === 2 ? '#263238' : '#E53935')))
            .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)

            if (item.num > 0) {
              Column().width(10).height(10).borderRadius(5).backgroundColor('#E53935')
                .scale({ x: 1.05, y: 1.05 })
                .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            }
          }.width(46).height(46)

          Column({ space: 4 }) {
            Row() {
              Text(item.name).fontSize(13).fontColor('#263238')
                .fontWeight(FontWeight.Medium).maxLines(1)
              Column().layoutWeight(1)
              Text(item.tag).fontSize(10).fontColor('#B0BEC5')
            }.width('100%').alignItems(VerticalAlign.Center)

            Row() {
              Text(item.desc).fontSize(11).fontColor('#90A4AE').maxLines(1)
              Column().layoutWeight(1)
              if (item.num > 0) {
                Text(item.num + '条').fontSize(9).fontColor('#FFFFFF')
                  .padding({ left: 5, right: 5, top: 1, bottom: 1 })
                  .backgroundColor('#E53935').borderRadius(8)
              }
            }.width('100%').alignItems(VerticalAlign.Center)
          }.layoutWeight(1).alignItems(HorizontalAlign.Start)
        }.width('100%').padding(12).backgroundColor('#FFFFFF').borderRadius(10)
        .alignItems(VerticalAlign.Center)
      }, (item: GoodsItem) => item.name)

消息列表的技术核心在于未读红点的实现。使用Stack({ alignContent: Alignment.TopEnd })将10x10像素的红色圆点定位到头像右上角,仅在item.num > 0时渲染,配合脉冲缩放动画形成持续的视觉提醒。消息头像背景色采用四色循环index % 4,比其他Tab的三色循环更加丰富,因为消息来源类型更加多样(系统、服务、店铺、社区等)。未读条数角标使用红色背景白色文字的胶囊样式,圆角8像素,与红点形成双层未读提示体系。

Tab7个人中心与菜单系统

深空灰个人卡片

个人中心Tab以深空灰背景的个人信息卡开场,展示用户头像、昵称、智驾等级和行驶里程,随后是三格统计卡和六行功能菜单。

  @Builder
  tab6() {
    Column({ space: 12 }) {
      Row({ space: 12 }) {
        Column() {
          Text('驾').fontSize(24).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
        }.width(64).height(64).borderRadius(32).backgroundColor('#2962FF')
        .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)

        Column({ space: 6 }) {
          Text('车主_7078').fontSize(17).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
          Row({ space: 8 }) {
            Text('智驾等级 L2+').fontSize(10).fontColor('#00B8D4')
              .padding({ left: 8, right: 8, top: 3, bottom: 3 })
              .backgroundColor('rgba(0,184,212,0.15)').borderRadius(8)
              .scale({ x: 1.05, y: 1.05 })
              .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            Text('行驶 42,860 km').fontSize(10).fontColor('#B0BEC5')
          }.alignItems(VerticalAlign.Center)
        }.layoutWeight(1).alignItems(HorizontalAlign.Start)
      }.width('100%').padding(16).backgroundColor('#263238').borderRadius(14)
      .alignItems(VerticalAlign.Center)

      Row({ space: 8 }) {
        ForEach(this.myStatLabels, (label: string, index: number) => {
          Column({ space: 4 }) {
            Text(this.myStatNums[index]).fontSize(18).fontColor('#2962FF')
              .fontWeight(FontWeight.Bold)
            Text(label).fontSize(11).fontColor('#90A4AE')
          }.layoutWeight(1).padding({ top: 10, bottom: 10 })
          .backgroundColor('#FFFFFF').borderRadius(10)
          .alignItems(HorizontalAlign.Center)
        }, (label: string) => label)
      }.width('100%').alignItems(VerticalAlign.Center)

个人卡片的视觉风格与HUD深色统计卡呼应,采用#263238深空灰背景。用户头像使用64x64的圆形蓝色背景块,内嵌"驾"字白色粗体文字。"智驾等级 L2+"标签使用半透明荧光青背景rgba(0,184,212,0.15)配合脉冲动画,成为个人信息区唯一的动态元素。三格统计卡使用极客蓝数值文字,与个人卡的荧光青形成色彩区分。整个个人中心的配色策略是从暗到明的渐变——深空灰个人卡到白色统计卡再到白色菜单项,形成了视觉层次的自然过渡。

六行功能菜单

功能菜单以ForEach遍历menuNames数组生成六行列表项,每行包含圆形图标、菜单名称和右箭头,点击不同菜单项触发不同的弹框或Tab切换。

      ForEach(this.menuNames, (menu: string, index: number) => {
        Row({ space: 10 }) {
          Column() {
            Text(index === 0 ? '📦' : (index === 1 ? '❤️' : (index === 2 ? '🎟️' : (index === 3 ? '🔧' : (index === 4 ? '🎧' : '⚙️')))))
              .fontSize(16)
          }.width(36).height(36).borderRadius(18).backgroundColor('#E8EAF6')
          .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)

          Text(menu).fontSize(13).fontColor('#263238')
          Column().layoutWeight(1)
          Text('›').fontSize(18).fontColor('#B0BEC5')
        }.width('100%').padding({ left: 12, right: 12, top: 10, bottom: 10 })
        .backgroundColor('#FFFFFF').borderRadius(10)
        .alignItems(VerticalAlign.Center)
        .onClick(() => {
          if (index === 1) {
            this.showDeleteModal = true
          } else if (index === 3) {
            this.showShopModal = true
          } else if (index === 0) {
            this.showPackModal = true
          } else {
            this.currentTab = 6
          }
        })
      }, (menu: string) => menu)

      Text('智能驾驶升级馆 · 让每一次升级都安心').fontSize(10).fontColor('#B0BEC5')

菜单图标的实现采用了嵌套三元运算符根据index选择不同的emoji图标,虽然写法紧凑但可读性有所牺牲。菜单点击逻辑通过index判断触发不同操作:index为0(我的订单)触发升级套餐弹框,index为1(我的收藏)触发删除确认弹框,index为3(预约安装)触发预约安装弹框,其他菜单项则保持在个人中心Tab。底部的slogan文字"智能驾驶升级馆 · 让每一次升级都安心"以灰色小字居中显示,为整个Tab页面画上句号。

应用交互流程概览

点击底部Tab

点击发布装备

点击升级套餐

点击预约安装

点击商品项

取消收藏

立即购买

应用启动

渲染头部Builder

根据currentTab渲染对应Tab内容

用户操作

更新currentTab状态

showAddModal = true

弹出底部发布装备弹框

选择品类Chip/车型Chip

输入装备描述

点击立即上架关闭弹框

showPackModal = true

弹出底部升级套餐弹框

选择方案Chip

步进器调整硬件数量

实时计算packTotal总价

点击确认升级关闭弹框

showShopModal = true

弹出底部预约安装弹框

选择门店Chip/时段Chip

步进器调整工位数量

点击提交预约关闭弹框

selectedItem = item

showDetailModal = true

右侧滑出详情面板

查看详情/取消收藏

showDeleteModal = true

弹出删除确认弹框

关闭详情弹框

上述流程图完整描绘了应用的核心交互路径。从应用启动到各个弹框的触发和关闭,每一步都由@State状态变量的变更驱动。五个弹框的显隐完全由对应的布尔状态变量控制,通过Stack的图层叠加实现模态覆盖效果。升级套餐弹框中的总价计算packTotal()是一个典型的状态联动案例——当用户切换方案Chip或调整数量步进器时,chipAstepNum的变化会自动触发packTotal()的重新计算和UI更新。

弹框系统:发布装备与升级套餐

发布装备底部弹框

发布装备弹框从底部弹出,包含品类选择Chip、车型适配Chip、装备描述输入框和立即上架按钮,整体采用底部弹出配合顶部圆角的Sheet样式。

  @Builder
  addModal() {
    Column() {
      Column().layoutWeight(1)
      Column({ space: 12 }) {
        Row() {
          Text('发布智驾装备').fontSize(16).fontColor('#263238').fontWeight(FontWeight.Bold)
          Column().layoutWeight(1)
          Text('✕').fontSize(14).fontColor('#90A4AE')
            .onClick(() => { this.showAddModal = false })
        }.width('100%').alignItems(VerticalAlign.Center)

        Text('选择品类').fontSize(12).fontColor('#90A4AE')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.kindChips, (kind: string, index: number) => {
            Text(kind).fontSize(12)
              .fontColor(this.chipA === index ? '#FFFFFF' : '#2962FF')
              .padding({ left: 12, right: 12, top: 6, bottom: 6 })
              .backgroundColor(this.chipA === index ? '#2962FF' : '#E8EAF6')
              .borderRadius(14)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => { this.chipA = index })
          }, (kind: string) => kind)
        }.width('100%')

        Text('适配车型').fontSize(12).fontColor('#90A4AE')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.carChips, (car: string, index: number) => {
            Text(car).fontSize(12)
              .fontColor(this.chipB === index ? '#FFFFFF' : '#00B8D4')
              .padding({ left: 12, right: 12, top: 6, bottom: 6 })
              .backgroundColor(this.chipB === index ? '#00B8D4' : '#E0F7FA')
              .borderRadius(14)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => { this.chipB = index })
          }, (car: string) => car)
        }.width('100%')

        Text('装备描述').fontSize(12).fontColor('#90A4AE')
        TextInput({ placeholder: '一句话介绍你的智驾装备', text: this.addDesc })
          .height(44).fontSize(12).backgroundColor('#F5F5F5').borderRadius(8)
          .onChange((value: string) => { this.addDesc = value })

        Text('立即上架').fontSize(14).fontColor('#FFFFFF')
          .width('100%').textAlign(TextAlign.Center).padding(12)
          .backgroundColor('#2962FF').borderRadius(22)
          .onClick(() => { this.showAddModal = false })
      }.width('100%').padding(16).backgroundColor('#FFFFFF')
      .borderRadius({ topLeft: 16, topRight: 16 })
      .constraintSize({ maxHeight: '80%' })
      .alignItems(HorizontalAlign.Start)
      .onClick((event: ClickEvent) => { event.stopPropagation() })
    }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
    .onClick(() => { this.showAddModal = false })
  }

弹框的实现采用了经典的Overlay模式:外层Column铺满全屏并设置半透明黑色背景rgba(0,0,0,0.5)作为遮罩,内层通过Column().layoutWeight(1)将内容推到底部。Chip选择器的选中态通过条件表达式动态切换文字颜色和背景色——选中时为白字蓝底,未选中时为蓝字浅蓝底。TextInput组件的onChange回调将输入值同步到addDesc状态变量,实现双向数据绑定。内层Column的onClick使用event.stopPropagation()阻止事件冒泡,确保点击弹框内容区域不会触发外层遮罩的关闭逻辑,而点击遮罩区域则直接关闭弹框。

升级套餐弹框与数量步进器

升级套餐弹框展示了方案选择、数量步进和实时总价计算三个核心功能,步进器通过条件样式反映操作边界(最小1件、最大6件)。

  @Builder
  packModal() {
    Column() {
      Column().layoutWeight(1)
      Column({ space: 12 }) {
        Row() {
          Text('升级套餐').fontSize(16).fontColor('#263238').fontWeight(FontWeight.Bold)
          Column().layoutWeight(1)
          Text('✕').fontSize(14).fontColor('#90A4AE')
            .onClick(() => { this.showPackModal = false })
        }.width('100%').alignItems(VerticalAlign.Center)

        Text('当前方案:' + this.packChips[this.chipA]).fontSize(13).fontColor('#2962FF')
          .fontWeight(FontWeight.Medium)

        Text('选择方案').fontSize(12).fontColor('#90A4AE')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.packChips, (pack: string, index: number) => {
            Text(pack).fontSize(12)
              .fontColor(this.chipA === index ? '#FFFFFF' : '#263238')
              .padding({ left: 14, right: 14, top: 7, bottom: 7 })
              .backgroundColor(this.chipA === index ? '#2962FF' : '#E8EAF6')
              .borderRadius(15)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => { this.chipA = index })
          }, (pack: string) => pack)
        }.width('100%')

        Row() {
          Text('硬件数量(限 1-6 件)').fontSize(12).fontColor('#90A4AE')
          Column().layoutWeight(1)
          Text('¥' + this.packTotal()).fontSize(18).fontColor('#E53935').fontWeight(FontWeight.Bold)
            .scale({ x: 1.05, y: 1.05 })
            .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
        }.width('100%').alignItems(VerticalAlign.Center)

        Row({ space: 16 }) {
          Text('-').fontSize(16).fontColor(this.stepNum <= 1 ? '#B0BEC5' : '#2962FF')
            .width(30).height(30).borderRadius(15).textAlign(TextAlign.Center)
            .border({ width: 1, color: this.stepNum <= 1 ? '#B0BEC5' : '#2962FF' })
            .onClick(() => {
              if (this.stepNum > 1) { this.stepNum = this.stepNum - 1 }
            })
          Text(this.stepNum + ' 件').fontSize(14).fontColor('#263238').fontWeight(FontWeight.Medium)
          Text('+').fontSize(16).fontColor(this.stepNum >= 6 ? '#B0BEC5' : '#2962FF')
            .width(30).height(30).borderRadius(15).textAlign(TextAlign.Center)
            .border({ width: 1, color: this.stepNum >= 6 ? '#B0BEC5' : '#2962FF' })
            .onClick(() => {
              if (this.stepNum < 6) { this.stepNum = this.stepNum + 1 }
            })
        }.justifyContent(FlexAlign.Center).width('100%')

        Text('确认升级').fontSize(14).fontColor('#FFFFFF')
          .width('100%').textAlign(TextAlign.Center).padding(12)
          .backgroundColor('#00B8D4').borderRadius(22)
          .onClick(() => { this.showPackModal = false })
      }.width('100%').padding(16).backgroundColor('#FFFFFF')
      .borderRadius({ topLeft: 16, topRight: 16 })
      .constraintSize({ maxHeight: '80%' })
      .alignItems(HorizontalAlign.Start)
      .onClick((event: ClickEvent) => { event.stopPropagation() })
    }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
    .onClick(() => { this.showPackModal = false })
  }

步进器的核心技术在于条件样式绑定。当stepNum <= 1时,减号按钮的文字和边框变为灰色#B0BEC5,表示已到达下限不可再减;当stepNum >= 6时,加号按钮同样变为灰色不可再加。这种视觉反馈通过条件表达式直接绑定到fontColorborder属性,框架会在stepNum变化时自动更新样式。总价packTotal()的计算公式为packPrices[chipA] * stepNum,当方案或数量任一变化时都会触发重新计算,总价数字还应用了脉冲动画强调金额。确认升级按钮使用荧光青#00B8D4背景,与方案Chip的极客蓝形成色彩区分。

弹框系统:预约安装与删除确认

预约安装弹框的双Chip与双步进器

预约安装弹框包含门店选择Chip、时段选择Chip和工位数量步进器,复用了chipAchipB两个状态变量分别管理门店和时段的选中状态。

  @Builder
  shopModal() {
    Column() {
      Column().layoutWeight(1)
      Column({ space: 12 }) {
        Row() {
          Text('预约安装').fontSize(16).fontColor('#263238').fontWeight(FontWeight.Bold)
          Column().layoutWeight(1)
          Text('✕').fontSize(14).fontColor('#90A4AE')
            .onClick(() => { this.showShopModal = false })
        }.width('100%').alignItems(VerticalAlign.Center)

        Text('选择门店').fontSize(12).fontColor('#90A4AE')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.shopChips, (shop: string, index: number) => {
            Text(shop).fontSize(12)
              .fontColor(this.chipA === index ? '#FFFFFF' : '#2962FF')
              .padding({ left: 12, right: 12, top: 6, bottom: 6 })
              .backgroundColor(this.chipA === index ? '#2962FF' : '#E8EAF6')
              .borderRadius(14)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => { this.chipA = index })
          }, (shop: string) => shop)
        }.width('100%')

        Text('选择时段').fontSize(12).fontColor('#90A4AE')
        Row({ space: 8 }) {
          ForEach(this.timeChips, (time: string, index: number) => {
            Text(time).fontSize(12)
              .fontColor(this.chipB === index ? '#FFFFFF' : '#00B8D4')
              .padding({ left: 16, right: 16, top: 7, bottom: 7 })
              .backgroundColor(this.chipB === index ? '#00B8D4' : '#E0F7FA')
              .borderRadius(15)
              .onClick(() => { this.chipB = index })
          }, (time: string) => time)
        }.width('100%')

        Row() {
          Text('施工工位(限 1-2 位)').fontSize(12).fontColor('#90A4AE')
          Column().layoutWeight(1)
          Text(this.shopChips[this.chipA] + ' · ' + this.timeChips[this.chipB])
            .fontSize(11).fontColor('#263238')
        }.width('100%').alignItems(VerticalAlign.Center)

        Row({ space: 16 }) {
          Text('-').fontSize(16).fontColor(this.stepNumB <= 1 ? '#B0BEC5' : '#E53935')
            .width(30).height(30).borderRadius(15).textAlign(TextAlign.Center)
            .border({ width: 1, color: this.stepNumB <= 1 ? '#B0BEC5' : '#E53935' })
            .onClick(() => {
              if (this.stepNumB > 1) { this.stepNumB = this.stepNumB - 1 }
            })
          Text(this.stepNumB + ' 位').fontSize(14).fontColor('#263238').fontWeight(FontWeight.Medium)
          Text('+').fontSize(16).fontColor(this.stepNumB >= 2 ? '#B0BEC5' : '#E53935')
            .width(30).height(30).borderRadius(15).textAlign(TextAlign.Center)
            .border({ width: 1, color: this.stepNumB >= 2 ? '#B0BEC5' : '#E53935' })
            .onClick(() => {
              if (this.stepNumB < 2) { this.stepNumB = this.stepNumB + 1 }
            })
        }.justifyContent(FlexAlign.Center).width('100%')

        Text('提交预约').fontSize(14).fontColor('#FFFFFF')
          .width('100%').textAlign(TextAlign.Center).padding(12)
          .backgroundColor('#2962FF').borderRadius(22)
          .onClick(() => { this.showShopModal = false })
      }.width('100%').padding(16).backgroundColor('#FFFFFF')
      .borderRadius({ topLeft: 16, topRight: 16 })
      .constraintSize({ maxHeight: '80%' })
      .alignItems(HorizontalAlign.Start)
      .onClick((event: ClickEvent) => { event.stopPropagation() })
    }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
    .onClick(() => { this.showShopModal = false })
  }

预约安装弹框的状态复用策略值得关注。chipAchipB两个状态变量在多个弹框间共享——在发布装备弹框中分别管理品类和车型,在升级套餐弹框中管理方案选择,在预约安装弹框中管理门店和时段。这种共享设计减少了状态变量的数量,但也意味着同时只能打开一个弹框(应用的设计约束保证了这一点)。工位步进器的范围为1-2位,比升级套餐的1-6件更窄,使用独立的stepNumB变量管理。步进器的边框和文字颜色采用警示红#E53935而非极客蓝,与提交预约按钮的极客蓝形成功能区分。

删除确认居中弹框

删除确认弹框采用居中小卡片样式,顶部红色警示区配合底部取消/删除双按钮,与底部弹出的其他弹框形成视觉对比。

  @Builder
  deleteModal() {
    Column() {
      Column() {
        Column({ space: 4 }) {
          Text('⚠').fontSize(26).fontColor('#FFFFFF')
          Text('删除后不可恢复').fontSize(10).fontColor('#FFEBEE')
        }.width('100%').padding({ top: 16, bottom: 16 })
        .backgroundColor('#E53935')
        .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)

        Column({ space: 14 }) {
          Text('确认删除该装备收藏?').fontSize(15).fontColor('#263238')
            .fontWeight(FontWeight.Medium)

          Row({ space: 10 }) {
            Text('取消').fontSize(13).fontColor('#607D8B')
              .layoutWeight(1).textAlign(TextAlign.Center).padding(10)
              .backgroundColor('#ECEFF1').borderRadius(20)
              .onClick(() => { this.showDeleteModal = false })

            Text('删除').fontSize(13).fontColor('#FFFFFF')
              .layoutWeight(1).textAlign(TextAlign.Center).padding(10)
              .backgroundColor('#E53935').borderRadius(20)
              .onClick(() => { this.showDeleteModal = false })
          }.width('100%').alignItems(VerticalAlign.Center)
        }.width('100%').padding(16)
      }.width('72%').borderRadius(12).backgroundColor('#FFFFFF')
      .alignItems(HorizontalAlign.Center)
      .onClick((event: ClickEvent) => { event.stopPropagation() })
    }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
    .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)
    .onClick(() => { this.showDeleteModal = false })
  }

删除确认弹框的定位方式与其他弹框截然不同。外层Column使用justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)将内容居中显示,而非底部弹出。弹框宽度为72%,顶部红色区域以警示图标"⚠"和"删除后不可恢复"提示文字构成警示头部,底部白色区域包含确认文字和取消/删除双按钮。两个按钮各占layoutWeight(1)的等宽空间,取消按钮使用灰色背景#ECEFF1,删除按钮使用红色背景#E53935,通过颜色对比引导用户谨慎操作。整个弹框宽度仅72%,比底部弹框的全宽更紧凑,符合居中确认对话框的视觉惯例。

弹框系统:商品详情右侧滑出面板

详情面板的完整结构

商品详情弹框采用右侧滑出的全高面板设计,从右侧80%宽度滑入,包含大色块头图区、可滚动内容区和底部购买按钮三个核心区块。

  @Builder
  detailModal() {
    Column() {
      Column({ space: 12 }) {
        Column({ space: 6 }) {
          Text(this.selName().substring(0, 1)).fontSize(44).fontColor('#FFFFFF')
            .fontWeight(FontWeight.Bold)
          Text(this.selTag()).fontSize(11).fontColor('#E8EAF6')
            .padding({ left: 10, right: 10, top: 3, bottom: 3 })
            .backgroundColor('rgba(255,255,255,0.25)').borderRadius(10)
        }.width('100%').height(170)
        .backgroundColor('#2962FF')
        .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)

        Scroll() {
          Column({ space: 12 }) {
            Text(this.selName()).fontSize(17).fontColor('#263238')
              .fontWeight(FontWeight.Bold).maxLines(2)
            Text(this.selDesc()).fontSize(12).fontColor('#90A4AE')

            Row({ space: 8 }) {
              Text(this.selStars()).fontSize(16).fontColor('#FFB300')
              Text(this.selScore() + ' 分').fontSize(12).fontColor('#FFB300')
              Column().layoutWeight(1)
              Text(this.selNum() + ' 人加购').fontSize(10).fontColor('#B0BEC5')
            }.width('100%').alignItems(VerticalAlign.Center)

            Row({ space: 6 }) {
              Text('¥').fontSize(14).fontColor('#E53935')
              Text(Math.floor(this.selPrice()) + '').fontSize(28).fontColor('#E53935')
                .fontWeight(FontWeight.Bold)
              Text('分期免息').fontSize(9).fontColor('#E53935')
                .padding({ left: 5, right: 5, top: 2, bottom: 2 })
                .border({ width: 1, color: '#E53935' }).borderRadius(4)
            }.alignItems(VerticalAlign.Bottom)

            Column({ space: 6 }) {
              Text('好评率 ' + this.selScore() + '%').fontSize(11).fontColor('#00B8D4')
              Row() {
                Column().width(this.selScore() + '%').height(7)
                  .backgroundColor('#00B8D4').borderRadius(4)
              }.width('100%').height(7).backgroundColor('#E8EAF6').borderRadius(4)
            }.width('100%').alignItems(HorizontalAlign.Start)

            Text('适配车型').fontSize(12).fontColor('#90A4AE')
            Flex({ wrap: FlexWrap.Wrap }) {
              ForEach(this.carChips, (car: string) => {
                Text(car).fontSize(11).fontColor('#2962FF')
                  .padding({ left: 10, right: 10, top: 5, bottom: 5 })
                  .backgroundColor('#E8EAF6').borderRadius(12)
                  .margin({ right: 8, bottom: 8 })
              }, (car: string) => car)
            }.width('100%')

            Text('取消收藏').fontSize(11).fontColor('#B0BEC5')
              .padding(8)
              .onClick(() => {
                this.showDetailModal = false
                this.showDeleteModal = true
              })
          }.width('100%').padding(16)
          .alignItems(HorizontalAlign.Start)
        }.layoutWeight(1).width('100%')

        Text('立即购买').fontSize(15).fontColor('#FFFFFF')
          .width('100%').textAlign(TextAlign.Center).padding(14)
          .backgroundColor('#2962FF').borderRadius(24)
          .margin({ bottom: 16 })
          .scale({ x: 1.05, y: 1.05 })
          .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
          .onClick(() => { this.showDetailModal = false })
      }.width('80%').height('100%').backgroundColor('#FFFFFF')
      .alignItems(HorizontalAlign.Start)
      .onClick((event: ClickEvent) => { event.stopPropagation() })
    }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
    .justifyContent(FlexAlign.End).alignItems(HorizontalAlign.End)
    .onClick(() => { this.showDetailModal = false })
  }

详情面板的布局策略是通过外层Column的justifyContent(FlexAlign.End).alignItems(HorizontalAlign.End)将80%宽度的内层面板推到右侧,模拟从右侧滑入的效果。头图区使用170像素高度的极客蓝色块,内嵌商品首字大号白色文字和标签,形成强烈的品牌色视觉锚点。内容区使用Scroll包裹,支持长内容滚动,包含了商品名称、描述、五星评分(通过selStars()生成)、价格大字(28号红色粗体)、好评率进度条和适配车型Chip列表。"取消收藏"按钮的点击事件链式触发了两个状态变更:先关闭详情弹框,再打开删除确认弹框,实现了跨弹框的流程跳转。底部"立即购买"按钮应用了脉冲缩放动画,是整个面板中唯一的动态元素,引导用户完成购买转化。

页面装配与构建函数

Stack层叠与条件渲染

应用的build()函数使用Stack作为根容器,将主内容区和五个弹框层叠在一起,通过条件渲染控制弹框的显隐,实现了完整的页面装配。

  build() {
    Stack() {
      Column() {
        this.headerBuilder()
        Scroll() {
          Column() {
            if (this.currentTab === 0) {
              this.tab0()
            } else if (this.currentTab === 1) {
              this.tab1()
            } else if (this.currentTab === 2) {
              this.tab2()
            } else if (this.currentTab === 3) {
              this.tab3()
            } else if (this.currentTab === 4) {
              this.tab4()
            } else if (this.currentTab === 5) {
              this.tab5()
            } else {
              this.tab6()
            }
          }.width('100%')
        }.layoutWeight(1).width('100%')
        this.bottomBuilder()
      }.width('100%').height('100%')
      if (this.showAddModal) { this.addModal() }
      if (this.showPackModal) { this.packModal() }
      if (this.showShopModal) { this.shopModal() }
      if (this.showDeleteModal) { this.deleteModal() }
      if (this.showDetailModal) { this.detailModal() }
    }.width('100%').height('100%')
  }

页面装配的核心设计是Stack的层叠机制。底层是主内容区Column,包含headerBuilder、可滚动的内容区和bottomBuilder三个垂直排列的区域。内容区通过if-else if条件判断根据currentTab的值渲染对应的Tab Builder,layoutWeight(1)使内容区占据头部和底部之间的所有剩余空间。五个弹框作为Stack的上层,各自通过独立的布尔状态变量控制渲染——当状态为true时对应弹框Builder被调用并渲染在Stack上层,遮罩层覆盖底层内容区,实现模态效果。这种架构使得所有弹框共享同一个Stack容器,无需额外的弹框管理器,代码结构清晰且易于维护。

技术对比分析

技术维度 智驾商城Tab(tab0) 行车记录Tab(tab1) HUD抬显Tab(tab2) 智驾硬件Tab(tab3)
列表布局 行式列表(单列) Flex双列网格 横向Scroll滚动 行式列表(单列)
数据可视化 好评进度条 万像素柱状图 亮度柱状图 TOPS算力柱状图
统计卡样式 三格白色卡(脉冲) 三格深色卡(脉冲)
柱状图颜色 不适用 蓝/青双色交替 青/灰双色交替 蓝/青/红三色循环
动画元素 热度数字脉冲 夜视标签脉冲 峰值亮度脉冲 算力数值脉冲
卡片宽度 100% 48% 140px固定 100%
点击行为 打开详情弹框 打开详情弹框 打开详情弹框 无点击事件
数据条数 24条 12条 10条 10条
标签样式 浅蓝底蓝字 荧光青底白字 不适用 浅蓝底蓝字
进度条高度 6px 4px 不适用 5px
弹框维度 发布装备(addModal) 升级套餐(packModal) 预约安装(shopModal) 删除确认(deleteModal) 商品详情(detailModal)
弹出位置 底部弹出 底部弹出 底部弹出 居中显示 右侧滑出
宽度 100% 100% 100% 72% 80%
Chip选择器 品类+车型(双组) 方案(单组) 门店+时段(双组) 车型(只读展示)
步进器 数量(1-6件) 工位(1-2位)
状态变量 chipA/chipB/addDesc chipA/stepNum chipA/chipB/stepNumB selectedItem
确认按钮颜色 极客蓝 荧光青 极客蓝 警示红 极客蓝(脉冲)
关闭方式 遮罩点击/叉号 遮罩点击/叉号 遮罩点击/叉号 遮罩点击/取消按钮 遮罩点击
最大高度 80% 80% 80% 自适应 100%
事件冒泡阻止

安装DevEco Studio程序

在这里插入图片描述
选择目标安装目录:

在这里插入图片描述
设置环境变量,但是需要重启一下:

在这里插入图片描述
新建一个空白模板:

在这里插入图片描述
设置API为24的模板项目:
在这里插入图片描述
初始化项目,自动下载相关依赖:

在这里插入图片描述


完整代码:

// ============================================================
// 智能驾驶升级馆 —— 汽车之家 App 风格的智驾硬件升级场景
// 单文件 ArkTS 页面:7 Tab + 5 弹框
// 配色:极客蓝 #2962FF / 深空灰 #263238 / 荧光青 #00B8D4
//       浅蓝底 #E8EAF6 / 警示红 #E53935
// ============================================================

interface GoodsItem {
  name: string
  price: number
  desc: string
  score: number
  num: number
  tag: string
  hot: string
}

@Entry
@Component
struct Index {
  @State currentTab: number = 0
  @State showAddModal: boolean = false
  @State showPackModal: boolean = false
  @State showShopModal: boolean = false
  @State showDeleteModal: boolean = false
  @State showDetailModal: boolean = false
  @State selectedItem: GoodsItem | null = null
  @State chipA: number = 0
  @State chipB: number = 0
  @State stepNum: number = 1
  @State stepNumB: number = 1
  @State addDesc: string = ''

  // ---------- 顶部 Tab ----------
  private tabs: string[] = ['智驾商城', '行车记录', 'HUD抬显', '智驾硬件', '用车社区', '消息', '我的']

  // ---------- 各类 chips ----------
  private kindChips: string[] = ['行车记录仪', 'HUD', '激光雷达', '域控制器', '传感器']
  private carChips: string[] = ['特斯拉', '蔚来', '小鹏', '理想', '比亚迪', '极氪']
  private packChips: string[] = ['基础辅助', '高阶智驾', '全栈智享', '旗舰领航']
  private packPrices: number[] = [3999, 12999, 25999, 39999]
  private shopChips: string[] = ['中关村店', '望京店', '亦庄店', '顺义店']
  private timeChips: string[] = ['上午', '下午', '晚间']
  private topicChips: string[] = ['城区领航实测', 'HUD改装日记', '记录仪夜视', '激光雷达解析', 'OTA体验', '续航分享', '高速NOA']

  // ---------- 统计卡数据 ----------
  private statLabels: string[] = ['在售装备', '本月安装', '好评率']
  private statNums: string[] = ['1,286', '3,452', '98.6%']
  private statColors: string[] = ['#2962FF', '#00B8D4', '#E53935']
  private myStatLabels: string[] = ['我的装备', 'OTA记录', '预约安装']
  private myStatNums: string[] = ['8', '23', '2']

  // ---------- 菜单 ----------
  private menuNames: string[] = ['我的订单', '我的收藏', '优惠券', '预约安装', '客服中心', '设置']

  // ---------- 智驾商城 24 条装备 ----------
  private mainList: GoodsItem[] = [
    {
      name: '70迈 4K 智能记录仪 Pro',
      price: 899,
      desc: '4K超清 · 语音控制 · 电子狗',
      score: 96,
      num: 460,
      tag: '热销',
      hot: '爆'
    },
    {
      name: '盯盯拍 Mini5 记录仪',
      price: 699,
      desc: '5K超清 · 语音抓拍 · 缩时录影',
      score: 94,
      num: 380,
      tag: '热销',
      hot: '荐'
    },
    {
      name: '海康威视 N6+ 记录仪',
      price: 749,
      desc: '2K星光 · 停车监控 · 语音声控',
      score: 92,
      num: 300,
      tag: '新品',
      hot: '新'
    },
    {
      name: '华为智选 70寸 HUD',
      price: 1699,
      desc: '70寸投影 · 自动亮度 · 导航投射',
      score: 95,
      num: 410,
      tag: '热销',
      hot: '爆'
    },
    {
      name: '路畅 6G 毫米波雷达',
      price: 2599,
      desc: '前向感知 · 盲区补全 · 全天候',
      score: 90,
      num: 260,
      tag: '新品',
      hot: '荐'
    },
    {
      name: '禾赛 XT32 激光雷达',
      price: 4999,
      desc: '32线 · 200米量程 · 车规级',
      score: 97,
      num: 480,
      tag: '旗舰',
      hot: '爆'
    },
    {
      name: '速腾 M1 激光雷达',
      price: 4599,
      desc: 'MEMS · 点云成像 · 150米',
      score: 95,
      num: 440,
      tag: '旗舰',
      hot: '爆'
    },
    {
      name: '地平线 征程5 域控制器',
      price: 8999,
      desc: '128TOPS · 深度学习加速',
      score: 93,
      num: 330,
      tag: '高算力',
      hot: '荐'
    },
    {
      name: '黑芝麻 A1000 域控制器',
      price: 9999,
      desc: '116TOPS · 车规级双芯',
      score: 92,
      num: 290,
      tag: '高算力',
      hot: '荐'
    },
    {
      name: 'P卡 4G GPS定位器',
      price: 199,
      desc: '4G全网通 · 远程监听 · 电子围栏',
      score: 88,
      num: 120,
      tag: '特价',
      hot: '惠'
    },
    {
      name: '70迈 智能后视镜 X200',
      price: 1299,
      desc: '10寸流媒体 · 后路影像',
      score: 91,
      num: 240,
      tag: '热销',
      hot: '荐'
    },
    {
      name: '凌度 BL990 后视镜记录仪',
      price: 599,
      desc: '双镜头 · 倒车影像 · 循环录',
      score: 87,
      num: 140,
      tag: '特价',
      hot: '惠'
    },
    {
      name: '任我游 流媒体后视镜',
      price: 899,
      desc: '9.6寸 · 防眩光 · 广角后摄',
      score: 89,
      num: 160,
      tag: '热销',
      hot: '荐'
    },
    {
      name: '铁将军 太阳能胎压监测',
      price: 349,
      desc: '太阳能 · 六感应 · 实时显示',
      score: 90,
      num: 180,
      tag: '特价',
      hot: '惠'
    },
    {
      name: '伟力通 胎压监测 T6',
      price: 299,
      desc: '内置传感 · 高温报警',
      score: 91,
      num: 200,
      tag: '热销',
      hot: '荐'
    },
    {
      name: '小米 车载智慧屏 Pro',
      price: 1099,
      desc: '16寸 · 米家互联 · 语音助手',
      score: 92,
      num: 220,
      tag: '新品',
      hot: '新'
    },
    {
      name: 'bee电 智能胎压充电帽',
      price: 129,
      desc: '实时胎压 · USB供电 · 四只装',
      score: 85,
      num: 90,
      tag: '特价',
      hot: '惠'
    },
    {
      name: '科盛 ADAS 预警仪',
      price: 1599,
      desc: '防碰撞 · 车道偏离预警',
      score: 88,
      num: 170,
      tag: '新品',
      hot: '荐'
    },
    {
      name: '中恒 D5 OBD 行车电脑',
      price: 459,
      desc: 'OBD即插 · 故障检测 · 仪表数据',
      score: 86,
      num: 110,
      tag: '特价',
      hot: '惠'
    },
    {
      name: '纽曼 车载空气净化器',
      price: 399,
      desc: 'HEPA滤网 · 负离子 · 静音',
      score: 84,
      num: 80,
      tag: '特价',
      hot: '惠'
    },
    {
      name: '飞利浦 激光大灯总成',
      price: 2899,
      desc: '激光大灯 · 自适应远光',
      score: 93,
      num: 210,
      tag: '旗舰',
      hot: '荐'
    },
    {
      name: '海拉 智能大灯控制器',
      price: 1899,
      desc: 'ADB防眩 · 分区照明',
      score: 91,
      num: 190,
      tag: '高配',
      hot: '新'
    },
    {
      name: 'Mobileye 6L 辅助驾驶盒',
      price: 12999,
      desc: '单目视觉 · 预警算法大师',
      score: 96,
      num: 350,
      tag: '旗舰',
      hot: '爆'
    },
    {
      name: '经纬恒润 L2 辅助套件',
      price: 6999,
      desc: 'ACC · LKA · 一键升级',
      score: 94,
      num: 320,
      tag: '高算力',
      hot: '爆'
    }
  ]

  // ---------- 行车记录 12 条(num = 万像素) ----------
  private dashList: GoodsItem[] = [
    { name: '70迈 Pro', price: 899, desc: '4K超清 · 语音声控', score: 96, num: 480, tag: '夜视', hot: '爆' },
    { name: '盯盯拍 Mini5', price: 699, desc: '5K画质 · 缩时录影', score: 94, num: 460, tag: '夜视', hot: '荐' },
    { name: '海康 N6+', price: 749, desc: '2K星光 · 停车监控', score: 92, num: 380, tag: '星光', hot: '荐' },
    { name: '凌度 BL990', price: 599, desc: '双镜头 · 循环录制', score: 87, num: 260, tag: '夜视', hot: '惠' },
    { name: '任我游 后视镜', price: 899, desc: '流媒体 · 防眩光', score: 89, num: 300, tag: '夜视', hot: '荐' },
    { name: '超级喵 夜枭', price: 1099, desc: 'SONY传感器 · 暗光王', score: 93, num: 440, tag: '星光', hot: '爆' },
    { name: 'PAPAGO N291', price: 659, desc: '149度广角 · GPS', score: 90, num: 340, tag: '夜视', hot: '荐' },
    { name: '70迈 M300', price: 399, desc: '隐藏式 · 2K小机身', score: 88, num: 250, tag: '特价', hot: '惠' },
    { name: '黑剑 H9', price: 1299, desc: '4K · 后摄双录', score: 91, num: 420, tag: '夜视', hot: '荐' },
    { name: '神眸 X5', price: 549, desc: '语音抓拍 · 紧急锁定', score: 86, num: 220, tag: '星光', hot: '惠' },
    { name: '丁威特 D680', price: 469, desc: '迷你机身 · 电容供电', score: 85, num: 180, tag: '特价', hot: '惠' },
    { name: '捷渡 D160', price: 559, desc: '倒车影像 · 三镜头', score: 84, num: 150, tag: '夜视', hot: '惠' }
  ]

  // ---------- HUD 抬显 10 条(num = 流明) ----------
  private hudList: GoodsItem[] = [
    { name: '华为智选 HUD', price: 1699, desc: '70寸投影 · 自动亮度', score: 95, num: 480, tag: '热销', hot: '爆' },
    { name: '车萝卜 Plus', price: 999, desc: '6寸反射 · 导航投射', score: 92, num: 380, tag: '热销', hot: '荐' },
    { name: '百路达 M3', price: 899, desc: '曲面成像 · 语音控制', score: 90, num: 340, tag: '新品', hot: '新' },
    { name: '任我游 H7', price: 799, desc: '5.5寸 · 胎压同屏', score: 89, num: 300, tag: '特价', hot: '惠' },
    { name: '纽曼 H5', price: 599, desc: 'OBD数据 · 速显抬头', score: 86, num: 220, tag: '特价', hot: '惠' },
    { name: '中恒 H8', price: 699, desc: '高清反射 · 故障码', score: 88, num: 260, tag: '新品', hot: '新' },
    { name: '途乐 H6', price: 649, desc: '星空屏 · 亮度自适应', score: 87, num: 240, tag: '热销', hot: '荐' },
    { name: '车视杰 H9', price: 1199, desc: '9寸大屏 · 双模投射', score: 91, num: 400, tag: '旗舰', hot: '爆' },
    { name: '联想 H3', price: 749, desc: '轻巧机身 · GPS速显', score: 85, num: 200, tag: '特价', hot: '惠' },
    { name: '极目 P1', price: 1399, desc: 'AR实景 · 4K投影', score: 93, num: 420, tag: '旗舰', hot: '爆' }
  ]

  // ---------- 智驾硬件 10 条(num = 算力 TOPS) ----------
  private hwList: GoodsItem[] = [
    { name: '英伟达 Orin-X 域控', price: 15999, desc: '254TOPS · 双芯级联', score: 98, num: 254, tag: '旗舰', hot: '爆' },
    { name: '地平线 征程5', price: 8999, desc: '128TOPS · 深度学习', score: 90, num: 128, tag: '高算力', hot: '荐' },
    { name: '黑芝麻 A1000', price: 9999, desc: '116TOPS · 车规双芯', score: 86, num: 116, tag: '高算力', hot: '荐' },
    { name: '华为 MDC 610', price: 29999, desc: '200TOPS · 全栈算法', score: 95, num: 200, tag: '旗舰', hot: '爆' },
    { name: 'Mobileye EyeQ6', price: 12999, desc: '等效算力 · 视觉方案', score: 82, num: 90, tag: '新品', hot: '新' },
    { name: '地平线 J6P', price: 19999, desc: '560TOPS · 城区领航', score: 96, num: 460, tag: '旗舰', hot: '爆' },
    { name: '黑芝麻 A2000', price: 21999, desc: '300TOPS · 舱驾一体', score: 93, num: 300, tag: '新品', hot: '新' },
    { name: '德赛西威 IVA9', price: 16999, desc: '180TOPS · 中算力平台', score: 88, num: 180, tag: '高算力', hot: '荐' },
    { name: '经纬恒润 ADC', price: 13999, desc: '150TOPS · L2进阶', score: 84, num: 150, tag: '热销', hot: '荐' },
    { name: '禾赛 XT32 雷达', price: 4999, desc: '32线 · 点云成像', score: 92, num: 120, tag: '传感器', hot: '爆' }
  ]

  // ---------- 用车社区 10 条帖子(num = 点赞数) ----------
  private postList: GoodsItem[] = [
    { name: '京A老司机', price: 0, desc: '城区领航首月体验:路口通行率高,无保护左转稳定,雨夜会自动降级提醒接管。', score: 98, num: 486, tag: '城区领航', hot: '精' },
    { name: '沪上鹏友', price: 0, desc: 'HUD改装日记:走线藏饰条,成像在玻璃外侧,白天也清晰,强烈推荐反射式。', score: 92, num: 352, tag: 'HUD改装', hot: '热' },
    { name: '深南夜猫', price: 0, desc: '记录仪夜视横评:三款机型地库对比,星光夜视效果确实碾压普通传感器。', score: 90, num: 298, tag: '记录仪夜视', hot: '荐' },
    { name: '杭州车主陈', price: 0, desc: '激光雷达清洗很重要,泥点一多感知立刻变保守,记得常擦雷达罩。', score: 86, num: 210, tag: '激光雷达', hot: '' },
    { name: '蓉城特斯拉', price: 0, desc: 'OTA 后自动变道更果断了,匝道汇入依旧偏保守,整体像老司机带实习生。', score: 94, num: 410, tag: 'OTA体验', hot: '精' },
    { name: '广州奶爸', price: 0, desc: '长途高速全程领航,中途只接管一次,到家腰不酸,智驾真是带娃神器。', score: 96, num: 465, tag: '高速NOA', hot: '热' },
    { name: '西安油电双持', price: 0, desc: '续航分享:开智驾比脚踩省半个电,高速能耗曲线明显更平顺。', score: 88, num: 175, tag: '续航分享', hot: '' },
    { name: '青岛湾流', price: 0, desc: '域控升级作业:换大算力盒子后车道保持丝滑不少,值得为算力买单。', score: 91, num: 260, tag: 'HUD改装', hot: '荐' },
    { name: '长沙米粉队长', price: 0, desc: '暴雨天智驾会提前提示降级,逻辑透明不吓人,这点好评。', score: 87, num: 190, tag: 'OTA体验', hot: '' },
    { name: '哈尔滨冰哥', price: 0, desc: '零下二十度记录仪电池报警,换电容版机器后冬季再没断录。', score: 85, num: 130, tag: '记录仪夜视', hot: '' }
  ]

  // ---------- 消息 12 条(num = 未读条数) ----------
  private msgList: GoodsItem[] = [
    {
      name: 'OTA 升级助手',
      price: 0,
      desc: '您的爱车有新版本推送,新增城区领航功能',
      score: 0,
      num: 3,
      tag: '10:24',
      hot: '系'
    },
    {
      name: '安装顾问 小李',
      price: 0,
      desc: '您预约的 HUD 安装工位已确认,请准时到店',
      score: 0,
      num: 1,
      tag: '09:47',
      hot: '服'
    },
    {
      name: '70迈官方旗舰店',
      price: 0,
      desc: '您收藏的记录仪降价 120 元,限时三天',
      score: 0,
      num: 2,
      tag: '昨天',
      hot: '店'
    },
    {
      name: '用车社区',
      price: 0,
      desc: '您发布的 HUD 改装日记被推荐到首页',
      score: 0,
      num: 5,
      tag: '昨天',
      hot: '社'
    },
    {
      name: '智驾评测君',
      price: 0,
      desc: '本周激光雷达横评已更新,点击查看',
      score: 0,
      num: 0,
      tag: '周一',
      hot: '评'
    },
    {
      name: '系统通知',
      price: 0,
      desc: '账号安全等级提升完成,建议开启双重验证',
      score: 0,
      num: 0,
      tag: '周一',
      hot: '系'
    },
    {
      name: '同城车友会',
      price: 0,
      desc: '周六亦庄店车友聚会开始报名,前 20 名送洗车',
      score: 0,
      num: 4,
      tag: '周二',
      hot: '社'
    },
    {
      name: '保险管家',
      price: 0,
      desc: '您的车险还有 30 天到期,可在线比价续保',
      score: 0,
      num: 0,
      tag: '周三',
      hot: '保'
    },
    {
      name: '预约中心',
      price: 0,
      desc: '施工完成回访:请为本次安装服务打分',
      score: 0,
      num: 1,
      tag: '周四',
      hot: '服'
    },
    {
      name: '配件到货提醒',
      price: 0,
      desc: '缺货的胎压传感器已补货,库存紧张',
      score: 0,
      num: 2,
      tag: '周四',
      hot: '店'
    },
    {
      name: '夜间行车小助手',
      price: 0,
      desc: '本周夜间事故率下降 12%,感谢规范用灯',
      score: 0,
      num: 0,
      tag: '周五',
      hot: '评'
    },
    {
      name: '会员权益中心',
      price: 0,
      desc: '您的智驾会员积分即将过期,可兑换保养券',
      score: 0,
      num: 6,
      tag: '周五',
      hot: '保'
    }
  ]

  // ---------- 选中项读取辅助 ----------
  selName(): string {
    if (this.selectedItem !== null) { return this.selectedItem.name }
    return ''
  }
  selPrice(): number {
    if (this.selectedItem !== null) { return this.selectedItem.price }
    return 0
  }
  selDesc(): string {
    if (this.selectedItem !== null) { return this.selectedItem.desc }
    return ''
  }
  selScore(): number {
    if (this.selectedItem !== null) { return this.selectedItem.score }
    return 0
  }
  selNum(): number {
    if (this.selectedItem !== null) { return this.selectedItem.num }
    return 0
  }
  selTag(): string {
    if (this.selectedItem !== null) { return this.selectedItem.tag }
    return ''
  }
  selStars(): string {
    if (this.selectedItem === null) { return '' }
    let filled: number = Math.floor(this.selectedItem.score / 20)
    let res: string = ''
    for (let i = 0; i < 5; i++) {
      if (i < filled) { res += '★' } else { res += '☆' }
    }
    return res
  }
  maxNumOf(list: GoodsItem[]): number {
    let m: number = 1
    for (let i = 0; i < list.length; i++) {
      if (list[i].num > m) { m = list[i].num }
    }
    return m
  }
  packTotal(): number {
    return Math.floor(this.packPrices[this.chipA] * this.stepNum)
  }

  // ================= 静态头部(无任何动画) =================
  @Builder
  headerBuilder() {
    Column({ space: 10 }) {
      // 顶部行:标题 + OTA 图标 + 消息图标数字角标
      Row() {
        Column() {
          Text('智能驾驶升级馆').fontSize(20).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
        }.alignItems(HorizontalAlign.Start)

        Column().layoutWeight(1)

        Row({ space: 12 }) {
          Text('📡').fontSize(22)
          Stack({ alignContent: Alignment.TopEnd }) {
            Text('🔔').fontSize(22)
            Text('6').fontSize(8).fontColor('#FFFFFF').backgroundColor('#E53935')
              .borderRadius(7).padding({ left: 4, right: 4, top: 1, bottom: 1 })
              .constraintSize({ minWidth: 14 })
          }.width(30).height(30)
        }.alignItems(VerticalAlign.Center)
      }.width('100%').alignItems(VerticalAlign.Center)

      // 搜索胶囊
      Row({ space: 8 }) {
        Text('🛰').fontSize(16)
        Text('搜智驾硬件/记录仪').fontSize(13).fontColor('#90A4AE')
        Column().layoutWeight(1)
        Text('🔍').fontSize(14)
      }.width('100%').height(36).padding({ left: 14, right: 14 })
      .backgroundColor('#E8EAF6').borderRadius(18)
      .alignItems(VerticalAlign.Center)

      // 信息条
      Row({ space: 6 }) {
        Text('🚀').fontSize(13)
        Text('本周 OTA 推送:新增城区领航 · 覆盖 38 万台车')
          .fontSize(11).fontColor('#FFFFFF')
      }.width('100%').padding({ left: 12, right: 12, top: 6, bottom: 6 })
      .backgroundColor('rgba(255,255,255,0.2)').borderRadius(8)
      .alignItems(VerticalAlign.Center)
    }.width('100%').padding({ left: 14, right: 14, top: 10, bottom: 10 })
    .backgroundColor('#2962FF').alignItems(HorizontalAlign.Start)
  }

  // ================= 底部 Tab 栏 =================
  @Builder
  bottomBuilder() {
    Row() {
      ForEach(this.tabs, (tab: string, index: number) => {
        Column({ space: 2 }) {
          Text(tab).fontSize(10).fontColor(this.currentTab === index ? '#2962FF' : '#999999')
          Column().width(18).height(3).borderRadius(2)
            .backgroundColor(this.currentTab === index ? '#2962FF' : '#FFFFFF00')
        }.onClick(() => { this.currentTab = index })
      }, (tab: string) => tab)
    }.width('100%').height(56).backgroundColor('#FFFFFF')
    .justifyContent(FlexAlign.SpaceAround).alignItems(VerticalAlign.Center)
  }

  // ================= Tab1 智驾商城 =================
  @Builder
  tab0() {
    Column({ space: 12 }) {
      // 三格统计卡
      Row({ space: 8 }) {
        ForEach(this.statLabels, (label: string, index: number) => {
          Column({ space: 4 }) {
            Text(this.statNums[index]).fontSize(18)
              .fontColor(this.statColors[index]).fontWeight(FontWeight.Bold)
            Text(label).fontSize(11).fontColor('#90A4AE')
          }.layoutWeight(1).padding({ top: 10, bottom: 10 })
          .backgroundColor('#FFFFFF').borderRadius(10).alignItems(HorizontalAlign.Center)
          .scale(index === 2 ? { x: 1.05, y: 1.05 } : { x: 1, y: 1 })
          .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
        }, (label: string) => label)
      }.width('100%').alignItems(VerticalAlign.Center)

      // 快捷操作入口
      Row({ space: 8 }) {
        Text('📤 发布装备').fontSize(12).fontColor('#2962FF').padding(8)
          .backgroundColor('#E8EAF6').borderRadius(14)
          .onClick(() => { this.showAddModal = true })
        Text('⚡ 升级套餐').fontSize(12).fontColor('#00B8D4').padding(8)
          .backgroundColor('#E0F7FA').borderRadius(14)
          .onClick(() => { this.showPackModal = true })
        Text('🔧 预约安装').fontSize(12).fontColor('#E53935').padding(8)
          .backgroundColor('#FDECEA').borderRadius(14)
          .onClick(() => { this.showShopModal = true })
      }.width('100%').justifyContent(FlexAlign.SpaceBetween)

      // 装备列表标题
      Row() {
        Text('智驾装备精选').fontSize(15).fontColor('#263238').fontWeight(FontWeight.Bold)
        Column().layoutWeight(1)
        Text('共 24 件').fontSize(11).fontColor('#90A4AE')
      }.width('100%').alignItems(VerticalAlign.Center)

      // 24 条装备行式列表
      ForEach(this.mainList, (item: GoodsItem, index: number) => {
        Row({ space: 10 }) {
          // 左侧首字圆形图标
          Column() {
            Text(item.name.substring(0, 1)).fontSize(16).fontColor('#FFFFFF')
              .fontWeight(FontWeight.Bold)
          }.width(46).height(46).borderRadius(23)
          .backgroundColor(index % 3 === 0 ? '#2962FF' : (index % 3 === 1 ? '#00B8D4' : '#263238'))
          .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)

          // 中部信息
          Column({ space: 5 }) {
            Text(item.name).fontSize(14).fontColor('#263238')
              .fontWeight(FontWeight.Medium).maxLines(1)
            Text(item.desc).fontSize(11).fontColor('#90A4AE').maxLines(1)
            // 好评进度条
            Row() {
              Column().width(item.score + '%').height(6)
                .backgroundColor('#00B8D4').borderRadius(3)
            }.width('100%').height(6).backgroundColor('#E8EAF6').borderRadius(3)

            Row({ space: 6 }) {
              Text('¥' + Math.floor(item.price)).fontSize(15).fontColor('#E53935')
                .fontWeight(FontWeight.Bold)
              Text(item.tag).fontSize(9).fontColor('#2962FF')
                .padding({ left: 5, right: 5, top: 2, bottom: 2 })
                .backgroundColor('#E8EAF6').borderRadius(4)
            }.alignItems(VerticalAlign.Center)
          }.layoutWeight(1).alignItems(HorizontalAlign.Start)

          // 右侧热度
          Column({ space: 6 }) {
            Text(item.hot).fontSize(12).fontColor('#E53935').fontWeight(FontWeight.Bold)
              .scale({ x: 1.05, y: 1.05 })
              .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            Text(item.num + '').fontSize(11).fontColor('#263238')
            Text('热度').fontSize(9).fontColor('#B0BEC5')
          }.alignItems(HorizontalAlign.Center)
        }.width('100%').padding(12).backgroundColor('#FFFFFF').borderRadius(10)
        .alignItems(VerticalAlign.Center)
        .onClick(() => {
          this.selectedItem = item
          this.showDetailModal = true
        })
      }, (item: GoodsItem) => item.name)
    }.width('100%').padding(12).alignItems(HorizontalAlign.Start)
  }

  // ================= Tab2 行车记录 =================
  @Builder
  tab1() {
    Column({ space: 12 }) {
      // 像素柱状图
      Text('万像素像素量分布').fontSize(15).fontColor('#263238').fontWeight(FontWeight.Bold)
      Row({ space: 6 }) {
        ForEach(this.dashList, (item: GoodsItem, index: number) => {
          Column() {
            Column()
              .width(14)
              .height(item.num / this.maxNumOf(this.dashList) * 120)
              .backgroundColor(index % 2 === 0 ? '#2962FF' : '#00B8D4')
              .borderRadius({ topLeft: 4, topRight: 4 })
          }.alignItems(HorizontalAlign.Center)
        }, (item: GoodsItem) => item.name)
      }.width('100%').height(130).backgroundColor('#FFFFFF').borderRadius(10)
      .padding({ left: 8, right: 8 })
      .justifyContent(FlexAlign.SpaceBetween)

      Row() {
        Text('● 4K 级').fontSize(10).fontColor('#2962FF')
        Column().layoutWeight(1)
        Text('● 2K 级').fontSize(10).fontColor('#00B8D4')
      }.width('100%')

      // 12 条记录仪双列网格
      Text('记录仪机型一览').fontSize(15).fontColor('#263238').fontWeight(FontWeight.Bold)
      Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
        ForEach(this.dashList, (item: GoodsItem, index: number) => {
          Column({ space: 6 }) {
            Row() {
              Text(item.name).fontSize(13).fontColor('#263238')
                .fontWeight(FontWeight.Medium).maxLines(1)
              Column().layoutWeight(1)
              Text(item.num + '万').fontSize(12).fontColor('#2962FF')
                .fontWeight(FontWeight.Bold)
            }.width('100%').alignItems(VerticalAlign.Center)

            Text(item.desc).fontSize(10).fontColor('#90A4AE').maxLines(1)

            Row() {
              Text('¥' + Math.floor(item.price)).fontSize(13).fontColor('#E53935')
                .fontWeight(FontWeight.Bold)
              Column().layoutWeight(1)
              // 夜视标签(脉冲)
              Text(item.tag).fontSize(9).fontColor('#FFFFFF').padding(3)
                .backgroundColor('#00B8D4').borderRadius(4)
                .scale({ x: 1.05, y: 1.05 })
                .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            }.width('100%').alignItems(VerticalAlign.Center)

            Row() {
              Column().width(item.score + '%').height(4)
                .backgroundColor('#2962FF').borderRadius(2)
            }.width('100%').height(4).backgroundColor('#E8EAF6').borderRadius(2)
          }.width('48%').padding(10).backgroundColor('#FFFFFF').borderRadius(10)
          .alignItems(HorizontalAlign.Start)
          .margin({ bottom: 10 })
          .onClick(() => {
            this.selectedItem = item
            this.showDetailModal = true
          })
        }, (item: GoodsItem) => item.name)
      }.width('100%')
    }.width('100%').padding(12).alignItems(HorizontalAlign.Start)
  }

  // ================= Tab3 HUD 抬显 =================
  @Builder
  tab2() {
    Column({ space: 12 }) {
      // 深色统计卡
      Row({ space: 8 }) {
        Column({ space: 4 }) {
          Text('70寸').fontSize(20).fontColor('#00B8D4').fontWeight(FontWeight.Bold)
          Text('最大投影尺寸').fontSize(10).fontColor('#B0BEC5')
        }.layoutWeight(1).alignItems(HorizontalAlign.Center)

        Column().width(1).height(32).backgroundColor('#455A64')

        Column({ space: 4 }) {
          Text('12000').fontSize(20).fontColor('#00B8D4').fontWeight(FontWeight.Bold)
            .scale({ x: 1.05, y: 1.05 })
            .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
          Text('峰值亮度 cd/m2').fontSize(10).fontColor('#B0BEC5')
        }.layoutWeight(1).alignItems(HorizontalAlign.Center)

        Column().width(1).height(32).backgroundColor('#455A64')

        Column({ space: 4 }) {
          Text('0.1s').fontSize(20).fontColor('#00B8D4').fontWeight(FontWeight.Bold)
          Text('数据刷新延迟').fontSize(10).fontColor('#B0BEC5')
        }.layoutWeight(1).alignItems(HorizontalAlign.Center)
      }.width('100%').padding({ top: 14, bottom: 14 })
      .backgroundColor('#263238').borderRadius(12)
      .alignItems(VerticalAlign.Center)

      // 亮度柱状图(num = 流明)
      Text('机型亮度对比').fontSize(15).fontColor('#263238').fontWeight(FontWeight.Bold)
      Row({ space: 8 }) {
        ForEach(this.hudList, (item: GoodsItem, index: number) => {
          Column() {
            Column()
              .width(16)
              .height(item.num / this.maxNumOf(this.hudList) * 120)
              .backgroundColor(index % 2 === 0 ? '#00B8D4' : '#263238')
              .borderRadius({ topLeft: 4, topRight: 4 })
          }.alignItems(HorizontalAlign.Center)
        }, (item: GoodsItem) => item.name)
      }.width('100%').height(130).backgroundColor('#FFFFFF').borderRadius(10)
      .padding({ left: 8, right: 8 })
      .justifyContent(FlexAlign.SpaceBetween)

      // 横向滚动 HUD 卡
      Row() {
        Text('横向滑动挑选 HUD').fontSize(15).fontColor('#263238').fontWeight(FontWeight.Bold)
        Column().layoutWeight(1)
        Text('10 款').fontSize(11).fontColor('#90A4AE')
      }.width('100%').alignItems(VerticalAlign.Center)

      Scroll() {
        Row({ space: 12 }) {
          ForEach(this.hudList, (item: GoodsItem, index: number) => {
            Column({ space: 8 }) {
              // 投影尺寸大字
              Column({ space: 4 }) {
                Text(item.num + '寸').fontSize(26).fontColor('#FFFFFF')
                  .fontWeight(FontWeight.Bold)
                Text(item.name).fontSize(11).fontColor('#E0F7FA')
              }.width('100%').height(90).justifyContent(FlexAlign.Center)
              .backgroundColor(index % 2 === 0 ? '#2962FF' : '#263238')
              .borderRadius(10).alignItems(HorizontalAlign.Center)

              Text('¥' + Math.floor(item.price)).fontSize(14).fontColor('#E53935')
                .fontWeight(FontWeight.Bold)
              Text(item.desc).fontSize(10).fontColor('#90A4AE').maxLines(1)
              Text('查看详情').fontSize(10).fontColor('#FFFFFF').padding({
                left: 14, right: 14, top: 5, bottom: 5
              }).backgroundColor('#00B8D4').borderRadius(12)
            }.width(140).padding(10).backgroundColor('#FFFFFF').borderRadius(12)
            .alignItems(HorizontalAlign.Center)
            .onClick(() => {
              this.selectedItem = item
              this.showDetailModal = true
            })
          }, (item: GoodsItem) => item.name)
        }.padding({ left: 12, right: 12 })
      }.scrollable(ScrollDirection.Horizontal).width('100%')
    }.width('100%').padding(12).alignItems(HorizontalAlign.Start)
  }

  // ================= Tab4 智驾硬件 =================
  @Builder
  tab3() {
    Column({ space: 12 }) {
      // 算力柱状图(num = TOPS)
      Text('平台算力对比 TOPS').fontSize(15).fontColor('#263238').fontWeight(FontWeight.Bold)
      Row({ space: 8 }) {
        ForEach(this.hwList, (item: GoodsItem, index: number) => {
          Column() {
            Column()
              .width(16)
              .height(item.num / this.maxNumOf(this.hwList) * 120)
              .backgroundColor(index % 3 === 0 ? '#2962FF' : (index % 3 === 1 ? '#00B8D4' : '#E53935'))
              .borderRadius({ topLeft: 4, topRight: 4 })
          }.alignItems(HorizontalAlign.Center)
        }, (item: GoodsItem) => item.name)
      }.width('100%').height(130).backgroundColor('#FFFFFF').borderRadius(10)
      .padding({ left: 8, right: 8 })
      .justifyContent(FlexAlign.SpaceBetween)

      // 10 条硬件列表
      ForEach(this.hwList, (item: GoodsItem, index: number) => {
        Row({ space: 10 }) {
          // 左侧彩条
          Column().width(4).height(56).borderRadius(2)
            .backgroundColor(index % 3 === 0 ? '#2962FF' : (index % 3 === 1 ? '#00B8D4' : '#E53935'))

          Column({ space: 5 }) {
            Row({ space: 6 }) {
              Text(item.name).fontSize(14).fontColor('#263238')
                .fontWeight(FontWeight.Medium).maxLines(1)
              Text(item.tag).fontSize(9).fontColor('#2962FF')
                .padding({ left: 5, right: 5, top: 2, bottom: 2 })
                .backgroundColor('#E8EAF6').borderRadius(4)
            }.alignItems(VerticalAlign.Center)

            Text(item.desc).fontSize(11).fontColor('#90A4AE').maxLines(1)

            // OTA 进度条
            Row({ space: 6 }) {
              Row() {
                Column().width(item.score + '%').height(5)
                  .backgroundColor('#2962FF').borderRadius(3)
              }.layoutWeight(1).height(5).backgroundColor('#E8EAF6').borderRadius(3)

              Text('OTA ' + item.score + '%').fontSize(9).fontColor('#2962FF')
            }.width('100%').alignItems(VerticalAlign.Center)
          }.layoutWeight(1).alignItems(HorizontalAlign.Start)

          // 算力数值(脉冲)
          Column({ space: 3 }) {
            Text(item.num + '').fontSize(16).fontColor('#263238').fontWeight(FontWeight.Bold)
              .scale({ x: 1.05, y: 1.05 })
              .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            Text('TOPS').fontSize(9).fontColor('#90A4AE')
          }.alignItems(HorizontalAlign.Center)
        }.width('100%').padding(12).backgroundColor('#FFFFFF').borderRadius(10)
        .alignItems(VerticalAlign.Center)
      }, (item: GoodsItem) => item.name)
    }.width('100%').padding(12).alignItems(HorizontalAlign.Start)
  }

  // ================= Tab5 用车社区 =================
  @Builder
  tab4() {
    Column({ space: 12 }) {
      // 话题 chips
      Flex({ wrap: FlexWrap.Wrap }) {
        ForEach(this.topicChips, (topic: string, index: number) => {
          Text('# ' + topic).fontSize(11)
            .fontColor(index % 2 === 0 ? '#2962FF' : '#00B8D4')
            .padding({ left: 10, right: 10, top: 5, bottom: 5 })
            .backgroundColor('#E8EAF6').borderRadius(12)
            .margin({ right: 8, bottom: 8 })
        }, (topic: string) => topic)
      }.width('100%')

      // 10 条帖子
      ForEach(this.postList, (item: GoodsItem, index: number) => {
        Row({ space: 10 }) {
          // 圆形头像
          Column() {
            Text(item.name.substring(0, 1)).fontSize(16).fontColor('#FFFFFF')
              .fontWeight(FontWeight.Bold)
          }.width(42).height(42).borderRadius(21)
          .backgroundColor(index % 3 === 0 ? '#2962FF' : (index % 3 === 1 ? '#00B8D4' : '#E53935'))
          .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)

          Column({ space: 5 }) {
            Row({ space: 6 }) {
              Text(item.name).fontSize(13).fontColor('#263238')
                .fontWeight(FontWeight.Medium)
              Text('# ' + item.tag).fontSize(9).fontColor('#00B8D4')
            }.alignItems(VerticalAlign.Center)

            Text(item.desc).fontSize(11).fontColor('#607D8B').maxLines(2)

            Row({ space: 12 }) {
              // 点赞数字(脉冲)
              Row({ space: 4 }) {
                Text('👍').fontSize(12)
                Text(item.num + '').fontSize(11).fontColor('#E53935')
                  .fontWeight(FontWeight.Bold)
                  .scale({ x: 1.05, y: 1.05 })
                  .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
              }.alignItems(VerticalAlign.Center)

              Row({ space: 4 }) {
                Text('💬').fontSize(12)
                Text(Math.floor(item.num / 5) + '').fontSize(11).fontColor('#90A4AE')
              }.alignItems(VerticalAlign.Center)

              Column().layoutWeight(1)

              if (item.hot !== '') {
                Text(item.hot).fontSize(9).fontColor('#E53935')
                  .padding({ left: 5, right: 5, top: 2, bottom: 2 })
                  .border({ width: 1, color: '#E53935' }).borderRadius(4)
              }
            }.width('100%').alignItems(VerticalAlign.Center)
          }.layoutWeight(1).alignItems(HorizontalAlign.Start)
        }.width('100%').padding(12).backgroundColor('#FFFFFF').borderRadius(10)
        .alignItems(VerticalAlign.Top)
      }, (item: GoodsItem) => item.name)
    }.width('100%').padding(12).alignItems(HorizontalAlign.Start)
  }

  // ================= Tab6 消息 =================
  @Builder
  tab5() {
    Column({ space: 10 }) {
      Row() {
        Text('消息中心').fontSize(15).fontColor('#263238').fontWeight(FontWeight.Bold)
        Column().layoutWeight(1)
        Text('未读 24').fontSize(11).fontColor('#E53935')
      }.width('100%').alignItems(VerticalAlign.Center)

      // 12 条消息
      ForEach(this.msgList, (item: GoodsItem, index: number) => {
        Row({ space: 10 }) {
          // 圆形头像
          Stack({ alignContent: Alignment.TopEnd }) {
            Column() {
              Text(item.hot).fontSize(15).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
            }.width(44).height(44).borderRadius(22)
            .backgroundColor(index % 4 === 0 ? '#2962FF' : (index % 4 === 1 ? '#00B8D4' : (index % 4 === 2 ? '#263238' : '#E53935')))
            .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)

            if (item.num > 0) {
              // 未读红点(脉冲)
              Column().width(10).height(10).borderRadius(5).backgroundColor('#E53935')
                .scale({ x: 1.05, y: 1.05 })
                .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            }
          }.width(46).height(46)

          Column({ space: 4 }) {
            Row() {
              Text(item.name).fontSize(13).fontColor('#263238')
                .fontWeight(FontWeight.Medium).maxLines(1)
              Column().layoutWeight(1)
              Text(item.tag).fontSize(10).fontColor('#B0BEC5')
            }.width('100%').alignItems(VerticalAlign.Center)

            Row() {
              Text(item.desc).fontSize(11).fontColor('#90A4AE').maxLines(1)
              Column().layoutWeight(1)
              if (item.num > 0) {
                Text(item.num + '条').fontSize(9).fontColor('#FFFFFF')
                  .padding({ left: 5, right: 5, top: 1, bottom: 1 })
                  .backgroundColor('#E53935').borderRadius(8)
              }
            }.width('100%').alignItems(VerticalAlign.Center)
          }.layoutWeight(1).alignItems(HorizontalAlign.Start)
        }.width('100%').padding(12).backgroundColor('#FFFFFF').borderRadius(10)
        .alignItems(VerticalAlign.Center)
      }, (item: GoodsItem) => item.name)
    }.width('100%').padding(12).alignItems(HorizontalAlign.Start)
  }

  // ================= Tab7 我的 =================
  @Builder
  tab6() {
    Column({ space: 12 }) {
      // 深空灰个人卡
      Row({ space: 12 }) {
        Column() {
          Text('驾').fontSize(24).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
        }.width(64).height(64).borderRadius(32).backgroundColor('#2962FF')
        .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)

        Column({ space: 6 }) {
          Text('车主_7078').fontSize(17).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
          Row({ space: 8 }) {
            Text('智驾等级 L2+').fontSize(10).fontColor('#00B8D4')
              .padding({ left: 8, right: 8, top: 3, bottom: 3 })
              .backgroundColor('rgba(0,184,212,0.15)').borderRadius(8)
              .scale({ x: 1.05, y: 1.05 })
              .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            Text('行驶 42,860 km').fontSize(10).fontColor('#B0BEC5')
          }.alignItems(VerticalAlign.Center)
        }.layoutWeight(1).alignItems(HorizontalAlign.Start)
      }.width('100%').padding(16).backgroundColor('#263238').borderRadius(14)
      .alignItems(VerticalAlign.Center)

      // 三格统计
      Row({ space: 8 }) {
        ForEach(this.myStatLabels, (label: string, index: number) => {
          Column({ space: 4 }) {
            Text(this.myStatNums[index]).fontSize(18).fontColor('#2962FF')
              .fontWeight(FontWeight.Bold)
            Text(label).fontSize(11).fontColor('#90A4AE')
          }.layoutWeight(1).padding({ top: 10, bottom: 10 })
          .backgroundColor('#FFFFFF').borderRadius(10)
          .alignItems(HorizontalAlign.Center)
        }, (label: string) => label)
      }.width('100%').alignItems(VerticalAlign.Center)

      // 6 行菜单
      ForEach(this.menuNames, (menu: string, index: number) => {
        Row({ space: 10 }) {
          Column() {
            Text(index === 0 ? '📦' : (index === 1 ? '❤️' : (index === 2 ? '🎟️' : (index === 3 ? '🔧' : (index === 4 ? '🎧' : '⚙️')))))
              .fontSize(16)
          }.width(36).height(36).borderRadius(18).backgroundColor('#E8EAF6')
          .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)

          Text(menu).fontSize(13).fontColor('#263238')
          Column().layoutWeight(1)
          Text('›').fontSize(18).fontColor('#B0BEC5')
        }.width('100%').padding({ left: 12, right: 12, top: 10, bottom: 10 })
        .backgroundColor('#FFFFFF').borderRadius(10)
        .alignItems(VerticalAlign.Center)
        .onClick(() => {
          if (index === 1) {
            this.showDeleteModal = true
          } else if (index === 3) {
            this.showShopModal = true
          } else if (index === 0) {
            this.showPackModal = true
          } else {
            this.currentTab = 6
          }
        })
      }, (menu: string) => menu)

      Text('智能驾驶升级馆 · 让每一次升级都安心').fontSize(10).fontColor('#B0BEC5')
    }.width('100%').padding(12).alignItems(HorizontalAlign.Start)
  }

  // ================= 弹框1:发布装备(底部弹出) =================
  @Builder
  addModal() {
    Column() {
      Column().layoutWeight(1)
      Column({ space: 12 }) {
        Row() {
          Text('发布智驾装备').fontSize(16).fontColor('#263238').fontWeight(FontWeight.Bold)
          Column().layoutWeight(1)
          Text('✕').fontSize(14).fontColor('#90A4AE')
            .onClick(() => { this.showAddModal = false })
        }.width('100%').alignItems(VerticalAlign.Center)

        Text('选择品类').fontSize(12).fontColor('#90A4AE')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.kindChips, (kind: string, index: number) => {
            Text(kind).fontSize(12)
              .fontColor(this.chipA === index ? '#FFFFFF' : '#2962FF')
              .padding({ left: 12, right: 12, top: 6, bottom: 6 })
              .backgroundColor(this.chipA === index ? '#2962FF' : '#E8EAF6')
              .borderRadius(14)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => { this.chipA = index })
          }, (kind: string) => kind)
        }.width('100%')

        Text('适配车型').fontSize(12).fontColor('#90A4AE')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.carChips, (car: string, index: number) => {
            Text(car).fontSize(12)
              .fontColor(this.chipB === index ? '#FFFFFF' : '#00B8D4')
              .padding({ left: 12, right: 12, top: 6, bottom: 6 })
              .backgroundColor(this.chipB === index ? '#00B8D4' : '#E0F7FA')
              .borderRadius(14)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => { this.chipB = index })
          }, (car: string) => car)
        }.width('100%')

        Text('装备描述').fontSize(12).fontColor('#90A4AE')
        TextInput({ placeholder: '一句话介绍你的智驾装备', text: this.addDesc })
          .height(44).fontSize(12).backgroundColor('#F5F5F5').borderRadius(8)
          .onChange((value: string) => { this.addDesc = value })

        Text('立即上架').fontSize(14).fontColor('#FFFFFF')
          .width('100%').textAlign(TextAlign.Center).padding(12)
          .backgroundColor('#2962FF').borderRadius(22)
          .onClick(() => { this.showAddModal = false })
      }.width('100%').padding(16).backgroundColor('#FFFFFF')
      .borderRadius({ topLeft: 16, topRight: 16 })
      .constraintSize({ maxHeight: '80%' })
      .alignItems(HorizontalAlign.Start)
    }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
    .onClick(() => { this.showAddModal = false })
  }

  // ================= 弹框2:升级套餐(底部弹出) =================
  @Builder
  packModal() {
    Column() {
      Column().layoutWeight(1)
      Column({ space: 12 }) {
        Row() {
          Text('升级套餐').fontSize(16).fontColor('#263238').fontWeight(FontWeight.Bold)
          Column().layoutWeight(1)
          Text('✕').fontSize(14).fontColor('#90A4AE')
            .onClick(() => { this.showPackModal = false })
        }.width('100%').alignItems(VerticalAlign.Center)

        Text('当前方案:' + this.packChips[this.chipA]).fontSize(13).fontColor('#2962FF')
          .fontWeight(FontWeight.Medium)

        Text('选择方案').fontSize(12).fontColor('#90A4AE')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.packChips, (pack: string, index: number) => {
            Text(pack).fontSize(12)
              .fontColor(this.chipA === index ? '#FFFFFF' : '#263238')
              .padding({ left: 14, right: 14, top: 7, bottom: 7 })
              .backgroundColor(this.chipA === index ? '#2962FF' : '#E8EAF6')
              .borderRadius(15)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => { this.chipA = index })
          }, (pack: string) => pack)
        }.width('100%')

        Row() {
          Text('硬件数量(限 1-6 件)').fontSize(12).fontColor('#90A4AE')
          Column().layoutWeight(1)
          // 总价(脉冲)
          Text('¥' + this.packTotal()).fontSize(18).fontColor('#E53935').fontWeight(FontWeight.Bold)
            .scale({ x: 1.05, y: 1.05 })
            .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
        }.width('100%').alignItems(VerticalAlign.Center)

        // 数量 stepper
        Row({ space: 16 }) {
          Text('-').fontSize(16).fontColor(this.stepNum <= 1 ? '#B0BEC5' : '#2962FF')
            .width(30).height(30).borderRadius(15).textAlign(TextAlign.Center)
            .border({ width: 1, color: this.stepNum <= 1 ? '#B0BEC5' : '#2962FF' })
            .onClick(() => {
              if (this.stepNum > 1) { this.stepNum = this.stepNum - 1 }
            })
          Text(this.stepNum + ' 件').fontSize(14).fontColor('#263238').fontWeight(FontWeight.Medium)
          Text('+').fontSize(16).fontColor(this.stepNum >= 6 ? '#B0BEC5' : '#2962FF')
            .width(30).height(30).borderRadius(15).textAlign(TextAlign.Center)
            .border({ width: 1, color: this.stepNum >= 6 ? '#B0BEC5' : '#2962FF' })
            .onClick(() => {
              if (this.stepNum < 6) { this.stepNum = this.stepNum + 1 }
            })
        }.justifyContent(FlexAlign.Center).width('100%')

        Text('确认升级').fontSize(14).fontColor('#FFFFFF')
          .width('100%').textAlign(TextAlign.Center).padding(12)
          .backgroundColor('#00B8D4').borderRadius(22)
          .onClick(() => { this.showPackModal = false })
      }.width('100%').padding(16).backgroundColor('#FFFFFF')
      .borderRadius({ topLeft: 16, topRight: 16 })
      .constraintSize({ maxHeight: '80%' })
      .alignItems(HorizontalAlign.Start)
    }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
    .onClick(() => { this.showPackModal = false })
  }

  // ================= 弹框3:预约安装(底部弹出) =================
  @Builder
  shopModal() {
    Column() {
      Column().layoutWeight(1)
      Column({ space: 12 }) {
        Row() {
          Text('预约安装').fontSize(16).fontColor('#263238').fontWeight(FontWeight.Bold)
          Column().layoutWeight(1)
          Text('✕').fontSize(14).fontColor('#90A4AE')
            .onClick(() => { this.showShopModal = false })
        }.width('100%').alignItems(VerticalAlign.Center)

        Text('选择门店').fontSize(12).fontColor('#90A4AE')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.shopChips, (shop: string, index: number) => {
            Text(shop).fontSize(12)
              .fontColor(this.chipA === index ? '#FFFFFF' : '#2962FF')
              .padding({ left: 12, right: 12, top: 6, bottom: 6 })
              .backgroundColor(this.chipA === index ? '#2962FF' : '#E8EAF6')
              .borderRadius(14)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => { this.chipA = index })
          }, (shop: string) => shop)
        }.width('100%')

        Text('选择时段').fontSize(12).fontColor('#90A4AE')
        Row({ space: 8 }) {
          ForEach(this.timeChips, (time: string, index: number) => {
            Text(time).fontSize(12)
              .fontColor(this.chipB === index ? '#FFFFFF' : '#00B8D4')
              .padding({ left: 16, right: 16, top: 7, bottom: 7 })
              .backgroundColor(this.chipB === index ? '#00B8D4' : '#E0F7FA')
              .borderRadius(15)
              .onClick(() => { this.chipB = index })
          }, (time: string) => time)
        }.width('100%')

        Row() {
          Text('施工工位(限 1-2 位)').fontSize(12).fontColor('#90A4AE')
          Column().layoutWeight(1)
          Text(this.shopChips[this.chipA] + ' · ' + this.timeChips[this.chipB])
            .fontSize(11).fontColor('#263238')
        }.width('100%').alignItems(VerticalAlign.Center)

        // 工位 stepper
        Row({ space: 16 }) {
          Text('-').fontSize(16).fontColor(this.stepNumB <= 1 ? '#B0BEC5' : '#E53935')
            .width(30).height(30).borderRadius(15).textAlign(TextAlign.Center)
            .border({ width: 1, color: this.stepNumB <= 1 ? '#B0BEC5' : '#E53935' })
            .onClick(() => {
              if (this.stepNumB > 1) { this.stepNumB = this.stepNumB - 1 }
            })
          Text(this.stepNumB + ' 位').fontSize(14).fontColor('#263238').fontWeight(FontWeight.Medium)
          Text('+').fontSize(16).fontColor(this.stepNumB >= 2 ? '#B0BEC5' : '#E53935')
            .width(30).height(30).borderRadius(15).textAlign(TextAlign.Center)
            .border({ width: 1, color: this.stepNumB >= 2 ? '#B0BEC5' : '#E53935' })
            .onClick(() => {
              if (this.stepNumB < 2) { this.stepNumB = this.stepNumB + 1 }
            })
        }.justifyContent(FlexAlign.Center).width('100%')

        Text('提交预约').fontSize(14).fontColor('#FFFFFF')
          .width('100%').textAlign(TextAlign.Center).padding(12)
          .backgroundColor('#2962FF').borderRadius(22)
          .onClick(() => { this.showShopModal = false })
      }.width('100%').padding(16).backgroundColor('#FFFFFF')
      .borderRadius({ topLeft: 16, topRight: 16 })
      .constraintSize({ maxHeight: '80%' })
      .alignItems(HorizontalAlign.Start)
    }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
    .onClick(() => { this.showShopModal = false })
  }

  // ================= 弹框4:删除确认(居中小卡警示) =================
  @Builder
  deleteModal() {
    Column() {
      Column() {
        // 红色警示区
        Column({ space: 4 }) {
          Text('⚠').fontSize(26).fontColor('#FFFFFF')
          Text('删除后不可恢复').fontSize(10).fontColor('#FFEBEE')
        }.width('100%').padding({ top: 16, bottom: 16 })
        .backgroundColor('#E53935')
        .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)

        Column({ space: 14 }) {
          Text('确认删除该装备收藏?').fontSize(15).fontColor('#263238')
            .fontWeight(FontWeight.Medium)

          Row({ space: 10 }) {
            Text('取消').fontSize(13).fontColor('#607D8B')
              .layoutWeight(1).textAlign(TextAlign.Center).padding(10)
              .backgroundColor('#ECEFF1').borderRadius(20)
              .onClick(() => { this.showDeleteModal = false })

            Text('删除').fontSize(13).fontColor('#FFFFFF')
              .layoutWeight(1).textAlign(TextAlign.Center).padding(10)
              .backgroundColor('#E53935').borderRadius(20)
              .onClick(() => { this.showDeleteModal = false })
          }.width('100%').alignItems(VerticalAlign.Center)
        }.width('100%').padding(16)
      }.width('72%').borderRadius(12).backgroundColor('#FFFFFF')
      .alignItems(HorizontalAlign.Center)
    }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
    .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)
    .onClick(() => { this.showDeleteModal = false })
  }

  // ================= 弹框5:详情(右侧滑出全高面板) =================
  @Builder
  detailModal() {
    Column() {
      Column({ space: 12 }) {
        // 大色块头图区
        Column({ space: 6 }) {
          Text(this.selName().substring(0, 1)).fontSize(44).fontColor('#FFFFFF')
            .fontWeight(FontWeight.Bold)
          Text(this.selTag()).fontSize(11).fontColor('#E8EAF6')
            .padding({ left: 10, right: 10, top: 3, bottom: 3 })
            .backgroundColor('rgba(255,255,255,0.25)').borderRadius(10)
        }.width('100%').height(170)
        .backgroundColor('#2962FF')
        .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)

        Scroll() {
          Column({ space: 12 }) {
            Text(this.selName()).fontSize(17).fontColor('#263238')
              .fontWeight(FontWeight.Bold).maxLines(2)
            Text(this.selDesc()).fontSize(12).fontColor('#90A4AE')

            // 评分星级
            Row({ space: 8 }) {
              Text(this.selStars()).fontSize(16).fontColor('#FFB300')
              Text(this.selScore() + ' 分').fontSize(12).fontColor('#FFB300')
              Column().layoutWeight(1)
              Text(this.selNum() + ' 人加购').fontSize(10).fontColor('#B0BEC5')
            }.width('100%').alignItems(VerticalAlign.Center)

            // 价格大字
            Row({ space: 6 }) {
              Text('¥').fontSize(14).fontColor('#E53935')
              Text(Math.floor(this.selPrice()) + '').fontSize(28).fontColor('#E53935')
                .fontWeight(FontWeight.Bold)
              Text('分期免息').fontSize(9).fontColor('#E53935')
                .padding({ left: 5, right: 5, top: 2, bottom: 2 })
                .border({ width: 1, color: '#E53935' }).borderRadius(4)
            }.alignItems(VerticalAlign.Bottom)

            // 好评进度
            Column({ space: 6 }) {
              Text('好评率 ' + this.selScore() + '%').fontSize(11).fontColor('#00B8D4')
              Row() {
                Column().width(this.selScore() + '%').height(7)
                  .backgroundColor('#00B8D4').borderRadius(4)
              }.width('100%').height(7).backgroundColor('#E8EAF6').borderRadius(4)
            }.width('100%').alignItems(HorizontalAlign.Start)

            // 适配车型 chips
            Text('适配车型').fontSize(12).fontColor('#90A4AE')
            Flex({ wrap: FlexWrap.Wrap }) {
              ForEach(this.carChips, (car: string) => {
                Text(car).fontSize(11).fontColor('#2962FF')
                  .padding({ left: 10, right: 10, top: 5, bottom: 5 })
                  .backgroundColor('#E8EAF6').borderRadius(12)
                  .margin({ right: 8, bottom: 8 })
              }, (car: string) => car)
            }.width('100%')

            Text('取消收藏').fontSize(11).fontColor('#B0BEC5')
              .padding(8)
              .onClick(() => {
                this.showDetailModal = false
                this.showDeleteModal = true
              })
          }.width('100%').padding(16)
          .alignItems(HorizontalAlign.Start)
        }.layoutWeight(1).width('100%')

        // 底部立即购买
        Text('立即购买').fontSize(15).fontColor('#FFFFFF')
          .width('100%').textAlign(TextAlign.Center).padding(14)
          .backgroundColor('#2962FF').borderRadius(24)
          .margin({ bottom: 16 })
          .scale({ x: 1.05, y: 1.05 })
          .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
          .onClick(() => { this.showDetailModal = false })
      }.width('80%').height('100%').backgroundColor('#FFFFFF')
      .alignItems(HorizontalAlign.Start)
    }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
    .justifyContent(FlexAlign.End).alignItems(HorizontalAlign.End)
    .onClick(() => { this.showDetailModal = false })
  }

  // ================= 页面装配 =================
  build() {
    Stack() {
      Column() {
        this.headerBuilder()
        Scroll() {
          Column() {
            if (this.currentTab === 0) {
              this.tab0()
            } else if (this.currentTab === 1) {
              this.tab1()
            } else if (this.currentTab === 2) {
              this.tab2()
            } else if (this.currentTab === 3) {
              this.tab3()
            } else if (this.currentTab === 4) {
              this.tab4()
            } else if (this.currentTab === 5) {
              this.tab5()
            } else {
              this.tab6()
            }
          }.width('100%')
        }.layoutWeight(1).width('100%')
        this.bottomBuilder()
      }.width('100%').height('100%')
      if (this.showAddModal) { this.addModal() }
      if (this.showPackModal) { this.packModal() }
      if (this.showShopModal) { this.shopModal() }
      if (this.showDeleteModal) { this.deleteModal() }
      if (this.showDetailModal) { this.detailModal() }
    }.width('100%').height('100%')
  }
}


总结

在这里插入图片描述

本文深入解析了一款基于HarmonyOS ArkTS声明式UI框架构建的智能驾驶升级馆应用。该应用以单文件页面架构集成了七个功能Tab和五个交互弹框,覆盖了智驾装备商城、行车记录、HUD抬显、智驾硬件、用车社区、消息中心和个人中心的完整业务场景。在数据层面,应用通过统一的GoodsItem接口适配了装备、帖子、消息等多种数据类型,通过六组私有数组预置了78条结构化数据,并通过sel*系列辅助方法实现了对选中商品的安全访问。在UI层面,应用大量运用了ForEach列表渲染、Flex弹性布局、Scroll滚动容器和Stack层叠管理等ArkTS原生组件,配合柱状图、进度条和脉冲动画等可视化手段,构建了信息密度高且交互友好的用户界面。

从技术实现角度来看,应用的核心设计模式体现在三个方面。首先是状态驱动的弹框管理系统——五个布尔状态变量各自控制一个弹框的显隐,通过Stack的条件渲染实现了模态层叠效果,内层event.stopPropagation()确保了点击弹框内容不会误触关闭。其次是状态联动的实时计算——升级套餐弹框中的packTotal()方法根据方案索引和数量步进值动态计算总价,当任一输入变化时框架自动触发UI更新,体现了ArkTS响应式编程的核心优势。最后是视觉一致性的系统化保障——应用定义了极客蓝、荧光青、深空灰、浅蓝底和警示红五色配色体系,通过index % 3取模运算在列表项间循环分配颜色,脉冲缩放动画(duration: 700ms, iterations: -1, curve: EaseInOut)作为统一的动效语言贯穿统计卡、热度数字、标签和按钮等所有交互元素。

从工程实践角度来看,该应用的代码组织展现了ArkTS声明式UI的最佳实践。@Builder装饰器被系统性地用于构建可复用的UI片段——从headerBuilder和bottomBuilder到七个Tab内容和五个弹框,每个Builder各司其职、边界清晰。@State状态变量的粒度控制恰到好处——既不过度细分导致状态管理复杂,也不过度聚合影响渲染性能。辅助方法如maxNumOf()selStars()封装了通用计算逻辑,减少了模板代码的重复。柱状图、进度条等数据可视化的实现完全基于Column组件的动态宽高属性,无需引入第三方图表库,体现了ArkTS原生组件的强大表达能力。整体而言,该应用为HarmonyOS平台的电商类应用开发提供了一个结构完整、设计精良的参考实现。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐