引言

在这里插入图片描述

二手车交易市场近年来正在经历从线下展厅向线上数字化平台的深度迁移。一辆二手车从上架到成交,涉及车源筛选、车辆检测、估值定价、置换补贴、预约看车、金融分期等多个业务环节,每个环节都需要精确的数据呈现和流畅的交互体验。传统的二手车App往往只关注车源列表展示,而忽略了检测报告可视化、置换估值计算、消息通知等辅助决策场景。本文将围绕一套完整的二手车精选行主页面源码,从数据建模到视图构建、从列表渲染到弹层交互,逐段剖析其声明式UI架构设计和技术实现细节。

在技术架构层面,本应用采用HarmonyOS ArkTS声明式UI范式,以@Entry@Component装饰器构建单文件入口组件。组件内部通过十余个@State变量管理页面导航状态、弹层显示状态、用户输入文本和交互选择索引。特别值得关注的是,该应用引入了TextInput组件的双向数据绑定机制,通过mileageInputphoneInput两个@State字符串变量实现了表单输入的状态化管理。此外,tradeEstimate()方法基于选中项的price字段进行85折折旧计算,体现了业务逻辑与UI渲染的深度耦合。整个页面的构建器函数通过@Builder装饰器拆分为头部、底部、七个Tab内容区和五个弹层,形成了清晰的视图层组织结构。

在业务设计层面,应用围绕二手车交易场景构建了七大功能Tab:精选车源、新能源二手、SUV专区、检测报告、置换估值、消息通知和个人中心。精选车源Tab以横向滚动的Banner大卡和纵向列表相结合,展示24台精选二手车;新能源二手Tab以双列网格布局展示12台新能源车型,并通过续航里程柱状图提供数据可视化;SUV专区以横向滚动画廊形式展示10台SUV车型;检测报告Tab以柱状图和列表结合的方式展示10项第三方检测项的通过状态和评分;置换估值Tab以预估价格卡片和置换车系列表呈现置换交易场景。配色采用汽车蓝#0277BD为主色调,琥珀橙#FF8F00为价格强调色,深灰蓝#37474F用于数据统计区域,形成了专业可信的汽车交易视觉风格。

一、数据模型与状态管理基础

在这里插入图片描述

1.1 GoodsItem接口定义

应用首先定义了全局接口GoodsItem作为所有列表项的统一数据契约,包含七个字段,与整体应用的列表渲染逻辑保持一致。

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

在该二手车应用中,GoodsItem的字段语义在不同Tab下有着不同的含义。在精选车源列表中,price表示车辆价格(以千元为单位,78表示7.8万),num表示行驶里程(以千公里为单位,42表示4.2万公里),score表示车况评分百分比。在新能源二手列表中,num被复用为续航里程(km),score表示电池健康度。在检测报告列表中,num表示检测覆盖率,score表示检测得分。在置换估值列表中,price表示置换价格,num表示补贴金额系数。在消息列表中,num表示消息时间(分钟),hot字段表示消息发送者首字。这种字段语义复用策略减少了接口定义数量,同时要求开发者在每个Tab的渲染逻辑中做精确的语义映射。

1.2 组件声明与状态变量

在这里插入图片描述

页面核心组件通过@Entry@Component装饰器声明,内部定义了大量响应式状态变量来管理页面交互状态。

@Entry
@Component
struct Index {
  @State currentTab: number = 0
  @State showAddModal: boolean = false
  @State showBookModal: boolean = false
  @State showTradeModal: 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 mileageInput: string = ''
  @State phoneInput: string = ''

状态变量体系分为四类。第一类是导航状态currentTab,控制七个Tab间的切换。第二类是弹层显示状态,包括快速卖车弹层、预约看车弹层、置换估值弹层、删除确认弹层和详情弹层,五个布尔变量初始值均为false。第三类是选中项引用selectedItem,类型为GoodsItem | null,在列表点击时被赋值,在弹层中通过辅助方法安全读取。第四类是交互状态,chipAchipB用于多个弹层中Chip选项的选中索引,stepNum用于试驾圈数的步进控制,mileageInputphoneInput是两个字符串类型的状态变量,通过TextInputonChange回调实现双向数据绑定。

1.3 Chip配置与颜色方案

在这里插入图片描述

组件内部定义了丰富的Chip配置数组和颜色方案,为弹层中的选择交互提供数据支撑。

  private tabs: string[] = ['精选车源', '新能源二手', 'SUV专区', '检测报告', '置换估值', '消息', '我的']
  private seriesChips: string[] = ['大众朗逸', '丰田凯美瑞', '本田雅阁', '特斯拉Model 3', '比亚迪汉']
  private ageChips: string[] = ['1-3年', '3-5年', '5-8年', '8年以上']
  private storeChips: string[] = ['浦东店', '徐汇店', '静安店', '闵行店']
  private slotChips: string[] = ['上午', '下午', '晚间']
  private condChips: string[] = ['优秀', '良好', '一般']
  private dispChips: string[] = ['1.5T', '2.0T', '2.5L', '纯电']
  private configChips: string[] = ['无钥匙进入', '倒车影像', '定速巡航', 'CarPlay', '座椅加热', '全景天窗']
  private menuList: string[] = ['我的收藏', '浏览足迹', '卖车订单', '估值记录', '收货地址', '设置']
  private menuIcons: string[] = ['⭐', '👣', '📦', '💰', '📍', '🛠']
  private colorArr: string[] = ['#0277BD', '#FF8F00', '#37474F', '#00897B']
  private bannerColors: string[] = ['#01579B', '#37474F', '#00695C']

seriesChips定义了五个主流车系用于快速卖车弹层中的车系选择。ageChips按车龄段划分四个区间。storeChips列出了上海四个线下门店。slotChips定义了看车时段。condChipsdispChips用于置换估值弹层中的车况和排量选择。configChips定义了六个配置亮点标签,用于详情弹层展示。colorArrbannerColors分别定义了列表头像和Banner卡片的颜色循环方案,colorArr包含四个颜色按索引取模循环,bannerColors包含三个颜色用于Banner大卡。

1.4 精选车源Banner与主列表数据

在这里插入图片描述

精选车源Tab的Banner区域使用了三条精选车辆数据,主列表则包含24台二手车信息。

  private bannerList: GoodsItem[] = [
    {
      name: '特斯拉Model 3 2021款 标准续航',
      price: 175,
      desc: '2021年上牌 · 3.9万公里 · 电池健康95%',
      score: 95,
      num: 39,
      tag: '新能源精选',
      hot: '急'
    },
    {
      name: '宝马3系 2019款 325Li 首发版',
      price: 228,
      desc: '2019年上牌 · 7.8万公里 · 尊选认证',
      score: 89,
      num: 78,
      tag: '豪华精选',
      hot: '热'
    },
    {
      name: '丰田凯美瑞 2020款 2.0G 豪华版',
      price: 135,
      desc: '2020年上牌 · 5.6万公里 · 4S店保养',
      score: 88,
      num: 56,
      tag: '家用首选',
      hot: '荐'
    }
  ]

Banner数据中每条记录的price字段值为175,在渲染时通过(item.price / 10).toFixed(1)转换为"17.5万"显示。hot字段使用"急"“热”"荐"等单字标识紧急程度。Banner卡片使用bannerColors数组中的三种颜色作为背景,配合白色文字形成高对比度的大卡展示效果。主列表的24台车覆盖了从大众朗逸(7.8万)到奔驰C级(23.5万)的广泛价格区间,涵盖了合资品牌、豪华品牌和国产新能源品牌,为用户提供了充分的选择空间。

二、辅助方法与业务逻辑函数

2.1 选中项取值方法

在这里插入图片描述

应用定义了五个辅助方法来安全地从selectedItem中提取字段值,每个方法都进行空值检查后返回对应字段。

  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
  }
  selTag(): string {
    if (this.selectedItem !== null) {
      return this.selectedItem.tag
    }
    return '官方认证'
  }

值得注意的是,selTag()方法在selectedItem为空时返回的默认值不是空字符串,而是"官方认证"。这是因为详情弹层中展示车辆标签时,如果用户通过非列表点击方式进入(如从菜单跳转),显示"官方认证"比空白更具信息完整性。其余四个方法分别返回空字符串和零值作为默认值,确保弹层中不会出现undefined导致的运行时错误。

2.2 最大值计算与消息时间格式化

在这里插入图片描述

maxNumOf方法用于柱状图渲染中的比例缩放计算,msgTime方法将分钟数转换为友好的时间描述。

  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
  }

  msgTime(n: number): string {
    if (n < 60) {
      return n + '分钟前'
    }
    if (n < 1440) {
      return Math.floor(n / 60) + '小时前'
    }
    return Math.floor(n / 1440) + '天前'
  }

msgTime方法实现了三级时间格式化逻辑:小于60分钟显示"X分钟前",小于1440分钟(24小时)显示"X小时前",大于等于1440分钟显示"X天前"。Math.floor确保时间值向下取整,避免出现"1.5小时前"这种不自然的时间描述。该方法在消息Tab的每条消息项中被调用,将item.num字段(以分钟为单位的时间偏移量)转换为用户友好的相对时间文本。

2.3 置换估值计算

tradeEstimate方法是置换估值Tab的核心业务逻辑,根据选中车辆的价格进行85折折旧计算。

  tradeEstimate(): number {
    let base: number = 78
    if (this.selectedItem !== null) {
      base = this.selectedItem.price
    }
    return Math.floor(base * 0.85) / 10
    }

方法以78作为默认基准值(对应7.8万的朗逸),如果用户选中了某台车辆则使用该车的price值作为基准。计算公式为Math.floor(base * 0.85) / 10,先乘以0.85进行85折折旧,再除以10将千元单位转换为万元单位。例如选中宝马3系(price=228),计算结果为Math.floor(228 * 0.85) / 10 = Math.floor(193.8) / 10 = 193 / 10 = 19.3,即预估可卖19.3万。这一方法在置换估值Tab的预估价格卡片和置换弹层中均有调用,当selectedItem变化时自动重新计算并更新显示。

三、头部与底部导航构建

在这里插入图片描述

3.1 头部Builder

头部区域集成了定位信息、品牌标题、搜索框和急速卖车入口四个功能区块。

  @Builder headerBuilder() {
    Column() {
      Row() {
        Row({ space: 4 }) {
          Text('📍').fontSize(14)
          Text('上海·浦东').fontSize(13).fontColor('#FFFFFF')
        }
        Column().layoutWeight(1)
        Text('🎧').fontSize(18)
      }.width('100%').padding({ left: 16, right: 16, top: 12 })
      Row({ space: 10 }) {
        Text('二手车精选').fontSize(22).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
        Row({ space: 6 }) {
          Text('🔍').fontSize(12)
          Text('搜车系/车型').fontSize(12).fontColor('#78909C')
          Column().layoutWeight(1)
          Text('📷').fontSize(13)
        }
        .layoutWeight(1).height(32).borderRadius(16).backgroundColor('#ECEFF1')
        .padding({ left: 12, right: 12 })
      }.width('100%').padding({ left: 16, right: 16, top: 10, bottom: 10 })
      Row({ space: 6 }) {
        Text('⚡').fontSize(13)
        Text('急速卖车 · 30分钟上门评估').fontSize(12).fontColor('#FFFFFF').fontWeight(FontWeight.Medium)
        Column().layoutWeight(1)
        Text('立即发布 ›').fontSize(11).fontColor('#FFE0B2')
      }
      .width('100%').height(36).backgroundColor('#FF8F00')
      .padding({ left: 16, right: 16 })
      .onClick(() => {
        this.showAddModal = true
      })
    }.width('100%').backgroundColor('#0277BD')
  }

头部采用三层垂直布局。第一层是定位行,左侧显示定位图标和"上海·浦东"文本,右侧是耳机图标(客服入口)。第二层是标题搜索行,左侧加粗显示"二手车精选"品牌名称,右侧是圆角搜索框,内部包含搜索图标、占位文字和拍照图标。第三层是琥珀橙色的急速卖车入口条,展示"急速卖车·30分钟上门评估"的营销文案和"立即发布"链接,点击后触发快速卖车弹层。整个头部使用汽车蓝#0277BD作为背景色,白色文字确保高对比度可读性。

3.2 底部导航栏Builder

底部导航栏通过ForEach遍历tabs数组渲染七个Tab项,选中状态使用汽车蓝标识。

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

底部导航栏的Tab项没有设置layoutWeight(1),而是依靠Row默认的均匀分布行为。每个Tab项由文字标签和底部指示条组成,选中时文字变为汽车蓝、指示条显示蓝色背景,未选中时文字为灰色、指示条完全透明(#FFFFFF00)。点击事件直接更新currentTab触发页面内容切换。

四、精选车源Tab构建

4.1 Banner大卡横向滚动

精选车源Tab顶部展示今日精选大卡,通过横向Scroll容器实现Banner轮播效果。

  @Builder tab0() {
    Column({ space: 12 }) {
      Row({ space: 8 }) {
        Text('🔥').fontSize(16)
        Text('今日精选大卡').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#263238')
        Column().layoutWeight(1)
        Text('左滑查看更多').fontSize(10).fontColor('#B0BEC5')
      }.width('100%')
      Scroll() {
        Row({ space: 12 }) {
          ForEach(this.bannerList, (item: GoodsItem, index: number) => {
            Column({ space: 8 }) {
              Row() {
                Text(item.tag).fontSize(10).fontColor('#FFFFFF').backgroundColor('rgba(255,255,255,0.25)')
                  .padding({ left: 8, right: 8, top: 3, bottom: 3 }).borderRadius(8)
                Column().layoutWeight(1)
                Text(item.hot).fontSize(12).fontColor('#FFFFFF').backgroundColor('#FF8F00')
                  .width(24).height(24).borderRadius(12).textAlign(TextAlign.Center)
                  .scale({ x: 1.05, y: 1.05 })
                  .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
              }.width('100%')
              Column({ space: 4 }) {
                Text(item.name).fontSize(16).fontWeight(FontWeight.Bold).fontColor('#FFFFFF').maxLines(1)
                Text(item.desc).fontSize(10).fontColor('#CFD8DC').maxLines(1)
              }.width('100%').alignItems(HorizontalAlign.Start)
              Column().layoutWeight(1)
              Row() {
                Row({ space: 2 }) {
                  Text((item.price / 10).toFixed(1)).fontSize(22).fontWeight(FontWeight.Bold).fontColor('#FFD54F')
                  Text('万').fontSize(10).fontColor('#FFD54F')
                }
                Column().layoutWeight(1)
                Text('立即看车 ›').fontSize(11).fontColor('#FFFFFF')
              }.width('100%')
            }
            .width(300).height(170).borderRadius(14).padding(14)
            .backgroundColor(this.bannerColors[index])
            .onClick(() => {
              this.selectedItem = item
              this.showDetailModal = true
            })
          }, (item: GoodsItem) => item.name)
        }.padding({ left: 2, right: 2 })
      }.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).width('100%')

Banner大卡宽度300vp、高度170vp,使用bannerColors数组中对应索引的颜色作为背景。卡片内部从上到下依次排列:标签行(半透明白色标签+橙色热度角标)、车名和描述行、弹性空白区、价格行。价格以金黄色#FFD54F显示,通过(item.price / 10).toFixed(1)将千元单位转换为万元并保留一位小数。热度角标附带脉冲缩放动画,吸引视觉焦点。Scroll容器设置scrollable(ScrollDirection.Horizontal)实现横向滚动,scrollBar(BarState.Off)隐藏滚动条保持视觉整洁。

4.2 精选车源列表

在Banner下方,精选车源列表通过ForEach遍历mainList渲染24台车辆的详细信息卡片。

      Column({ space: 10 }) {
        ForEach(this.mainList, (item: GoodsItem, index: number) => {
          Row({ space: 12 }) {
            Text(item.name.substring(0, 1)).fontSize(20).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
              .width(52).height(52).borderRadius(26).textAlign(TextAlign.Center)
              .backgroundColor(this.colorArr[index % 4])
            Column({ space: 5 }) {
              Row({ space: 6 }) {
                Text(item.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor('#263238').maxLines(1)
                if (item.hot !== '') {
                  Text(item.hot).fontSize(9).fontColor('#FFFFFF').backgroundColor('#E53935')
                    .padding({ left: 5, right: 5, top: 2, bottom: 2 }).borderRadius(4)
                    .scale({ x: 1.05, y: 1.05 })
                    .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
                }
              }.width('100%')
              Text(item.desc).fontSize(11).fontColor('#90A4AE').maxLines(1)
              Row() {
                Column().width(item.score + '%').height(5).borderRadius(3).backgroundColor('#0277BD')
              }.width('100%').height(5).borderRadius(3).backgroundColor('#ECEFF1')
              Row({ space: 6 }) {
                Text('表显里程 ' + (item.num / 10).toFixed(1) + '万km').fontSize(10).fontColor('#90A4AE')
                Text(item.tag).fontSize(10).fontColor('#0277BD').backgroundColor('#E1F5FE')
                  .padding({ left: 6, right: 6, top: 2, bottom: 2 }).borderRadius(4)
              }
            }.layoutWeight(1).alignItems(HorizontalAlign.Start)
            Column({ space: 6 }) {
              Row({ space: 2 }) {
                Text((item.price / 10).toFixed(1)).fontSize(19).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
                Text('万').fontSize(10).fontColor('#FF8F00')
              }
              Text('首付' + Math.floor(item.price / 10 / 3) + '万起').fontSize(9).fontColor('#B0BEC5')
            }.alignItems(HorizontalAlign.End)
          }
          .width('100%').padding(12).backgroundColor('#FFFFFF').borderRadius(12)
          .onClick(() => {
            this.selectedItem = item
            this.showDetailModal = true
          })
        }, (item: GoodsItem) => item.name)
      }.width('100%')
    }.width('100%').padding(12)
  }

列表卡片采用三段式横向布局。左侧是52x52的圆形头像,通过item.name.substring(0, 1)提取车名首字作为内容,背景色通过colorArr[index % 4]按索引取模循环四种颜色。中间区域展示车名、热度标签(条件渲染if (item.hot !== '')确保空热度标识不显示)、车况描述、评分进度条和里程标签。进度条使用嵌套Row+Column实现,底层灰色背景条加上层蓝色填充条,宽度通过item.score + '%'动态设置。右侧区域以琥珀橙显示价格和首付信息,首付通过Math.floor(item.price / 10 / 3)计算约为车价的三分之一。

五、新能源二手与SUV专区Tab构建

5.1 新能源二手续航柱状图与双列网格

新能源二手Tab以续航里程柱状图和双列网格卡片为核心内容,展示12台新能源车型。

  @Builder tab1() {
    Column({ space: 12 }) {
      Row({ space: 8 }) {
        Text('⚡').fontSize(16)
        Text('新能源续航榜').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#263238')
        Column().layoutWeight(1)
        Text('共' + this.evList.length + '台').fontSize(11).fontColor('#90A4AE')
      }.width('100%')
      Row({ space: 9 }) {
        ForEach(this.evList, (item: GoodsItem, index: number) => {
          Column({ space: 5 }) {
            Text(item.num + '').fontSize(8).fontColor('#00897B')
            Column().width(18).borderRadius(4)
              .height(item.num / this.maxNumOf(this.evList) * 120)
              .backgroundColor(index % 2 === 0 ? '#00897B' : '#4DB6AC')
            Text(item.name.substring(0, 2)).fontSize(8).fontColor('#90A4AE').maxLines(1)
          }
        }, (item: GoodsItem) => item.name)
      }.width('100%').alignItems(VerticalAlign.End).padding({ left: 4, right: 4 })

续航柱状图通过ForEach遍历evList渲染12个柱子。柱形高度通过item.num / this.maxNumOf(this.evList) * 120计算,其中num字段在此场景中表示续航里程(如420km、480km等)。柱子颜色通过index % 2 === 0进行奇偶交替,偶数索引使用深绿#00897B,奇数索引使用浅绿#4DB6AC,形成条纹视觉效果。每个柱子顶部显示续航数值,底部显示车名前两字。

5.2 新能源双列网格卡片

      Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
        ForEach(this.evList, (item: GoodsItem, index: number) => {
          Column({ space: 8 }) {
            Row() {
              Text(item.hot).fontSize(9).fontColor('#FFFFFF').backgroundColor('#00897B')
                .padding({ left: 6, right: 6, top: 2, bottom: 2 }).borderRadius(4)
              Column().layoutWeight(1)
              Text(item.num + 'km').fontSize(10).fontColor('#00897B').fontWeight(FontWeight.Bold)
            }.width('100%')
            Text(item.name).fontSize(13).fontWeight(FontWeight.Bold).fontColor('#263238').maxLines(1)
            Text(item.desc).fontSize(10).fontColor('#90A4AE').maxLines(1)
            Row() {
              Column().width(item.score + '%').height(6).borderRadius(3)
                .backgroundColor(item.score >= 90 ? '#00897B' : '#FF8F00')
                .scale({ x: 1.05, y: 1.05 })
                .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            }.width('100%').height(6).borderRadius(3).backgroundColor('#ECEFF1')
            Row() {
              Text('电池健康 ' + item.score + '%').fontSize(9).fontColor('#90A4AE')
              Column().layoutWeight(1)
              Row({ space: 2 }) {
                Text((item.price / 10).toFixed(1)).fontSize(15).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
                Text('万').fontSize(9).fontColor('#FF8F00')
              }
            }.width('100%')
            Text('月供' + Math.floor(item.price / 10 * 10000 / 36) + '元起').fontSize(9).fontColor('#B0BEC5')
          }
          .width('48%').padding(12).backgroundColor('#FFFFFF').borderRadius(12)
          .onClick(() => {
            this.selectedItem = item
            this.showDetailModal = true
          })
        }, (item: GoodsItem) => item.name)
      }.width('100%')

双列网格卡片宽度设为48%,通过FlexSpaceBetween间距分配。每张卡片顶部展示车型类型标签(如"纯电")和续航里程。电池健康度进度条根据score值是否大于等于90动态选择颜色:高健康度使用绿色,低健康度使用橙色,并附带脉冲动画。底部显示电池健康百分比、价格和月供信息。月供通过Math.floor(item.price / 10 * 10000 / 36)计算,即将车价从千元转为元后分36期等额本息计算。

5.3 SUV专区数据统计与横向画廊

SUV专区Tab以数据统计卡片、车龄分布柱状图和横向滚动画廊三个模块构建。

  @Builder tab2() {
    Column({ space: 12 }) {
      Row() {
        Column({ space: 4 }) {
          Text('128').fontSize(22).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
            .scale({ x: 1.06, y: 1.06 })
            .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
          Text('在售SUV').fontSize(10).fontColor('#B0BEC5')
        }.layoutWeight(1)
        Column().width(1).height(34).backgroundColor('#546E7A')
        Column({ space: 4 }) {
          Text('12').fontSize(22).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
          Text('今日上新').fontSize(10).fontColor('#B0BEC5')
        }.layoutWeight(1)
        Column().width(1).height(34).backgroundColor('#546E7A')
        Column({ space: 4 }) {
          Text('68%').fontSize(22).fontWeight(FontWeight.Bold).fontColor('#4DB6AC')
          Text('30天成交率').fontSize(10).fontColor('#B0BEC5')
        }.layoutWeight(1)
      }.width('100%').backgroundColor('#37474F').borderRadius(12).padding(16)

数据统计卡片使用深灰蓝#37474F背景,三等分布局展示在售SUV数量(128台)、今日上新(12台)和30天成交率(68%)。在售数量附加1.06倍缩放脉冲动画,突出市场活跃度。分割线使用#546E7A中灰色,在深色背景上呈现柔和的分隔效果。三个指标分别使用白色、琥珀橙和青绿色,通过色彩区分数据类型。

六、二手车交易流程交互架构图

以下流程图展示了用户从浏览车源到完成预约看车的完整交易决策流程,涵盖了车源筛选、详情查看、检测报告分析、置换估值和预约看车等核心环节。

新能源二手

SUV专区

检测报告

置换估值

发现问题项

全部通过

用户启动应用

加载精选车源Tab

浏览Banner大卡

查看精选车源列表

点击车源卡片

打开详情弹层

查看车辆详情

是否预约看车?

打开预约看车弹层

关闭弹层返回列表

选择门店

选择时段

设置试驾圈数

确认预约?

关闭弹层完成预约

切换Tab?

查看续航柱状图

查看车龄分布

查看检测结果

查看预估价格

查看检测明细

关注复检项

增强购买信心

发起置换申请?

打开置换弹层

选择车况排量

查看置换估价

提交置换申请

七、检测报告与置换估值Tab构建

7.1 检测报告综合评分与柱状图

检测报告Tab以综合评分卡片和检测项柱状图为顶部模块,展示第三方检测的整体结果。

  @Builder tab3() {
    Column({ space: 12 }) {
      Column({ space: 10 }) {
        Row({ space: 8 }) {
          Text('🧾').fontSize(16)
          Text('第三方检测 · 综合评分').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#263238')
          Column().layoutWeight(1)
          Row({ space: 2 }) {
            Text('92').fontSize(24).fontWeight(FontWeight.Bold).fontColor('#00897B')
              .scale({ x: 1.05, y: 1.05 })
              .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            Text('分').fontSize(10).fontColor('#90A4AE')
          }
        }.width('100%')
        Row({ space: 10 }) {
          ForEach(this.checkList, (item: GoodsItem) => {
            Column({ space: 4 }) {
              Column().width(16).borderRadius(3)
                .height(item.num / this.maxNumOf(this.checkList) * 90)
                .backgroundColor(item.tag === '通过' ? '#00897B' : '#FF8F00')
              Text(item.hot).fontSize(9).fontColor('#90A4AE')
            }
          }, (item: GoodsItem) => item.name)
        }.width('100%').alignItems(VerticalAlign.End).padding({ left: 4, right: 4 })
      }.width('100%').padding(14).backgroundColor('#FFFFFF').borderRadius(12)

综合评分卡片右上角显示92分的总分,使用绿色加粗大字号并附带脉冲动画。柱状图遍历checkList的10个检测项,柱形高度通过item.num / this.maxNumOf(this.checkList) * 90计算,最大高度基准为90vp。柱子颜色根据item.tag是否等于"通过"来决定:通过为绿色,复检为橙色。每个柱子底部显示检测项类别首字(如"外"“发”“底”“内”"泡"等),通过item.hot字段获取。

7.2 检测项明细列表

      Column({ space: 10 }) {
        ForEach(this.checkList, (item: GoodsItem) => {
          Row({ space: 10 }) {
            Column().width(4).height(60).borderRadius(2)
              .backgroundColor(item.tag === '通过' ? '#00897B' : '#FF8F00')
            Column({ space: 6 }) {
              Row({ space: 8 }) {
                Text(item.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor('#263238')
                Column().layoutWeight(1)
                Text(item.tag).fontSize(10)
                  .fontColor(item.tag === '通过' ? '#00897B' : '#FF8F00')
                  .backgroundColor(item.tag === '通过' ? '#E0F2F1' : '#FFF3E0')
                  .padding({ left: 8, right: 8, top: 3, bottom: 3 }).borderRadius(8)
              }.width('100%')
              Row() {
                Column().width(item.score + '%').height(6).borderRadius(3)
                  .backgroundColor(item.tag === '通过' ? '#00897B' : '#FF8F00')
              }.width('100%').height(6).borderRadius(3).backgroundColor('#ECEFF1')
              Row({ space: 8 }) {
                Text(item.desc).fontSize(10).fontColor('#90A4AE').maxLines(1)
                Column().layoutWeight(1)
                Text(item.score + '分').fontSize(10).fontColor('#263238')
              }.width('100%')
            }.layoutWeight(1).alignItems(HorizontalAlign.Start)
          }.width('100%').padding(12).backgroundColor('#FFFFFF').borderRadius(10)
        }, (item: GoodsItem) => item.name)
      }.width('100%')

检测项明细列表每条记录左侧使用4px宽的竖条作为状态标识,通过为绿色、复检为橙色。内容区域展示检测项名称、通过/复检标签(带背景色区分)、检测覆盖率进度条、检测描述和得分。标签使用条件三元表达式同时设置文字颜色和背景颜色,通过项为绿色背景#E0F2F1,复检项为橙色背景#FFF3E0。进度条颜色同样根据检测结果动态选择,确保视觉表达与数据语义一致。复检项(如变速箱检测和尾气排放检测)通过不同的颜色标识引导用户关注潜在风险。

7.3 置换估值预估与车系列表

置换估值Tab以大字号预估价格卡片和置换车系列表为核心,展示置换交易场景。

  @Builder tab4() {
    Column({ space: 12 }) {
      Column({ space: 6 }) {
        Text('我的爱车 · 预估可卖').fontSize(11).fontColor('#B0BEC5')
        Row({ space: 2 }) {
          Text('¥').fontSize(16).fontColor('#FF8F00').fontWeight(FontWeight.Bold)
          Text(this.tradeEstimate() + '').fontSize(34).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
            .scale({ x: 1.06, y: 1.06 })
            .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
          Text('万').fontSize(12).fontColor('#FF8F00')
        }
        Text('基于近3.2万条同车系成交数据估算').fontSize(9).fontColor('#90A4AE')
      }.width('100%').padding(20).backgroundColor('#FFF8E1').borderRadius(12).alignItems(HorizontalAlign.Center)

预估价格卡片使用浅琥珀色背景#FFF8E1,价格数值使用34px大字号琥珀橙加粗显示,并附带1.06倍缩放脉冲动画。价格通过tradeEstimate()方法实时计算,当用户从列表选中不同车辆时,预估价格自动更新。卡片底部显示"基于近3.2万条同车系成交数据估算"的数据来源说明,增强估价可信度。

7.4 置换补贴力度柱状图

      Row({ space: 8 }) {
        Text('💰').fontSize(15)
        Text('置换补贴力度').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#263238')
        Column().layoutWeight(1)
        Text('单位 元').fontSize(9).fontColor('#B0BEC5')
      }.width('100%')
      Row({ space: 12 }) {
        ForEach(this.tradeList, (item: GoodsItem, index: number) => {
          Column({ space: 4 }) {
            Text(item.num + '0').fontSize(8).fontColor('#FF8F00')
            Column().width(16).borderRadius(3)
              .height(item.num / this.maxNumOf(this.tradeList) * 90)
              .backgroundColor(index % 2 === 0 ? '#FF8F00' : '#FFB300')
            Text(item.hot).fontSize(9).fontColor('#90A4AE')
          }
        }, (item: GoodsItem) => item.name)
      }.width('100%').alignItems(VerticalAlign.End).padding({ left: 4, right: 4 })
    }.width('100%').padding(12)
  }

补贴力度柱状图遍历tradeList的10个车系,柱形高度通过item.num / this.maxNumOf(this.tradeList) * 90计算。柱子标签显示item.num + '0',即将num值乘以10显示,例如num=80显示"800"元。颜色按奇偶索引交替使用#FF8F00#FFB300两种橙色调,与置换估值Tab的整体琥珀色调保持一致。柱子底部显示车系首字标签。

八、消息与个人中心Tab构建

8.1 消息列表与未读标识

消息Tab通过条件渲染实现未读消息的红点标识和"新"字标签,并使用msgTime方法格式化消息时间。

  @Builder tab5() {
    Column() {
      ForEach(this.msgList, (item: GoodsItem, index: number) => {
        Row({ space: 12 }) {
          Stack({ alignContent: Alignment.TopEnd }) {
            Text(item.hot).fontSize(16).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
              .width(46).height(46).borderRadius(23).textAlign(TextAlign.Center)
              .backgroundColor(this.colorArr[index % 4])
            if (item.tag === '未读') {
              Column().width(12).height(12).borderRadius(6).backgroundColor('#E53935')
                .scale({ x: 1.15, y: 1.15 })
                .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            }
          }.width(46).height(46)
          Column({ space: 4 }) {
            Row({ space: 8 }) {
              Text(item.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor('#263238').maxLines(1)
              Column().layoutWeight(1)
              Text(this.msgTime(item.num)).fontSize(9).fontColor('#B0BEC5')
            }.width('100%')
            Row({ space: 6 }) {
              Text(item.desc).fontSize(11).fontColor('#90A4AE').maxLines(1).layoutWeight(1)
              if (item.tag === '未读') {
                Text('新').fontSize(8).fontColor('#FFFFFF').backgroundColor('#E53935')
                  .padding({ left: 5, right: 5, top: 1, bottom: 1 }).borderRadius(6)
              }
            }.width('100%')
          }.layoutWeight(1).alignItems(HorizontalAlign.Start)
        }
        .width('100%').padding({ left: 12, right: 12, top: 10, bottom: 10 })
        .backgroundColor(index % 2 === 0 ? '#FFFFFF' : '#F5F7FA').borderRadius(10)
      }, (item: GoodsItem) => item.name)
    }.width('100%').padding(12)
  }

消息列表的头像使用Stack容器实现头像与未读红点的层叠布局。未读红点通过Alignment.TopEnd对齐到头像右上角,尺寸12x12,附带1.15倍脉冲动画。消息时间通过msgTime(item.num)方法将分钟偏移量格式化为友好文本。消息描述右侧的"新"标签同样通过条件渲染if (item.tag === '未读')控制显示。列表行背景按奇偶索引交替使用白色和浅灰色#F5F7FA,形成斑马条纹效果,提升长列表的可读性。

8.2 个人中心Tab

个人中心Tab展示用户信息、统计数据和带图标的菜单列表。

  @Builder tab6() {
    Column({ space: 12 }) {
      Row({ space: 14 }) {
        Text('车').fontSize(24).fontWeight(FontWeight.Bold).fontColor('#01579B')
          .width(64).height(64).borderRadius(32).textAlign(TextAlign.Center).backgroundColor('#FFFFFF')
        Column({ space: 6 }) {
          Text('二手车老司机').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
          Row({ space: 6 }) {
            Text('看车足迹 Lv.5').fontSize(10).fontColor('#FFECB3').backgroundColor('#FF8F00')
              .padding({ left: 8, right: 8, top: 3, bottom: 3 }).borderRadius(8)
              .scale({ x: 1.05, y: 1.05 })
              .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            Text('已看 36 台').fontSize(10).fontColor('#B0BEC5')
          }
        }.layoutWeight(1).alignItems(HorizontalAlign.Start)
        Text('›').fontSize(20).fontColor('#FFFFFF')
      }.width('100%').padding(18).backgroundColor('#01579B').borderRadius(14)
      Row() {
        Column({ space: 4 }) {
          Text('28').fontSize(20).fontWeight(FontWeight.Bold).fontColor('#0277BD')
          Text('收藏车源').fontSize(10).fontColor('#90A4AE')
        }.layoutWeight(1)
        Column().width(1).height(30).backgroundColor('#ECEFF1')
        Column({ space: 4 }) {
          Text('3').fontSize(20).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
          Text('预约看车').fontSize(10).fontColor('#90A4AE')
        }.layoutWeight(1)
        Column().width(1).height(30).backgroundColor('#ECEFF1')
        Column({ space: 4 }) {
          Text('1').fontSize(20).fontWeight(FontWeight.Bold).fontColor('#00897B')
          Text('卖车订单').fontSize(10).fontColor('#90A4AE')
        }.layoutWeight(1)
      }.width('100%').padding(14).backgroundColor('#FFFFFF').borderRadius(12)

用户信息卡片使用深蓝#01579B背景,左侧64x64白色圆形头像内显示"车"字。用户等级标签"看车足迹 Lv.5"使用琥珀橙背景金色文字并附带脉冲动画。统计数据区三等分布局展示收藏车源(28台)、预约看车(3台)和卖车订单(1个),分别使用蓝色、橙色和绿色区分数据类型。

8.3 带图标的菜单列表

      Column() {
        ForEach(this.menuList, (m: string, index: number) => {
          Row({ space: 12 }) {
            Text(this.menuIcons[index]).fontSize(16)
            Text(m).fontSize(14).fontColor('#263238')
            Column().layoutWeight(1)
            Text('›').fontSize(18).fontColor('#B0BEC5')
          }
          .width('100%').height(50).padding({ left: 14, right: 14 }).backgroundColor('#FFFFFF')
          .onClick(() => {
            if (index === 2) {
              this.showAddModal = true
            } else if (index === 3) {
              this.showTradeModal = true
            } else {
              this.currentTab = 6
            }
          })
        }, (m: string) => m)
      }.width('100%').borderRadius(12)

菜单列表通过ForEach遍历menuListmenuIcons两个并行数组,每个菜单项由emoji图标、菜单名称、弹性空白和右箭头组成。点击事件根据index进行分支处理:索引2(卖车订单)打开快速卖车弹层,索引3(估值记录)打开置换估值弹层,其余菜单项跳转到个人中心Tab。这种通过索引分发点击事件的设计模式简洁直观,适用于功能固定的菜单场景。

九、弹层交互构建

9.1 快速卖车弹层

快速卖车弹层从底部弹出,提供车系选择、车龄选择、里程输入和手机号输入功能。

  @Builder addModal() {
    Column() {
      Column().layoutWeight(1)
      Column({ space: 12 }) {
        Row({ space: 10 }) {
          Text('快速卖车').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#263238')
          Column().layoutWeight(1)
          Text('✕').fontSize(16).fontColor('#90A4AE')
            .onClick(() => {
              this.showAddModal = false
            })
        }.width('100%')
        Text('选择车系').fontSize(13).fontColor('#455A64')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.seriesChips, (chip: string, index: number) => {
            Text(chip).fontSize(12)
              .fontColor(this.chipA === index ? '#FFFFFF' : '#455A64')
              .backgroundColor(this.chipA === index ? '#0277BD' : '#ECEFF1')
              .padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(16)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => {
                this.chipA = index
              })
          }, (chip: string) => chip)
        }.width('100%')
        Text('车龄').fontSize(13).fontColor('#455A64')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.ageChips, (chip: string, index: number) => {
            Text(chip).fontSize(12)
              .fontColor(this.chipB === index ? '#FFFFFF' : '#455A64')
              .backgroundColor(this.chipB === index ? '#0277BD' : '#ECEFF1')
              .padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(16)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => {
                this.chipB = index
              })
          }, (chip: string) => chip)
        }.width('100%')
        Text('行驶里程(万公里)').fontSize(13).fontColor('#455A64')
        TextInput({ placeholder: '请输入行驶里程', text: this.mileageInput })
          .height(44).fontSize(13).backgroundColor('#ECEFF1').borderRadius(8)
          .padding({ left: 12, right: 12 })
          .onChange((value: string) => {
            this.mileageInput = value
          })
        Text('联系手机号').fontSize(13).fontColor('#455A64')
        TextInput({ placeholder: '请输入手机号,评估师30分钟内回电' })
          .height(44).fontSize(13).backgroundColor('#ECEFF1').borderRadius(8)
          .padding({ left: 12, right: 12 }).type(InputType.PhoneNumber)
          .onChange((value: string) => {
            this.phoneInput = value
          })
        Text('提交卖车申请').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
          .width('100%').height(46).textAlign(TextAlign.Center).backgroundColor('#FF8F00').borderRadius(23)
          .onClick(() => {
            this.showAddModal = false
          })
      }
      .width('100%').padding(16).backgroundColor('#FFFFFF')
      .borderRadius({ topLeft: 16, topRight: 16 })
      .constraintSize({ maxHeight: '80%' })
      .onClick((event: ClickEvent) => {
        event.stopPropagation()
      })
    }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
    .onClick(() => {
      this.showAddModal = false
    })
  }

快速卖车弹层是本应用中最复杂的表单弹层,集成了两组Chip选择和两个TextInput输入框。车系Chip使用chipA索引控制选中态,车龄Chip使用chipB索引。里程输入使用TextInputtext参数绑定this.mileageInput状态变量,通过onChange回调将输入值同步到状态。手机号输入设置type(InputType.PhoneNumber)启用数字键盘。提交按钮使用琥珀橙胶囊形设计。弹层结构遵循底部弹出模式:遮罩层layoutWeight(1)将面板推至底部,面板设置顶部圆角和最大高度限制,内部stopPropagation阻止事件冒泡。

9.2 置换估值弹层

置换估值弹层提供车况选择、排量选择和实时估价计算功能。

  @Builder tradeModal() {
    Column() {
      Column().layoutWeight(1)
      Column({ space: 12 }) {
        Row({ space: 10 }) {
          Text('置换估值').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#263238')
          Column().layoutWeight(1)
          Text('✕').fontSize(16).fontColor('#90A4AE')
            .onClick(() => {
              this.showTradeModal = false
            })
        }.width('100%')
        Text('车况').fontSize(13).fontColor('#455A64')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.condChips, (chip: string, index: number) => {
            Text(chip).fontSize(12)
              .fontColor(this.chipA === index ? '#FFFFFF' : '#455A64')
              .backgroundColor(this.chipA === index ? '#FF8F00' : '#ECEFF1')
              .padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(16)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => {
                this.chipA = index
              })
          }, (chip: string) => chip)
        }.width('100%')
        Text('排量').fontSize(13).fontColor('#455A64')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.dispChips, (chip: string, index: number) => {
            Text(chip).fontSize(12)
              .fontColor(this.chipB === index ? '#FFFFFF' : '#455A64')
              .backgroundColor(this.chipB === index ? '#FF8F00' : '#ECEFF1')
              .padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(16)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => {
                this.chipB = index
              })
          }, (chip: string) => chip)
        }.width('100%')
        Column({ space: 4 }) {
          Text('预估置换价').fontSize(11).fontColor('#B0BEC5')
          Row({ space: 2 }) {
            Text('¥').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
            Text(this.tradeEstimate() + '').fontSize(32).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
              .scale({ x: 1.06, y: 1.06 })
              .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            Text('万').fontSize(12).fontColor('#FF8F00')
          }
          Text('按车况 ' + this.condChips[this.chipA] + ' · 排量 ' + this.dispChips[this.chipB] + ' 估算')
            .fontSize(9).fontColor('#90A4AE')
        }.width('100%').padding(18).backgroundColor('#FFF8E1').borderRadius(12).alignItems(HorizontalAlign.Center)
        Text('提交置换申请').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
          .width('100%').height(46).textAlign(TextAlign.Center).backgroundColor('#FF8F00').borderRadius(23)
          .onClick(() => {
            this.showTradeModal = false
          })
      }
      .width('100%').padding(16).backgroundColor('#FFFFFF')
      .borderRadius({ topLeft: 16, topRight: 16 })
      .constraintSize({ maxHeight: '80%' })
      .onClick((event: ClickEvent) => {
        event.stopPropagation()
      })
    }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
    .onClick(() => {
      this.showTradeModal = false
    })
  }

置换弹层中车况Chip使用琥珀橙选中态(#FF8F00),与快速卖车弹层的蓝色选中态形成区分。估价卡片底部显示"按车况 优秀 · 排量 2.0T 估算"的动态文本,通过this.condChips[this.chipA]this.dispChips[this.chipB]将选中的Chip索引映射为文本描述。预估价格通过tradeEstimate()方法实时计算,当用户切换车况或排量Chip时,虽然tradeEstimate()方法本身只依赖selectedItem,但卡片底部的描述文本会即时更新选中项名称,体现了状态联动更新机制。

9.3 详情弹层与主构建方法

详情弹层从右侧滑出,展示车辆完整信息和配置亮点标签。

  @Builder detailModal() {
    Column() {
      Column({ space: 10 }) {
        Text('🚘').fontSize(60)
        Text(this.selTag()).fontSize(11).fontColor('#FFFFFF').backgroundColor('rgba(255,255,255,0.25)')
          .padding({ left: 10, right: 10, top: 4, bottom: 4 }).borderRadius(10)
      }.width('100%').height(210).backgroundColor('#0277BD').justifyContent(FlexAlign.Center)
      Column({ space: 12 }) {
        Text(this.selName()).fontSize(19).fontWeight(FontWeight.Bold).fontColor('#263238').maxLines(2)
        Text(this.selDesc()).fontSize(11).fontColor('#90A4AE')
        Row({ space: 2 }) {
          Text('¥').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
          Text((this.selPrice() / 10).toFixed(1)).fontSize(32).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
            .scale({ x: 1.06, y: 1.06 })
            .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
          Text('万').fontSize(12).fontColor('#FF8F00')
        }
        // ... 检测评分进度条
        Text('配置亮点').fontSize(13).fontWeight(FontWeight.Bold).fontColor('#263238')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.configChips, (chip: string) => {
            Text(chip).fontSize(11).fontColor('#0277BD').backgroundColor('#E1F5FE')
              .padding({ left: 10, right: 10, top: 5, bottom: 5 }).borderRadius(12)
              .margin({ bottom: 8 })
          }, (chip: string) => chip)
        }.width('100%')
        Column().layoutWeight(1)
        Row({ space: 10 }) {
          Text('删除预约').fontSize(13).fontColor('#90A4AE')
            .width(96).height(44).textAlign(TextAlign.Center).backgroundColor('#ECEFF1').borderRadius(22)
            .onClick(() => {
              this.showDetailModal = false
              this.showDeleteModal = true
            })
          Text('预约看车').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
            .layoutWeight(1).height(44).textAlign(TextAlign.Center).backgroundColor('#FF8F00').borderRadius(22)
            .onClick(() => {
              this.showDetailModal = false
              this.showBookModal = true
            })
        }.width('100%')
      }.width('100%').padding(16).layoutWeight(1)
    }
    .width('80%').height('100%').backgroundColor('#FFFFFF')
    .onClick((event: ClickEvent) => {
      event.stopPropagation()
    })
  }

详情弹层顶部210vp高的汽车蓝区域展示车辆emoji图标和标签。价格使用32px大字号琥珀橙加粗并附带脉冲动画。配置亮点通过FlexWrap换行布局展示六个配置Chip,使用蓝色文字配浅蓝背景。底部双按钮设计:左侧"删除预约"灰色按钮打开删除确认弹层,右侧"预约看车"橙色按钮打开预约看车弹层,实现了弹层间的跳转流转。

9.4 主构建方法build()

  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%').backgroundColor('#F5F7FA')
        this.bottomBuilder()
      }.width('100%').height('100%')
      if (this.showAddModal) {
        this.addModal()
      }
      if (this.showBookModal) {
        this.bookModal()
      }
      if (this.showTradeModal) {
        this.tradeModal()
      }
      if (this.showDeleteModal) {
        this.deleteModal()
      }
      if (this.showDetailModal) {
        this.detailModal()
      }
    }.width('100%').height('100%')
  }

build()方法使用Stack作为最外层容器。主页面Column包含头部、Scroll内容区和底部导航栏。内容区通过if-else条件分支根据currentTab渲染对应Tab。五个弹层通过各自的条件渲染语句控制显示,层叠在主页面之上。内容区背景设为#F5F7FA浅灰色,与白色卡片形成微妙的层次对比。

十、技术点对比分析

技术维度 精选车源Tab 新能源二手Tab SUV专区Tab 检测报告Tab 置换估值Tab 消息Tab 个人中心Tab
布局容器 Scroll + ForEach Flex双列网格 Scroll画廊 Column + ForEach Column + ForEach ForEach Column + ForEach
数据量 24条+3条Banner 12条 10条 10条 10条 12条 静态
可视化组件 进度条 续航柱状图 车龄柱状图 检测柱状图 补贴柱状图 红点标识 渐变统计卡
主色调 #0277BD #00897B #37474F #00897B/#FF8F00 #FF8F00 #E53935 #01579B
动画效果 热度标签脉冲 电池健康脉冲 在售数量脉冲 综合评分脉冲 预估价格脉冲 红点脉冲 等级标签脉冲
点击交互 打开详情弹层 打开详情弹层 打开详情弹层 打开置换弹层 菜单跳转弹层
价格转换 price/10+toFixed(1) price/10+toFixed(1) price/10+toFixed(1) tradeEstimate() 静态文本

安装DevEco Studio程序

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

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

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

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

在这里插入图片描述


完整代码:

// 二手车精选行 · 汽车之家风格二手车交易场景
// 配色 汽车蓝 琥珀橙 深灰蓝 浅灰 警示红
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 showBookModal: boolean = false
  @State showTradeModal: 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 mileageInput: string = ''
  @State phoneInput: string = ''
  private tabs: string[] = ['精选车源', '新能源二手', 'SUV专区', '检测报告', '置换估值', '消息', '我的']
  private seriesChips: string[] = ['大众朗逸', '丰田凯美瑞', '本田雅阁', '特斯拉Model 3', '比亚迪汉']
  private ageChips: string[] = ['1-3年', '3-5年', '5-8年', '8年以上']
  private storeChips: string[] = ['浦东店', '徐汇店', '静安店', '闵行店']
  private slotChips: string[] = ['上午', '下午', '晚间']
  private condChips: string[] = ['优秀', '良好', '一般']
  private dispChips: string[] = ['1.5T', '2.0T', '2.5L', '纯电']
  private configChips: string[] = ['无钥匙进入', '倒车影像', '定速巡航', 'CarPlay', '座椅加热', '全景天窗']
  private menuList: string[] = ['我的收藏', '浏览足迹', '卖车订单', '估值记录', '收货地址', '设置']
  private menuIcons: string[] = ['⭐', '👣', '📦', '💰', '📍', '🛠']
  private colorArr: string[] = ['#0277BD', '#FF8F00', '#37474F', '#00897B']
  private bannerColors: string[] = ['#01579B', '#37474F', '#00695C']
  private bannerList: GoodsItem[] = [
    {
      name: '特斯拉Model 3 2021款 标准续航',
      price: 175,
      desc: '2021年上牌 · 3.9万公里 · 电池健康95%',
      score: 95,
      num: 39,
      tag: '新能源精选',
      hot: '急'
    },
    {
      name: '宝马3系 2019款 325Li 首发版',
      price: 228,
      desc: '2019年上牌 · 7.8万公里 · 尊选认证',
      score: 89,
      num: 78,
      tag: '豪华精选',
      hot: '热'
    },
    {
      name: '丰田凯美瑞 2020款 2.0G 豪华版',
      price: 135,
      desc: '2020年上牌 · 5.6万公里 · 4S店保养',
      score: 88,
      num: 56,
      tag: '家用首选',
      hot: '荐'
    }
  ]
  private mainList: GoodsItem[] = [
    {
      name: '大众朗逸 2021款 1.5L 自动',
      price: 78,
      desc: '2021年上牌 · 4.2万公里',
      score: 92,
      num: 42,
      tag: '分期免息',
      hot: '荐'
    },
    {
      name: '丰田凯美瑞 2020款 2.0G 豪华',
      price: 135,
      desc: '2020年上牌 · 5.6万公里',
      score: 88,
      num: 56,
      tag: '一口价',
      hot: '热'
    },
    {
      name: '本田雅阁 2019款 260TURBO',
      price: 128,
      desc: '2019年上牌 · 6.8万公里',
      score: 85,
      num: 68,
      tag: '准新车',
      hot: '荐'
    },
    {
      name: '特斯拉Model 3 2021款 标续',
      price: 175,
      desc: '2021年上牌 · 3.9万公里',
      score: 95,
      num: 39,
      tag: '电池无忧',
      hot: '新'
    },
    {
      name: '比亚迪汉EV 2021款 旗舰',
      price: 168,
      desc: '2021年上牌 · 4.5万公里',
      score: 93,
      num: 45,
      tag: '官方认证',
      hot: '热'
    },
    {
      name: '日产轩逸 2020款 1.6L CVT',
      price: 82,
      desc: '2020年上牌 · 6.2万公里',
      score: 86,
      num: 62,
      tag: '分期免息',
      hot: '荐'
    },
    {
      name: '本田思域 2020款 220TURBO',
      price: 105,
      desc: '2020年上牌 · 5.1万公里',
      score: 90,
      num: 51,
      tag: '一口价',
      hot: '热'
    },
    {
      name: '大众速腾 2019款 280TSI',
      price: 98,
      desc: '2019年上牌 · 7.4万公里',
      score: 83,
      num: 74,
      tag: '准新车',
      hot: ''
    },
    {
      name: '丰田卡罗拉 2021款 1.2T',
      price: 88,
      desc: '2021年上牌 · 3.6万公里',
      score: 91,
      num: 36,
      tag: '分期免息',
      hot: '荐'
    },
    {
      name: '本田CR-V 2019款 240TURBO',
      price: 142,
      desc: '2019年上牌 · 8.1万公里',
      score: 82,
      num: 81,
      tag: 'SUV精选',
      hot: '热'
    },
    {
      name: '大众途观L 2020款 330TSI',
      price: 158,
      desc: '2020年上牌 · 6.5万公里',
      score: 87,
      num: 65,
      tag: '一口价',
      hot: '荐'
    },
    {
      name: '宝马3系 2019款 325Li',
      price: 228,
      desc: '2019年上牌 · 7.8万公里',
      score: 89,
      num: 78,
      tag: '尊选二手车',
      hot: '热'
    },
    {
      name: '奥迪A4L 2020款 40TFSI',
      price: 212,
      desc: '2020年上牌 · 5.9万公里',
      score: 88,
      num: 59,
      tag: '官方认证',
      hot: '荐'
    },
    {
      name: '奔驰C级 2019款 C260L',
      price: 235,
      desc: '2019年上牌 · 8.6万公里',
      score: 84,
      num: 86,
      tag: '一口价',
      hot: '热'
    },
    {
      name: '大众帕萨特 2020款 330TSI',
      price: 152,
      desc: '2020年上牌 · 6.9万公里',
      score: 85,
      num: 69,
      tag: '分期免息',
      hot: ''
    },
    {
      name: '丰田RAV4 2020款 2.0L CVT',
      price: 148,
      desc: '2020年上牌 · 5.4万公里',
      score: 90,
      num: 54,
      tag: 'SUV精选',
      hot: '荐'
    },
    {
      name: '本田XR-V 2021款 1.5L',
      price: 96,
      desc: '2021年上牌 · 3.2万公里',
      score: 93,
      num: 32,
      tag: '准新车',
      hot: '新'
    },
    {
      name: '日产天籁 2019款 2.0L',
      price: 102,
      desc: '2019年上牌 · 8.8万公里',
      score: 81,
      num: 88,
      tag: '一口价',
      hot: ''
    },
    {
      name: '别克君威 2020款 552T',
      price: 108,
      desc: '2020年上牌 · 6.1万公里',
      score: 84,
      num: 61,
      tag: '分期免息',
      hot: '荐'
    },
    {
      name: '马自达阿特兹 2020款 2.0L',
      price: 115,
      desc: '2020年上牌 · 5.7万公里',
      score: 87,
      num: 57,
      tag: '准新车',
      hot: '热'
    },
    {
      name: '大众高尔夫 2021款 280TSI',
      price: 112,
      desc: '2021年上牌 · 2.8万公里',
      score: 94,
      num: 28,
      tag: '一口价',
      hot: '新'
    },
    {
      name: '丰田亚洲龙 2019款 2.5L',
      price: 138,
      desc: '2019年上牌 · 9.2万公里',
      score: 80,
      num: 92,
      tag: '官方认证',
      hot: ''
    },
    {
      name: '沃尔沃S60 2020款 T4',
      price: 178,
      desc: '2020年上牌 · 5.3万公里',
      score: 88,
      num: 53,
      tag: '尊选二手车',
      hot: '荐'
    },
    {
      name: '比亚迪秦PLUS 2022款 DM-i',
      price: 98,
      desc: '2022年上牌 · 2.1万公里',
      score: 96,
      num: 21,
      tag: '新能源',
      hot: '新'
    }
  ]
  private evList: GoodsItem[] = [
    {
      name: '比亚迪海豚 2022款 骑士版',
      price: 68,
      desc: '2022年上牌 · 1.8万公里',
      score: 96,
      num: 420,
      tag: '首任车主',
      hot: '纯电'
    },
    {
      name: '特斯拉Model Y 2021款 长续航',
      price: 215,
      desc: '2021年上牌 · 4.6万公里',
      score: 91,
      num: 480,
      tag: '电池无忧',
      hot: '纯电'
    },
    {
      name: '比亚迪元PLUS 2022款 旗舰',
      price: 95,
      desc: '2022年上牌 · 2.4万公里',
      score: 95,
      num: 430,
      tag: '官方认证',
      hot: '纯电'
    },
    {
      name: '广汽AION S 2021款 魅580',
      price: 88,
      desc: '2021年上牌 · 5.2万公里',
      score: 88,
      num: 460,
      tag: '非营运',
      hot: '纯电'
    },
    {
      name: '蔚来ES6 2020款 性能版',
      price: 235,
      desc: '2020年上牌 · 6.8万公里',
      score: 86,
      num: 410,
      tag: '换电无忧',
      hot: '纯电'
    },
    {
      name: '小鹏P7 2021款 后驱长续航',
      price: 178,
      desc: '2021年上牌 · 4.1万公里',
      score: 90,
      num: 470,
      tag: '智驾领先',
      hot: '纯电'
    },
    {
      name: '特斯拉Model 3 2020款 标续',
      price: 152,
      desc: '2020年上牌 · 7.3万公里',
      score: 85,
      num: 405,
      tag: '急售',
      hot: '纯电'
    },
    {
      name: '比亚迪秦PLUS EV 2021款',
      price: 92,
      desc: '2021年上牌 · 5.6万公里',
      score: 89,
      num: 400,
      tag: '分期免息',
      hot: '纯电'
    },
    {
      name: '哪吒U 2022款 500版',
      price: 78,
      desc: '2022年上牌 · 2.0万公里',
      score: 93,
      num: 450,
      tag: '准新车',
      hot: '纯电'
    },
    {
      name: '零跑C11 2022款 豪华版',
      price: 118,
      desc: '2022年上牌 · 2.7万公里',
      score: 94,
      num: 475,
      tag: '官方认证',
      hot: '纯电'
    },
    {
      name: '欧拉好猫 2021款 400km',
      price: 72,
      desc: '2021年上牌 · 3.4万公里',
      score: 90,
      num: 380,
      tag: '女士一手',
      hot: '纯电'
    },
    {
      name: '荣威Ei5 2020款 500km',
      price: 62,
      desc: '2020年上牌 · 8.2万公里',
      score: 83,
      num: 415,
      tag: '一口价',
      hot: '纯电'
    }
  ]
  private suvList: GoodsItem[] = [
    {
      name: '哈弗H6 2020款 1.5T',
      price: 78,
      desc: '2020年上牌 · 6.4万公里',
      score: 85,
      num: 68,
      tag: '国民SUV',
      hot: '热'
    },
    {
      name: '吉利博越 2019款 1.8T',
      price: 68,
      desc: '2019年上牌 · 8.7万公里',
      score: 81,
      num: 83,
      tag: '一口价',
      hot: ''
    },
    {
      name: '长安CS75PLUS 2021款 2.0T',
      price: 98,
      desc: '2021年上牌 · 4.3万公里',
      score: 89,
      num: 45,
      tag: '准新车',
      hot: '荐'
    },
    {
      name: '本田CR-V 2019款 240TURBO',
      price: 142,
      desc: '2019年上牌 · 8.1万公里',
      score: 82,
      num: 81,
      tag: '保值王',
      hot: '热'
    },
    {
      name: '丰田汉兰达 2018款 2.0T',
      price: 205,
      desc: '2018年上牌 · 11.2万公里',
      score: 78,
      num: 96,
      tag: '七座',
      hot: '荐'
    },
    {
      name: '大众途观L 2020款 330TSI',
      price: 158,
      desc: '2020年上牌 · 6.5万公里',
      score: 87,
      num: 65,
      tag: '官方认证',
      hot: '热'
    },
    {
      name: '日产奇骏 2019款 2.5L',
      price: 122,
      desc: '2019年上牌 · 9.0万公里',
      score: 80,
      num: 88,
      tag: '四驱',
      hot: ''
    },
    {
      name: '别克昂科威 2020款 28T',
      price: 112,
      desc: '2020年上牌 · 7.1万公里',
      score: 84,
      num: 72,
      tag: '分期免息',
      hot: '荐'
    },
    {
      name: '宝马X3 2020款 xDrive28i',
      price: 298,
      desc: '2020年上牌 · 5.8万公里',
      score: 90,
      num: 58,
      tag: '尊选',
      hot: '热'
    },
    {
      name: '奥迪Q5L 2019款 45TFSI',
      price: 265,
      desc: '2019年上牌 · 8.9万公里',
      score: 86,
      num: 92,
      tag: '一口价',
      hot: '荐'
    }
  ]
  private checkList: GoodsItem[] = [
    {
      name: '外观漆面检测',
      price: 0,
      desc: '右前门补漆1处 · 覆盖件无伤',
      score: 92,
      num: 460,
      tag: '通过',
      hot: '外'
    },
    {
      name: '发动机舱检测',
      price: 0,
      desc: '无拆修记录 · 渗油检查正常',
      score: 96,
      num: 480,
      tag: '通过',
      hot: '发'
    },
    {
      name: '底盘悬挂检测',
      price: 0,
      desc: '无托底变形 · 减震无漏油',
      score: 88,
      num: 350,
      tag: '通过',
      hot: '底'
    },
    {
      name: '内饰功能检测',
      price: 0,
      desc: '座椅磨损轻微 · 电子件正常',
      score: 90,
      num: 420,
      tag: '通过',
      hot: '内'
    },
    {
      name: '泡水痕迹排查',
      price: 0,
      desc: '地毯线束干燥 · 无水渍锈迹',
      score: 99,
      num: 495,
      tag: '通过',
      hot: '泡'
    },
    {
      name: '火烧痕迹排查',
      price: 0,
      desc: '线束原厂状态 · 无熔蚀变形',
      score: 98,
      num: 490,
      tag: '通过',
      hot: '火'
    },
    {
      name: '调表风险核查',
      price: 0,
      desc: '维保里程连贯 · 判定无调表',
      score: 95,
      num: 300,
      tag: '通过',
      hot: '表'
    },
    {
      name: '变速箱检测',
      price: 0,
      desc: '换挡平顺 · 建议复查阀体',
      score: 78,
      num: 455,
      tag: '复检',
      hot: '变'
    },
    {
      name: '安全气囊检测',
      price: 0,
      desc: '气囊电脑无故障码',
      score: 97,
      num: 485,
      tag: '通过',
      hot: '囊'
    },
    {
      name: '尾气排放检测',
      price: 0,
      desc: '接近限值 · 建议清洗三元',
      score: 76,
      num: 260,
      tag: '复检',
      hot: '尾'
    }
  ]
  private tradeList: GoodsItem[] = [
    {
      name: '大众朗逸',
      price: 65,
      desc: '置换补贴至高8000元',
      score: 88,
      num: 80,
      tag: '限时补贴',
      hot: '朗'
    },
    {
      name: '丰田凯美瑞',
      price: 105,
      desc: '置换补贴至高12000元',
      score: 92,
      num: 120,
      tag: '厂家认证',
      hot: '凯'
    },
    {
      name: '本田雅阁',
      price: 98,
      desc: '置换补贴至高10000元',
      score: 90,
      num: 100,
      tag: '限时补贴',
      hot: '雅'
    },
    {
      name: '特斯拉Model 3',
      price: 168,
      desc: '置换享保险补贴',
      score: 95,
      num: 200,
      tag: '新能源',
      hot: 'T'
    },
    {
      name: '比亚迪汉',
      price: 155,
      desc: '置换补贴至高15000元',
      score: 94,
      num: 150,
      tag: '新能源',
      hot: '汉'
    },
    {
      name: '宝马3系',
      price: 235,
      desc: '置换享金融贴息',
      score: 90,
      num: 300,
      tag: '尊选',
      hot: '宝'
    },
    {
      name: '奥迪A4L',
      price: 218,
      desc: '置换赠保养礼包',
      score: 89,
      num: 280,
      tag: '官方认证',
      hot: '奥'
    },
    {
      name: '大众途观L',
      price: 152,
      desc: '置换补贴至高9000元',
      score: 87,
      num: 90,
      tag: '限时补贴',
      hot: '途'
    },
    {
      name: '丰田RAV4',
      price: 145,
      desc: '置换补贴至高11000元',
      score: 91,
      num: 110,
      tag: '厂家认证',
      hot: 'R'
    },
    {
      name: '比亚迪秦PLUS',
      price: 92,
      desc: '置换补贴至高13000元',
      score: 93,
      num: 130,
      tag: '新能源',
      hot: '秦'
    }
  ]
  private msgList: GoodsItem[] = [
    {
      name: '看车顾问小陈',
      price: 0,
      desc: '您预约的朗逸今天下午可看车,展厅已留车',
      score: 0,
      num: 10,
      tag: '未读',
      hot: '陈'
    },
    {
      name: '降价提醒',
      price: 0,
      desc: '您收藏的凯美瑞降价5000元,手慢无',
      score: 0,
      num: 30,
      tag: '未读',
      hot: '降'
    },
    {
      name: '评估师老王',
      price: 0,
      desc: '您的爱车评估报告已生成,点击查看详情',
      score: 0,
      num: 60,
      tag: '未读',
      hot: '王'
    },
    {
      name: '置换顾问Lisa',
      price: 0,
      desc: '置换补贴月底截止,别错过这波福利',
      score: 0,
      num: 120,
      tag: '未读',
      hot: 'L'
    },
    {
      name: '金融专员小刘',
      price: 0,
      desc: '您的分期方案已审批通过,额度8万元',
      score: 0,
      num: 180,
      tag: '未读',
      hot: '刘'
    },
    {
      name: '车主直售',
      price: 0,
      desc: '我那台雅阁可以再聊500块,您考虑一下',
      score: 0,
      num: 240,
      tag: '已读',
      hot: '车'
    },
    {
      name: '上新提醒',
      price: 0,
      desc: '本周浦东新上架车源36台,点击逛一逛',
      score: 0,
      num: 300,
      tag: '已读',
      hot: '新'
    },
    {
      name: '检测中心',
      price: 0,
      desc: '您预约的第三方检测已完成,报告可下载',
      score: 0,
      num: 420,
      tag: '已读',
      hot: '检'
    },
    {
      name: '看车顾问小周',
      price: 0,
      desc: '上周看的那台思域已售,相似车源已整理',
      score: 0,
      num: 600,
      tag: '已读',
      hot: '周'
    },
    {
      name: '售后回访',
      price: 0,
      desc: '对本次看车服务还满意吗,欢迎评价',
      score: 0,
      num: 720,
      tag: '已读',
      hot: '售'
    },
    {
      name: '置换顾问Amy',
      price: 0,
      desc: '您关注的车系本周新增置换名额2个',
      score: 0,
      num: 900,
      tag: '已读',
      hot: 'A'
    },
    {
      name: '安全中心',
      price: 0,
      desc: '账户安全提醒,新设备登录已通过验证',
      score: 0,
      num: 1440,
      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
  }
  selTag(): string {
    if (this.selectedItem !== null) {
      return this.selectedItem.tag
    }
    return '官方认证'
  }
  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
  }
  msgTime(n: number): string {
    if (n < 60) {
      return n + '分钟前'
    }
    if (n < 1440) {
      return Math.floor(n / 60) + '小时前'
    }
    return Math.floor(n / 1440) + '天前'
  }
  tradeEstimate(): number {
    let base: number = 78
    if (this.selectedItem !== null) {
      base = this.selectedItem.price
    }
    return Math.floor(base * 0.85) / 10
  }

  @Builder headerBuilder() {
    Column() {
      Row() {
        Row({ space: 4 }) {
          Text('📍').fontSize(14)
          Text('上海·浦东').fontSize(13).fontColor('#FFFFFF')
        }
        Column().layoutWeight(1)
        Text('🎧').fontSize(18)
      }.width('100%').padding({ left: 16, right: 16, top: 12 })
      Row({ space: 10 }) {
        Text('二手车精选').fontSize(22).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
        Row({ space: 6 }) {
          Text('🔍').fontSize(12)
          Text('搜车系/车型').fontSize(12).fontColor('#78909C')
          Column().layoutWeight(1)
          Text('📷').fontSize(13)
        }
        .layoutWeight(1).height(32).borderRadius(16).backgroundColor('#ECEFF1')
        .padding({ left: 12, right: 12 })
      }.width('100%').padding({ left: 16, right: 16, top: 10, bottom: 10 })
      Row({ space: 6 }) {
        Text('⚡').fontSize(13)
        Text('急速卖车 · 30分钟上门评估').fontSize(12).fontColor('#FFFFFF').fontWeight(FontWeight.Medium)
        Column().layoutWeight(1)
        Text('立即发布 ›').fontSize(11).fontColor('#FFE0B2')
      }
      .width('100%').height(36).backgroundColor('#FF8F00')
      .padding({ left: 16, right: 16 })
      .onClick(() => {
        this.showAddModal = true
      })
    }.width('100%').backgroundColor('#0277BD')
  }

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

  @Builder tab0() {
    Column({ space: 12 }) {
      Row({ space: 8 }) {
        Text('🔥').fontSize(16)
        Text('今日精选大卡').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#263238')
        Column().layoutWeight(1)
        Text('左滑查看更多').fontSize(10).fontColor('#B0BEC5')
      }.width('100%')
      Scroll() {
        Row({ space: 12 }) {
          ForEach(this.bannerList, (item: GoodsItem, index: number) => {
            Column({ space: 8 }) {
              Row() {
                Text(item.tag).fontSize(10).fontColor('#FFFFFF').backgroundColor('rgba(255,255,255,0.25)')
                  .padding({ left: 8, right: 8, top: 3, bottom: 3 }).borderRadius(8)
                Column().layoutWeight(1)
                Text(item.hot).fontSize(12).fontColor('#FFFFFF').backgroundColor('#FF8F00')
                  .width(24).height(24).borderRadius(12).textAlign(TextAlign.Center)
                  .scale({ x: 1.05, y: 1.05 })
                  .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
              }.width('100%')
              Column({ space: 4 }) {
                Text(item.name).fontSize(16).fontWeight(FontWeight.Bold).fontColor('#FFFFFF').maxLines(1)
                Text(item.desc).fontSize(10).fontColor('#CFD8DC').maxLines(1)
              }.width('100%').alignItems(HorizontalAlign.Start)
              Column().layoutWeight(1)
              Row() {
                Row({ space: 2 }) {
                  Text((item.price / 10).toFixed(1)).fontSize(22).fontWeight(FontWeight.Bold).fontColor('#FFD54F')
                  Text('万').fontSize(10).fontColor('#FFD54F')
                }
                Column().layoutWeight(1)
                Text('立即看车 ›').fontSize(11).fontColor('#FFFFFF')
              }.width('100%')
            }
            .width(300).height(170).borderRadius(14).padding(14)
            .backgroundColor(this.bannerColors[index])
            .onClick(() => {
              this.selectedItem = item
              this.showDetailModal = true
            })
          }, (item: GoodsItem) => item.name)
        }.padding({ left: 2, right: 2 })
      }.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).width('100%')
      Row({ space: 8 }) {
        Text('🚗').fontSize(15)
        Text('精选车源 ' + this.mainList.length + ' 台').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#263238')
        Column().layoutWeight(1)
        Text('按热度排序').fontSize(10).fontColor('#B0BEC5')
      }.width('100%')
      Column({ space: 10 }) {
        ForEach(this.mainList, (item: GoodsItem, index: number) => {
          Row({ space: 12 }) {
            Text(item.name.substring(0, 1)).fontSize(20).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
              .width(52).height(52).borderRadius(26).textAlign(TextAlign.Center)
              .backgroundColor(this.colorArr[index % 4])
            Column({ space: 5 }) {
              Row({ space: 6 }) {
                Text(item.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor('#263238').maxLines(1)
                if (item.hot !== '') {
                  Text(item.hot).fontSize(9).fontColor('#FFFFFF').backgroundColor('#E53935')
                    .padding({ left: 5, right: 5, top: 2, bottom: 2 }).borderRadius(4)
                    .scale({ x: 1.05, y: 1.05 })
                    .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
                }
              }.width('100%')
              Text(item.desc).fontSize(11).fontColor('#90A4AE').maxLines(1)
              Row() {
                Column().width(item.score + '%').height(5).borderRadius(3).backgroundColor('#0277BD')
              }.width('100%').height(5).borderRadius(3).backgroundColor('#ECEFF1')
              Row({ space: 6 }) {
                Text('表显里程 ' + (item.num / 10).toFixed(1) + '万km').fontSize(10).fontColor('#90A4AE')
                Text(item.tag).fontSize(10).fontColor('#0277BD').backgroundColor('#E1F5FE')
                  .padding({ left: 6, right: 6, top: 2, bottom: 2 }).borderRadius(4)
              }
            }.layoutWeight(1).alignItems(HorizontalAlign.Start)
            Column({ space: 6 }) {
              Row({ space: 2 }) {
                Text((item.price / 10).toFixed(1)).fontSize(19).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
                Text('万').fontSize(10).fontColor('#FF8F00')
              }
              Text('首付' + Math.floor(item.price / 10 / 3) + '万起').fontSize(9).fontColor('#B0BEC5')
            }.alignItems(HorizontalAlign.End)
          }
          .width('100%').padding(12).backgroundColor('#FFFFFF').borderRadius(12)
          .onClick(() => {
            this.selectedItem = item
            this.showDetailModal = true
          })
        }, (item: GoodsItem) => item.name)
      }.width('100%')
    }.width('100%').padding(12)
  }

  @Builder tab1() {
    Column({ space: 12 }) {
      Row({ space: 8 }) {
        Text('⚡').fontSize(16)
        Text('新能源续航榜').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#263238')
        Column().layoutWeight(1)
        Text('共' + this.evList.length + '台').fontSize(11).fontColor('#90A4AE')
      }.width('100%')
      Row({ space: 9 }) {
        ForEach(this.evList, (item: GoodsItem, index: number) => {
          Column({ space: 5 }) {
            Text(item.num + '').fontSize(8).fontColor('#00897B')
            Column().width(18).borderRadius(4)
              .height(item.num / this.maxNumOf(this.evList) * 120)
              .backgroundColor(index % 2 === 0 ? '#00897B' : '#4DB6AC')
            Text(item.name.substring(0, 2)).fontSize(8).fontColor('#90A4AE').maxLines(1)
          }
        }, (item: GoodsItem) => item.name)
      }.width('100%').padding({ left: 4, right: 4 })
      Row({ space: 8 }) {
        Text('🔋').fontSize(15)
        Text('新能源二手好车').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#263238')
        Column().layoutWeight(1)
        Text('双列网格').fontSize(10).fontColor('#B0BEC5')
      }.width('100%')
      Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
        ForEach(this.evList, (item: GoodsItem, index: number) => {
          Column({ space: 8 }) {
            Row() {
              Text(item.hot).fontSize(9).fontColor('#FFFFFF').backgroundColor('#00897B')
                .padding({ left: 6, right: 6, top: 2, bottom: 2 }).borderRadius(4)
              Column().layoutWeight(1)
              Text(item.num + 'km').fontSize(10).fontColor('#00897B').fontWeight(FontWeight.Bold)
            }.width('100%')
            Text(item.name).fontSize(13).fontWeight(FontWeight.Bold).fontColor('#263238').maxLines(1)
            Text(item.desc).fontSize(10).fontColor('#90A4AE').maxLines(1)
            Row() {
              Column().width(item.score + '%').height(6).borderRadius(3)
                .backgroundColor(item.score >= 90 ? '#00897B' : '#FF8F00')
                .scale({ x: 1.05, y: 1.05 })
                .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            }.width('100%').height(6).borderRadius(3).backgroundColor('#ECEFF1')
            Row() {
              Text('电池健康 ' + item.score + '%').fontSize(9).fontColor('#90A4AE')
              Column().layoutWeight(1)
              Row({ space: 2 }) {
                Text((item.price / 10).toFixed(1)).fontSize(15).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
                Text('万').fontSize(9).fontColor('#FF8F00')
              }
            }.width('100%')
            Text('月供' + Math.floor(item.price / 10 * 10000 / 36) + '元起').fontSize(9).fontColor('#B0BEC5')
          }
          .width('48%').padding(12).backgroundColor('#FFFFFF').borderRadius(12)
          .onClick(() => {
            this.selectedItem = item
            this.showDetailModal = true
          })
        }, (item: GoodsItem) => item.name)
      }.width('100%')
    }.width('100%').padding(12)
  }

  @Builder tab2() {
    Column({ space: 12 }) {
      Row() {
        Column({ space: 4 }) {
          Text('128').fontSize(22).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
            .scale({ x: 1.06, y: 1.06 })
            .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
          Text('在售SUV').fontSize(10).fontColor('#B0BEC5')
        }.layoutWeight(1)
        Column().width(1).height(34).backgroundColor('#546E7A')
        Column({ space: 4 }) {
          Text('12').fontSize(22).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
          Text('今日上新').fontSize(10).fontColor('#B0BEC5')
        }.layoutWeight(1)
        Column().width(1).height(34).backgroundColor('#546E7A')
        Column({ space: 4 }) {
          Text('68%').fontSize(22).fontWeight(FontWeight.Bold).fontColor('#4DB6AC')
          Text('30天成交率').fontSize(10).fontColor('#B0BEC5')
        }.layoutWeight(1)
      }.width('100%').backgroundColor('#37474F').borderRadius(12).padding(16)
      Row({ space: 8 }) {
        Text('🚙').fontSize(15)
        Text('车龄分布(月)').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#263238')
        Column().layoutWeight(1)
        Text('越短越保值').fontSize(10).fontColor('#B0BEC5')
      }.width('100%')
      Row({ space: 9 }) {
        ForEach(this.suvList, (item: GoodsItem, index: number) => {
          Column({ space: 4 }) {
            Text(Math.floor(item.num / 12) + '年').fontSize(8).fontColor('#546E7A')
            Column().width(18).borderRadius(4)
              .height(item.num / this.maxNumOf(this.suvList) * 100)
              .backgroundColor(this.colorArr[index % 4])
            Text(item.name.substring(0, 2)).fontSize(8).fontColor('#90A4AE').maxLines(1)
          }
        }, (item: GoodsItem) => item.name)
      }.width('100%').padding({ left: 4, right: 4 })
      Row({ space: 8 }) {
        Text('🛣').fontSize(15)
        Text('热门SUV横滑选车').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#263238')
        Column().layoutWeight(1)
        Text('右滑更多').fontSize(10).fontColor('#B0BEC5')
      }.width('100%')
      Scroll() {
        Row({ space: 10 }) {
          ForEach(this.suvList, (item: GoodsItem, index: number) => {
            Column({ space: 6 }) {
              Text(item.name.substring(0, 1)).fontSize(16).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
                .width(40).height(40).borderRadius(20).textAlign(TextAlign.Center)
                .backgroundColor(this.colorArr[index % 4])
              Text(item.name).fontSize(11).fontColor('#263238').maxLines(1)
              Text((item.price / 10).toFixed(1) + '万').fontSize(11).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
              Text(item.desc).fontSize(9).fontColor('#90A4AE').maxLines(1)
            }
            .width(110).padding(10).backgroundColor('#FFFFFF').borderRadius(10)
            .onClick(() => {
              this.selectedItem = item
              this.showDetailModal = true
            })
          }, (item: GoodsItem) => item.name)
        }.padding({ left: 2, right: 2 })
      }.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).width('100%')
    }.width('100%').padding(12)
  }

  @Builder tab3() {
    Column({ space: 12 }) {
      Column({ space: 10 }) {
        Row({ space: 8 }) {
          Text('🧾').fontSize(16)
          Text('第三方检测 · 综合评分').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#263238')
          Column().layoutWeight(1)
          Row({ space: 2 }) {
            Text('92').fontSize(24).fontWeight(FontWeight.Bold).fontColor('#00897B')
              .scale({ x: 1.05, y: 1.05 })
              .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            Text('分').fontSize(10).fontColor('#90A4AE')
          }
        }.width('100%')
        Row({ space: 10 }) {
          ForEach(this.checkList, (item: GoodsItem) => {
            Column({ space: 4 }) {
              Column().width(16).borderRadius(3)
                .height(item.num / this.maxNumOf(this.checkList) * 90)
                .backgroundColor(item.tag === '通过' ? '#00897B' : '#FF8F00')
              Text(item.hot).fontSize(9).fontColor('#90A4AE')
            }
          }, (item: GoodsItem) => item.name)
        }.width('100%').padding({ left: 4, right: 4 })
      }.width('100%').padding(14).backgroundColor('#FFFFFF').borderRadius(12)
      Row({ space: 8 }) {
        Text('🔍').fontSize(15)
        Text('检测项明细').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#263238')
        Column().layoutWeight(1)
        Text('共' + this.checkList.length + '项').fontSize(10).fontColor('#B0BEC5')
      }.width('100%')
      Column({ space: 10 }) {
        ForEach(this.checkList, (item: GoodsItem) => {
          Row({ space: 10 }) {
            Column().width(4).height(60).borderRadius(2)
              .backgroundColor(item.tag === '通过' ? '#00897B' : '#FF8F00')
            Column({ space: 6 }) {
              Row({ space: 8 }) {
                Text(item.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor('#263238')
                Column().layoutWeight(1)
                Text(item.tag).fontSize(10)
                  .fontColor(item.tag === '通过' ? '#00897B' : '#FF8F00')
                  .backgroundColor(item.tag === '通过' ? '#E0F2F1' : '#FFF3E0')
                  .padding({ left: 8, right: 8, top: 3, bottom: 3 }).borderRadius(8)
              }.width('100%')
              Row() {
                Column().width(item.score + '%').height(6).borderRadius(3)
                  .backgroundColor(item.tag === '通过' ? '#00897B' : '#FF8F00')
              }.width('100%').height(6).borderRadius(3).backgroundColor('#ECEFF1')
              Row({ space: 8 }) {
                Text(item.desc).fontSize(10).fontColor('#90A4AE').maxLines(1)
                Column().layoutWeight(1)
                Text(item.score + '分').fontSize(10).fontColor('#263238')
              }.width('100%')
            }.layoutWeight(1).alignItems(HorizontalAlign.Start)
          }.width('100%').padding(12).backgroundColor('#FFFFFF').borderRadius(10)
        }, (item: GoodsItem) => item.name)
      }.width('100%')
    }.width('100%').padding(12)
  }

  @Builder tab4() {
    Column({ space: 12 }) {
      Column({ space: 6 }) {
        Text('我的爱车 · 预估可卖').fontSize(11).fontColor('#B0BEC5')
        Row({ space: 2 }) {
          Text('¥').fontSize(16).fontColor('#FF8F00').fontWeight(FontWeight.Bold)
          Text(this.tradeEstimate() + '').fontSize(34).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
            .scale({ x: 1.06, y: 1.06 })
            .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
          Text('万').fontSize(12).fontColor('#FF8F00')
        }
        Text('基于近3.2万条同车系成交数据估算').fontSize(9).fontColor('#90A4AE')
      }.width('100%').padding(20).backgroundColor('#FFF8E1').borderRadius(12).alignItems(HorizontalAlign.Center)
      Row({ space: 8 }) {
        Text('🔄').fontSize(15)
        Text('热门置换车系').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#263238')
        Column().layoutWeight(1)
        Text('补贴限时领').fontSize(10).fontColor('#E53935')
      }.width('100%')
      Column({ space: 10 }) {
        ForEach(this.tradeList, (item: GoodsItem, index: number) => {
          Row({ space: 10 }) {
            Text(item.hot).fontSize(15).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
              .width(40).height(40).borderRadius(20).textAlign(TextAlign.Center)
              .backgroundColor(this.colorArr[index % 4])
            Column({ space: 3 }) {
              Text(item.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor('#263238').maxLines(1)
              Text(item.desc).fontSize(10).fontColor('#90A4AE').maxLines(1)
            }.layoutWeight(1).alignItems(HorizontalAlign.Start)
            Column({ space: 5 }) {
              Text((item.price / 10).toFixed(1) + '万').fontSize(13).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
              Text('去估值').fontSize(10).fontColor('#FFFFFF').backgroundColor('#FF8F00')
                .padding({ left: 10, right: 10, top: 4, bottom: 4 }).borderRadius(10)
            }
          }
          .width('100%').padding(12).backgroundColor('#FFFFFF').borderRadius(10)
          .onClick(() => {
            this.selectedItem = item
            this.showTradeModal = true
          })
        }, (item: GoodsItem) => item.name)
      }.width('100%')
      Row({ space: 8 }) {
        Text('💰').fontSize(15)
        Text('置换补贴力度').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#263238')
        Column().layoutWeight(1)
        Text('单位 元').fontSize(9).fontColor('#B0BEC5')
      }.width('100%')
      Row({ space: 12 }) {
        ForEach(this.tradeList, (item: GoodsItem, index: number) => {
          Column({ space: 4 }) {
            Text(item.num + '0').fontSize(8).fontColor('#FF8F00')
            Column().width(16).borderRadius(3)
              .height(item.num / this.maxNumOf(this.tradeList) * 90)
              .backgroundColor(index % 2 === 0 ? '#FF8F00' : '#FFB300')
            Text(item.hot).fontSize(9).fontColor('#90A4AE')
          }
        }, (item: GoodsItem) => item.name)
      }.width('100%').padding({ left: 4, right: 4 })
    }.width('100%').padding(12)
  }

  @Builder tab5() {
    Column() {
      ForEach(this.msgList, (item: GoodsItem, index: number) => {
        Row({ space: 12 }) {
          Stack({ alignContent: Alignment.TopEnd }) {
            Text(item.hot).fontSize(16).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
              .width(46).height(46).borderRadius(23).textAlign(TextAlign.Center)
              .backgroundColor(this.colorArr[index % 4])
            if (item.tag === '未读') {
              Column().width(12).height(12).borderRadius(6).backgroundColor('#E53935')
                .scale({ x: 1.15, y: 1.15 })
                .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            }
          }.width(46).height(46)
          Column({ space: 4 }) {
            Row({ space: 8 }) {
              Text(item.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor('#263238').maxLines(1)
              Column().layoutWeight(1)
              Text(this.msgTime(item.num)).fontSize(9).fontColor('#B0BEC5')
            }.width('100%')
            Row({ space: 6 }) {
              Text(item.desc).fontSize(11).fontColor('#90A4AE').maxLines(1).layoutWeight(1)
              if (item.tag === '未读') {
                Text('新').fontSize(8).fontColor('#FFFFFF').backgroundColor('#E53935')
                  .padding({ left: 5, right: 5, top: 1, bottom: 1 }).borderRadius(6)
              }
            }.width('100%')
          }.layoutWeight(1).alignItems(HorizontalAlign.Start)
        }
        .width('100%').padding({ left: 12, right: 12, top: 10, bottom: 10 })
        .backgroundColor(index % 2 === 0 ? '#FFFFFF' : '#F5F7FA').borderRadius(10)
      }, (item: GoodsItem) => item.name)
    }.width('100%').padding(12)
  }

  @Builder tab6() {
    Column({ space: 12 }) {
      Row({ space: 14 }) {
        Text('车').fontSize(24).fontWeight(FontWeight.Bold).fontColor('#01579B')
          .width(64).height(64).borderRadius(32).textAlign(TextAlign.Center).backgroundColor('#FFFFFF')
        Column({ space: 6 }) {
          Text('二手车老司机').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
          Row({ space: 6 }) {
            Text('看车足迹 Lv.5').fontSize(10).fontColor('#FFECB3').backgroundColor('#FF8F00')
              .padding({ left: 8, right: 8, top: 3, bottom: 3 }).borderRadius(8)
              .scale({ x: 1.05, y: 1.05 })
              .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            Text('已看 36 台').fontSize(10).fontColor('#B0BEC5')
          }
        }.layoutWeight(1).alignItems(HorizontalAlign.Start)
        Text('›').fontSize(20).fontColor('#FFFFFF')
      }.width('100%').padding(18).backgroundColor('#01579B').borderRadius(14)
      Row() {
        Column({ space: 4 }) {
          Text('28').fontSize(20).fontWeight(FontWeight.Bold).fontColor('#0277BD')
          Text('收藏车源').fontSize(10).fontColor('#90A4AE')
        }.layoutWeight(1)
        Column().width(1).height(30).backgroundColor('#ECEFF1')
        Column({ space: 4 }) {
          Text('3').fontSize(20).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
          Text('预约看车').fontSize(10).fontColor('#90A4AE')
        }.layoutWeight(1)
        Column().width(1).height(30).backgroundColor('#ECEFF1')
        Column({ space: 4 }) {
          Text('1').fontSize(20).fontWeight(FontWeight.Bold).fontColor('#00897B')
          Text('卖车订单').fontSize(10).fontColor('#90A4AE')
        }.layoutWeight(1)
      }.width('100%').padding(14).backgroundColor('#FFFFFF').borderRadius(12)
      Column() {
        ForEach(this.menuList, (m: string, index: number) => {
          Row({ space: 12 }) {
            Text(this.menuIcons[index]).fontSize(16)
            Text(m).fontSize(14).fontColor('#263238')
            Column().layoutWeight(1)
            Text('›').fontSize(18).fontColor('#B0BEC5')
          }
          .width('100%').height(50).padding({ left: 14, right: 14 }).backgroundColor('#FFFFFF')
          .onClick(() => {
            if (index === 2) {
              this.showAddModal = true
            } else if (index === 3) {
              this.showTradeModal = true
            } else {
              this.currentTab = 6
            }
          })
        }, (m: string) => m)
      }.width('100%').borderRadius(12)
    }.width('100%').padding(12)
  }

  @Builder addModal() {
    Column() {
      Column().layoutWeight(1)
      Column({ space: 12 }) {
        Row({ space: 10 }) {
          Text('快速卖车').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#263238')
          Column().layoutWeight(1)
          Text('✕').fontSize(16).fontColor('#90A4AE')
            .onClick(() => {
              this.showAddModal = false
            })
        }.width('100%')
        Text('选择车系').fontSize(13).fontColor('#455A64')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.seriesChips, (chip: string, index: number) => {
            Text(chip).fontSize(12)
              .fontColor(this.chipA === index ? '#FFFFFF' : '#455A64')
              .backgroundColor(this.chipA === index ? '#0277BD' : '#ECEFF1')
              .padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(16)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => {
                this.chipA = index
              })
          }, (chip: string) => chip)
        }.width('100%')
        Text('车龄').fontSize(13).fontColor('#455A64')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.ageChips, (chip: string, index: number) => {
            Text(chip).fontSize(12)
              .fontColor(this.chipB === index ? '#FFFFFF' : '#455A64')
              .backgroundColor(this.chipB === index ? '#0277BD' : '#ECEFF1')
              .padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(16)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => {
                this.chipB = index
              })
          }, (chip: string) => chip)
        }.width('100%')
        Text('行驶里程(万公里)').fontSize(13).fontColor('#455A64')
        TextInput({ placeholder: '请输入行驶里程', text: this.mileageInput })
          .height(44).fontSize(13).backgroundColor('#ECEFF1').borderRadius(8)
          .padding({ left: 12, right: 12 })
          .onChange((value: string) => {
            this.mileageInput = value
          })
        Text('联系手机号').fontSize(13).fontColor('#455A64')
        TextInput({ placeholder: '请输入手机号,评估师30分钟内回电' })
          .height(44).fontSize(13).backgroundColor('#ECEFF1').borderRadius(8)
          .padding({ left: 12, right: 12 }).type(InputType.PhoneNumber)
          .onChange((value: string) => {
            this.phoneInput = value
          })
        Text('提交卖车申请').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
          .width('100%').height(46).textAlign(TextAlign.Center).backgroundColor('#FF8F00').borderRadius(23)
          .onClick(() => {
            this.showAddModal = false
          })
      }
      .width('100%').padding(16).backgroundColor('#FFFFFF')
      .borderRadius({ topLeft: 16, topRight: 16 })
      .constraintSize({ maxHeight: '80%' })
    }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
    .onClick(() => {
      this.showAddModal = false
    })
  }

  @Builder bookModal() {
    Column() {
      Column().layoutWeight(1)
      Column({ space: 12 }) {
        Row({ space: 10 }) {
          Text('预约看车').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#263238')
          Column().layoutWeight(1)
          Text('✕').fontSize(16).fontColor('#90A4AE')
            .onClick(() => {
              this.showBookModal = false
            })
        }.width('100%')
        Row({ space: 10 }) {
          Text('🚗').fontSize(18)
          Column({ space: 2 }) {
            Text(this.selName()).fontSize(15).fontWeight(FontWeight.Bold).fontColor('#263238').maxLines(1)
            Text(this.selDesc()).fontSize(10).fontColor('#90A4AE').maxLines(1)
          }.layoutWeight(1).alignItems(HorizontalAlign.Start)
          Text((this.selPrice() / 10).toFixed(1) + '万').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
        }.width('100%').padding(12).backgroundColor('#F5F7FA').borderRadius(10)
        Text('选择门店').fontSize(13).fontColor('#455A64')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.storeChips, (chip: string, index: number) => {
            Text(chip).fontSize(12)
              .fontColor(this.chipA === index ? '#FFFFFF' : '#455A64')
              .backgroundColor(this.chipA === index ? '#0277BD' : '#ECEFF1')
              .padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(16)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => {
                this.chipA = index
              })
          }, (chip: string) => chip)
        }.width('100%')
        Text('选择时段').fontSize(13).fontColor('#455A64')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.slotChips, (chip: string, index: number) => {
            Text(chip).fontSize(12)
              .fontColor(this.chipB === index ? '#FFFFFF' : '#455A64')
              .backgroundColor(this.chipB === index ? '#0277BD' : '#ECEFF1')
              .padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(16)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => {
                this.chipB = index
              })
          }, (chip: string) => chip)
        }.width('100%')
        Row({ space: 14 }) {
          Text('试驾体验圈数').fontSize(13).fontColor('#455A64')
          Column().layoutWeight(1)
          Text('-').fontSize(14).fontColor('#455A64')
            .width(30).height(30).borderRadius(15).backgroundColor('#ECEFF1').textAlign(TextAlign.Center)
            .onClick(() => {
              if (this.stepNum > 1) {
                this.stepNum -= 1
              }
            })
          Text(this.stepNum + '').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#263238')
            .width(44).textAlign(TextAlign.Center)
          Text('+').fontSize(14).fontColor('#FFFFFF')
            .width(30).height(30).borderRadius(15).backgroundColor('#FF8F00').textAlign(TextAlign.Center)
            .onClick(() => {
              if (this.stepNum < 5) {
                this.stepNum += 1
              }
            })
        }.width('100%')
        Text('确认预约').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
          .width('100%').height(46).textAlign(TextAlign.Center).backgroundColor('#0277BD').borderRadius(23)
          .onClick(() => {
            this.showBookModal = false
          })
      }
      .width('100%').padding(16).backgroundColor('#FFFFFF')
      .borderRadius({ topLeft: 16, topRight: 16 })
      .constraintSize({ maxHeight: '80%' })
    }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
    .onClick(() => {
      this.showBookModal = false
    })
  }

  @Builder tradeModal() {
    Column() {
      Column().layoutWeight(1)
      Column({ space: 12 }) {
        Row({ space: 10 }) {
          Text('置换估值').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#263238')
          Column().layoutWeight(1)
          Text('✕').fontSize(16).fontColor('#90A4AE')
            .onClick(() => {
              this.showTradeModal = false
            })
        }.width('100%')
        Text('车况').fontSize(13).fontColor('#455A64')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.condChips, (chip: string, index: number) => {
            Text(chip).fontSize(12)
              .fontColor(this.chipA === index ? '#FFFFFF' : '#455A64')
              .backgroundColor(this.chipA === index ? '#FF8F00' : '#ECEFF1')
              .padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(16)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => {
                this.chipA = index
              })
          }, (chip: string) => chip)
        }.width('100%')
        Text('排量').fontSize(13).fontColor('#455A64')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.dispChips, (chip: string, index: number) => {
            Text(chip).fontSize(12)
              .fontColor(this.chipB === index ? '#FFFFFF' : '#455A64')
              .backgroundColor(this.chipB === index ? '#FF8F00' : '#ECEFF1')
              .padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(16)
              .margin({ right: 8, bottom: 8 })
              .onClick(() => {
                this.chipB = index
              })
          }, (chip: string) => chip)
        }.width('100%')
        Column({ space: 4 }) {
          Text('预估置换价').fontSize(11).fontColor('#B0BEC5')
          Row({ space: 2 }) {
            Text('¥').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
            Text(this.tradeEstimate() + '').fontSize(32).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
              .scale({ x: 1.06, y: 1.06 })
              .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            Text('万').fontSize(12).fontColor('#FF8F00')
          }
          Text('按车况 ' + this.condChips[this.chipA] + ' · 排量 ' + this.dispChips[this.chipB] + ' 估算')
            .fontSize(9).fontColor('#90A4AE')
        }.width('100%').padding(18).backgroundColor('#FFF8E1').borderRadius(12).alignItems(HorizontalAlign.Center)
        Text('提交置换申请').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
          .width('100%').height(46).textAlign(TextAlign.Center).backgroundColor('#FF8F00').borderRadius(23)
          .onClick(() => {
            this.showTradeModal = false
          })
      }
      .width('100%').padding(16).backgroundColor('#FFFFFF')
      .borderRadius({ topLeft: 16, topRight: 16 })
      .constraintSize({ maxHeight: '80%' })
    }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
    .onClick(() => {
      this.showTradeModal = false
    })
  }

  @Builder deleteModal() {
    Column() {
      Column() {
        Column({ space: 8 }) {
          Text('⚠️').fontSize(34)
          Text('确认删除该看车预约?').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
        }.width('100%').padding({ top: 22, bottom: 22 }).backgroundColor('#E53935')
        .justifyContent(FlexAlign.Center)
        Column() {
          Text('删除后将无法恢复,如需看车请重新预约').fontSize(11).fontColor('#90A4AE')
        }.width('100%').padding({ top: 18, bottom: 18 })
        Row() {
          Text('取消').fontSize(14).fontColor('#607D8B')
            .layoutWeight(1).height(46).textAlign(TextAlign.Center)
            .onClick(() => {
              this.showDeleteModal = false
            })
          Column().width(1).height(46).backgroundColor('#ECEFF1')
          Text('删除').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
            .layoutWeight(1).height(46).textAlign(TextAlign.Center).backgroundColor('#E53935')
            .onClick(() => {
              this.showDeleteModal = false
            })
        }.width('100%')
      }
      .width('72%').borderRadius(12).backgroundColor('#FFFFFF')
    }.width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
    .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)
    .onClick(() => {
      this.showDeleteModal = false
    })
  }

  @Builder detailModal() {
    Column() {
      Column({ space: 10 }) {
        Text('🚘').fontSize(60)
        Text(this.selTag()).fontSize(11).fontColor('#FFFFFF').backgroundColor('rgba(255,255,255,0.25)')
          .padding({ left: 10, right: 10, top: 4, bottom: 4 }).borderRadius(10)
      }.width('100%').height(210).backgroundColor('#0277BD').justifyContent(FlexAlign.Center)
      Column({ space: 12 }) {
        Text(this.selName()).fontSize(19).fontWeight(FontWeight.Bold).fontColor('#263238').maxLines(2)
        Text(this.selDesc()).fontSize(11).fontColor('#90A4AE')
        Row({ space: 2 }) {
          Text('¥').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
          Text((this.selPrice() / 10).toFixed(1)).fontSize(32).fontWeight(FontWeight.Bold).fontColor('#FF8F00')
            .scale({ x: 1.06, y: 1.06 })
            .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
          Text('万').fontSize(12).fontColor('#FF8F00')
        }
        Column({ space: 6 }) {
          Row({ space: 8 }) {
            Text('第三方检测分').fontSize(11).fontColor('#90A4AE')
            Column().layoutWeight(1)
            Text(this.selScore() + '').fontSize(11).fontWeight(FontWeight.Bold).fontColor('#00897B')
          }.width('100%')
          Row() {
            Column().width(this.selScore() + '%').height(6).borderRadius(3).backgroundColor('#00897B')
          }.width('100%').height(6).borderRadius(3).backgroundColor('#ECEFF1')
        }.width('100%')
        Text('配置亮点').fontSize(13).fontWeight(FontWeight.Bold).fontColor('#263238')
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.configChips, (chip: string) => {
            Text(chip).fontSize(11).fontColor('#0277BD').backgroundColor('#E1F5FE')
              .padding({ left: 10, right: 10, top: 5, bottom: 5 }).borderRadius(12)
              .margin({ bottom: 8 })
          }, (chip: string) => chip)
        }.width('100%')
        Column().layoutWeight(1)
        Row({ space: 10 }) {
          Text('删除预约').fontSize(13).fontColor('#90A4AE')
            .width(96).height(44).textAlign(TextAlign.Center).backgroundColor('#ECEFF1').borderRadius(22)
            .onClick(() => {
              this.showDetailModal = false
              this.showDeleteModal = true
            })
          Text('预约看车').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
            .layoutWeight(1).height(44).textAlign(TextAlign.Center).backgroundColor('#FF8F00').borderRadius(22)
            .onClick(() => {
              this.showDetailModal = false
              this.showBookModal = true
            })
        }.width('100%')
      }.width('100%').padding(16).layoutWeight(1)
    }
    .width('80%').height('100%').backgroundColor('#FFFFFF')
  }

  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%').backgroundColor('#F5F7FA')
        this.bottomBuilder()
      }.width('100%').height('100%')
      if (this.showAddModal) {
        this.addModal()
      }
      if (this.showBookModal) {
        this.bookModal()
      }
      if (this.showTradeModal) {
        this.tradeModal()
      }
      if (this.showDeleteModal) {
        this.deleteModal()
      }
      if (this.showDetailModal) {
        this.detailModal()
      }
    }.width('100%').height('100%')
  }
}


总结

在这里插入图片描述

本文完整剖析了一套基于HarmonyOS 6.1.1 ArkTS声明式UI范式构建的二手车精选行交易大厅应用。从GoodsItem统一数据接口的定义到十余个@State响应式状态变量的管理,从七个功能Tab的逐段构建到五个弹层交互的详细实现,涵盖了ArkTS声明式UI开发的全部核心环节。应用通过tradeEstimate()方法实现了置换估值的业务逻辑计算,通过msgTime()方法实现了消息时间的友好格式化,通过TextInputonChange回调实现了表单输入的双向数据绑定,充分展示了声明式UI在复杂业务场景下的数据驱动视图能力。

从UI设计层面来看,应用充分利用了ArkUI框架提供的多样化布局容器和样式属性。FlexFlexWrap.Wrap实现了新能源双列网格自动换行布局,ScrollHorizontal方向实现了Banner大卡和SUV画廊的横向滚动展示,StackAlignment.TopEnd实现了消息列表未读红标的角标层叠效果。动画方面,通过scale配合animationiterations: -1参数在各Tab的关键数据指标处实现了脉冲缩放效果,包括综合评分、预估价格、在售数量、电池健康度等,为用户提供了持续性的视觉焦点引导。三种柱状图(续航、车龄、补贴)均通过ForEach遍历数据数组并动态计算柱形高度比例实现,是纯声明式UI实现数据可视化的典型实践。

从交互架构层面来看,build()方法中的Stack层叠容器配合条件渲染if语句构建了完整的弹层管理系统。快速卖车弹层集成了Chip选择和TextInput输入两种交互形式,置换估值弹层通过Chip选择联动更新估价描述文本,详情弹层通过双按钮实现了向删除弹层和预约弹层的跳转流转。事件冒泡控制通过event.stopPropagation()防止内容区域点击触发遮罩关闭逻辑,constraintSize({ maxHeight: '80%' })限制弹层最大高度防止内容过多超出屏幕。整体架构清晰高效,为类似的二手车交易、房产经纪等重度信息展示型应用提供了可直接参考的设计范本和实现路径。

Logo

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

更多推荐