引言

在这里插入图片描述

中国茶文化源远流长,茶器作为茶文化的重要载体,其收藏与流转自古便有深厚的传统。从紫砂壶的泥料讲究到建盏的窑变之美,从普洱茶的年份转化到白茶的仓储陈化,茶器与老茶的交易需要极高的专业鉴赏能力。传统的通用二手交易平台难以呈现茶器的泥料、容量、养成年限、品相完好度等关键参数,也难以展示老茶的年份、茶类、仓储状况等核心信息。本项目"茶器流转阁"正是基于这一垂直领域的专业需求,打造了一个专注于二手茶器与老茶的交易平台,通过声明式UI将茶器与茶叶的专业参数以数据可视化的方式直观呈现,让买家在决策前即可全面了解器物状态。

在技术架构层面,本应用采用HarmonyOS ArkTS声明式开发范式,以单一@Entry @Component组件TeaLoopApp为根节点构建完整应用。整体架构分为三层:数据模型层定义了六个接口类型覆盖茶器交易全品类数据;视图层通过七个@Builder方法构建七个功能Tab页面和五个@Builder方法实现弹窗交互;逻辑层封装了容量最大值计算、养成年限最大值计算和选中项安全提取等私有方法。配色方案采用茶褐竹青风格,以#4E342E茶褐为主色调、#558B2F竹青为辅助色、#F5F1E8米宣为背景色、#B08D57铜金为强调色,营造出与茶器文化高度契合的古典雅致氛围。这种"色彩即文化"的设计理念贯穿于应用的每一个视觉细节。

在业务设计层面,应用围绕"浏览器物-查看详情-养护鉴定-社区交流"构建完整用户旅程。浏览环节提供茶器集市、紫砂壶专区、盖碗茶杯专区、茶饼老茶专区、茶友会、消息中心和个人中心共七个功能模块。交易环节涵盖发布茶器、茶器养护和老茶鉴定三种特色弹窗交互。增值服务包括老货鉴定、溯源查询、茶样试饮等。特别值得关注的是,茶友会Tab引入了社区帖子列表的概念,包含发帖人、标题、正文、回复数和点赞数,这是其他两个同类应用所不具备的社交功能。此外,茶器养护弹窗通过类型选择和频次选择组合生成养护方案,老茶鉴定弹窗通过年份和茶类选择组合输出鉴定结果,这些多维度组合式弹窗体现了茶器领域的专业交互需求。

接口定义与数据模型构建

在这里插入图片描述

本应用定义了六个接口类型,分别对应茶器交易的六大数据维度。每个接口精准刻画了茶器或茶叶的专业参数,为列表渲染和柱状图计算提供类型安全的数据基础。

interface TeaItem {
  name: string
  brand: string
  price: number
  originPrice: number
  craft: number
  city: string
  tag: string
}

interface ZishaItem {
  name: string
  clay: string
  capacity: number
  price: number
  aged: number
}

interface CupItem {
  name: string
  kiln: string
  size: string
  price: number
  flawless: boolean
}

interface CakeItem {
  name: string
  year: number
  kind: string
  price: number
  weight: number
}

interface ClubItem {
  user: string
  title: string
  content: string
  replies: number
  likes: number
}

interface NoteItem {
  name: string
  content: string
  time: string
  unread: number
  type: string
}

TeaItem接口是茶器集市的核心数据结构,包含名称、品牌、售价、原价、工艺品相分值(craft)、城市和标签七个字段。craft字段是一个0到100的数值,表示器物的品相完好度,是茶器交易中最关键的参数之一。ZishaItem接口针对紫砂壶增加了clay泥料类型(如紫泥、朱泥、段泥、天青泥等)、capacity容量(毫升)和aged养成年限(年),这三项是紫砂壶收藏的核心指标。CupItem接口关注盖碗茶杯的kiln窑口(如建阳窑、晓芳窑、龙泉窑等)、size尺寸和flawless是否全品无瑕疵的布尔值。CakeItem接口记录茶饼老茶的year生产年份、kind茶类(如生普、熟普、白茶、黑茶等)和weight重量(克)。ClubItem接口定义了茶友会帖子的数据结构,包含发帖人、标题、正文、回复数和点赞数。NoteItem接口用于消息通知。这套接口体系完整覆盖了茶器交易与社区交流的全链路数据需求。

组件状态声明与静态数据集

在这里插入图片描述

TeaLoopApp主组件通过@State装饰器声明了十一个响应式状态变量,比前述两个应用多一个,因为茶器养护弹窗需要careKindcareFreq两个选择变量,老茶鉴定弹窗需要verifyYearverifyKind两个选择变量。

@Entry
@Component
struct TeaLoopApp {
  @State currentTab: number = 0
  @State showPublish: boolean = false
  @State showCare: boolean = false
  @State showVerify: boolean = false
  @State showDelete: boolean = false
  @State showDetail: boolean = false
  @State selectedItem: TeaItem | null = null
  @State careKind: number = 0
  @State careFreq: number = 0
  @State verifyYear: number = 2
  @State verifyKind: number = 0
  @State itemKind: number = 0
  @State priceField: string = ''

  private tabs: string[] = ['茶器集市', '紫砂壶', '盖碗茶杯', '茶饼老茶', '茶友会', '消息', '我的']

  private teaList: TeaItem[] = [
    { name: '顾景亭款 石瓢壶 280ml', brand: '宜兴紫砂', price: 6800, originPrice: 12800, craft: 98, city: '宜兴', tag: '收藏' },
    { name: '朱泥西施壶 150ml', brand: '茗岭陶坊', price: 880, originPrice: 1980, craft: 92, city: '潮州', tag: '热门' },
    { name: '段泥仿古壶 220ml', brand: '黄龙山', price: 1280, originPrice: 2800, craft: 90, city: '苏州', tag: '包邮' },
    { name: '天青泥井栏壶 200ml', brand: '宜兴紫砂', price: 2280, originPrice: 4600, craft: 95, city: '无锡', tag: '精品' },
    { name: '建盏 曜变束口盏', brand: '建阳窑', price: 1580, originPrice: 3200, craft: 96, city: '南平', tag: '精品' },
    { name: '汝窑天青开片盖碗', brand: '晓芳窑', price: 980, originPrice: 2100, craft: 93, city: '景德镇', tag: '95新' },
    { name: '青瓷莲花盖碗 140ml', brand: '龙泉窑', price: 320, originPrice: 720, craft: 88, city: '丽水', tag: '包邮' },
    { name: '白瓷手绘兰草盖碗', brand: '景德镇', price: 260, originPrice: 580, craft: 90, city: '景德镇', tag: '急出' },
    { name: '老白茶 2015 寿眉饼', brand: '福鼎磻溪', price: 480, originPrice: 980, craft: 100, city: '福鼎', tag: '干仓' },
    { name: '普洱生茶 2018 冰岛', brand: '勐库大寨', price: 2680, originPrice: 5200, craft: 100, city: '临沧', tag: '收藏' },
    { name: '普洱熟茶 2012 中茶', brand: '中茶牌', price: 1280, originPrice: 2600, craft: 100, city: '昆明', tag: '热门' },
    { name: '武夷岩茶 老丛水仙 50g', brand: '慧苑坑', price: 380, originPrice: 820, craft: 100, city: '武夷山', tag: '急出' },
    { name: '凤凰单丛 蜜兰香 100g', brand: '乌岽山', price: 220, originPrice: 480, craft: 100, city: '潮州', tag: '包邮' },
    { name: '整架电木茶盘 1米', brand: '茶人坊', price: 580, originPrice: 1280, craft: 85, city: '广州', tag: '自提' },
    { name: '老竹茶盘 端石壶承', brand: '手作社', price: 260, originPrice: 560, craft: 90, city: '杭州', tag: '95新' },
    { name: '锡制茶叶罐 500g', brand: '个旧锡器', price: 320, originPrice: 680, craft: 94, city: '个旧', tag: '包邮' },
    { name: '粗陶侧把壶 300ml', brand: '柴烧坊', price: 380, originPrice: 820, craft: 87, city: '景洪', tag: '急出' },
    { name: '玻璃公道杯 加厚', brand: '无印良品', price: 45, originPrice: 98, craft: 95, city: '上海', tag: '全新' },
    { name: '竹制茶则 六件套', brand: '竹艺斋', price: 88, originPrice: 198, craft: 92, city: '安吉', tag: '包邮' },
    { name: '铸铁壶 南部铁器 1L', brand: '岩手堂', price: 1280, originPrice: 2680, craft: 91, city: '大连', tag: '热门' },
    { name: '银壶 手工锤纹 800ml', brand: '大理银坊', price: 3680, originPrice: 7200, craft: 97, city: '大理', tag: '精品' },
    { name: '养壶笔 茶巾 套装', brand: '茶人坊', price: 38, originPrice: 88, craft: 100, city: '厦门', tag: '全新' },
    { name: '柴烧主人杯 束口', brand: '建水窑', price: 168, originPrice: 380, craft: 89, city: '建水', tag: '急出' },
    { name: '老青砖茶 1998 茯砖', brand: '安化茶厂', price: 880, originPrice: 1800, craft: 100, city: '安化', tag: '收藏' }
  ]

currentTab控制Tab索引,五个show变量控制弹窗显隐。selectedItem使用TeaItem | null联合类型。careKindcareFreq分别服务于养护弹窗的器物类型选择和频次选择。verifyYear默认值为2(索引值,对应2015年),verifyKind服务于鉴定弹窗的茶类选择。teaList数组包含24条数据,覆盖了紫砂壶、建盏、汝窑盖碗、青瓷、普洱茶饼、白茶、岩茶、单丛、茶盘、茶叶罐、铸铁壶、银壶等丰富品类。每条数据中craft品相分值为100的均为茶叶类(茶叶不存在器物磨损概念),而器物类则根据实际品相给予85-98不等的分值。

私有方法:最大值计算与安全提取

在这里插入图片描述

组件封装了两个最大值计算方法和六个安全提取方法,其中maxCapacity()maxAge()分别用于紫砂壶柱状图的高度归一化。

  private maxCapacity(): number {
    let max: number = 0
    for (let i = 0; i < this.zishaList.length; i++) {
      if (this.zishaList[i].capacity > max) {
        max = this.zishaList[i].capacity
      }
    }
    return max
  }

  private maxAge(): number {
    let max: number = 0
    for (let i = 0; i < this.zishaList.length; i++) {
      if (this.zishaList[i].aged > max) {
        max = this.zishaList[i].aged
      }
    }
    return max
  }

  private selName(): string {
    if (this.selectedItem === null) {
      return ''
    }
    return this.selectedItem.name
  }

  private selPrice(): string {
    if (this.selectedItem === null) {
      return ''
    }
    return this.selectedItem.price + ''
  }

  private selBrand(): string {
    if (this.selectedItem === null) {
      return ''
    }
    return this.selectedItem.brand
  }

  private selCraft(): number {
    if (this.selectedItem === null) {
      return 0
    }
    return this.selectedItem.craft
  }

  private selCity(): string {
    if (this.selectedItem === null) {
      return ''
    }
    return this.selectedItem.city
  }

  private selOrigin(): string {
    if (this.selectedItem === null) {
      return ''
    }
    return this.selectedItem.originPrice + ''
  }

maxCapacity()遍历紫砂壶列表计算最大容量值(毫升),maxAge()遍历紫砂壶列表计算最大养成年限值(年)。这两个方法的返回值分别用于容量柱状图和养成年限进度条的比例计算。六个sel前缀方法实现空值安全提取:当selectedItemnull时返回空字符串或零值,避免详情弹窗渲染时空指针异常。selCraft()方法返回的是品相分值(0-100的数值),在详情弹窗中用于计算进度条填充宽度this.selCraft() + '%'和颜色判断this.selCraft() > 90。这种防御性编程策略确保了声明式UI在数据未就绪时不会崩溃。

主构建函数:三段式布局与弹窗条件渲染

在这里插入图片描述

build()方法构建应用的三段式页面骨架,并在末尾通过if条件渲染五个弹窗。

  build() {
    Column() {
      // 头部:静态茶器电商风
      Column() {
        Row() {
          Column() {
            Text('茶器流转阁')
              .fontSize(20)
              .fontWeight(FontWeight.Bold)
              .fontColor('#FFFFFF')
            Text('二手茶器老茶 一盏流转')
              .fontSize(11)
              .fontColor('#E8DCC8')
              .margin({ top: 2 })
          }
          .alignItems(HorizontalAlign.Start)

          Row() {
            Text('🍵')
              .fontSize(15)
            Text('搜茶器 / 泥料')
              .fontSize(12)
              .fontColor('#9A8C7A')
              .margin({ left: 6 })
          }
          .width(145)
          .height(32)
          .justifyContent(FlexAlign.Center)
          .backgroundColor('#F5F1E8')
          .borderRadius(16)
          .margin({ left: 12 })

          Text('发布')
            .fontSize(13)
            .fontWeight(FontWeight.Medium)
            .fontColor('#FFFFFF')
            .width(52)
            .height(30)
            .textAlign(TextAlign.Center)
            .backgroundColor('#558B2F')
            .borderRadius(15)
            .margin({ left: 10 })
            .onClick(() => {
              this.showPublish = true
            })
        }
        .width('100%')
        .padding({ left: 16, right: 16, top: 12, bottom: 10 })

        Row() {
          Text('🏺 老货鉴定')
            .fontSize(11)
            .fontColor('#E8DCC8')
            .margin({ right: 12 })
          Text('📜 溯源可查')
            .fontSize(11)
            .fontColor('#E8DCC8')
            .margin({ right: 12 })
          Text('🍵 茶样试饮')
            .fontSize(11)
            .fontColor('#E8DCC8')
        }
        .width('100%')
        .padding({ left: 16, bottom: 12 })
      }
      .width('100%')
      .backgroundColor('#4E342E')
      .borderRadius({ bottomLeft: 18, bottomRight: 18 })

      Scroll() {
        Column() {
          if (this.currentTab === 0) {
            this.MarketTab()
          } else if (this.currentTab === 1) {
            this.ZishaTab()
          } else if (this.currentTab === 2) {
            this.CupTab()
          } else if (this.currentTab === 3) {
            this.CakeTab()
          } else if (this.currentTab === 4) {
            this.ClubTab()
          } else if (this.currentTab === 5) {
            this.NoteTab()
          } else {
            this.MineTab()
          }
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
      }
      .layoutWeight(1)
      .scrollBar(BarState.Off)
      .width('100%')

      Row() {
        ForEach(this.tabs, (tab: string, index: number) => {
          Column() {
            Text(this.tabIcon(index))
              .fontSize(18)
              .fontColor(this.currentTab === index ? '#4E342E' : '#9E9E9E')
            Text(tab)
              .fontSize(10)
              .fontColor(this.currentTab === index ? '#4E342E' : '#9E9E9E')
              .margin({ top: 2 })
            if (this.currentTab === index) {
              Column()
                .width(20)
                .height(3)
                .backgroundColor('#B08D57')
                .borderRadius(2)
                .margin({ top: 3 })
            } else {
              Column()
                .width(20)
                .height(3)
                .backgroundColor('#00000000')
                .margin({ top: 3 })
            }
          }
          .justifyContent(FlexAlign.Center)
          .layoutWeight(1)
          .onClick(() => {
            this.currentTab = index
          })
        }, (tab: string) => tab)
      }
      .width('100%')
      .height(58)
      .backgroundColor('#FFFFFF')
      .border({ width: 1, color: '#E8DCC8', radius: 0 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F1E8')

    if (this.showPublish) {
      this.PublishDialog()
    }
    if (this.showCare) {
      this.CareDialog()
    }
    if (this.showVerify) {
      this.VerifyDialog()
    }
    if (this.showDelete) {
      this.DeleteDialog()
    }
    if (this.showDetail) {
      this.DetailDialog()
    }
  }

头部区域以茶褐#4E342E为背景色,底部圆角营造从屏幕顶部展开卷轴的视觉隐喻。头部第一行包含应用名称"茶器流转阁"、副标题、搜索框(占位文字"搜茶器/泥料")和发布按钮。发布按钮使用竹青#558B2F背景和白色文字,与茶褐主题形成自然的色彩搭配。头部第二行展示三个服务标签:老货鉴定、溯源可查、茶样试饮。中间内容区使用Scroll容器配合layoutWeight(1)占据剩余空间,通过if-else链式条件根据currentTab值调用对应@Builder方法。底部Tab导航栏选中Tab以茶褐色高亮并附带铜金色#B08D57指示条,与整体配色方案统一。五个弹窗在build()末尾通过if条件渲染。tabIcon()方法返回对应emoji图标:购物袋、茶壶、茶杯、砖块、农夫、对话和人物。

Tab1 茶器集市:横幅与品相进度条

在这里插入图片描述

茶器集市是首页Tab,包含渐变横幅广告和茶器列表。列表卡片展示器物品相进度条,这是本应用独特的可视化设计。

  // Tab1 茶器集市:行式列表
  @Builder
  MarketTab() {
    Column() {
      Row() {
        Column() {
          Text('🍵 秋茶季 · 器物焕新')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFFFFF')
          Text('闲置茶器折抵新器 立减 200')
            .fontSize(11)
            .fontColor('#E8DCC8')
            .margin({ top: 4 })
        }
        .alignItems(HorizontalAlign.Start)
        .layoutWeight(1)

        Text('🫖')
          .fontSize(36)
          .scale({ x: 1.12, y: 1.12 })
          .animation({ duration: 1000, iterations: -1, curve: Curve.EaseInOut })
      }
      .width('100%')
      .padding(16)
      .linearGradient({
        direction: GradientDirection.Right,
        colors: [['#4E342E', 0], ['#B08D57', 1]]
      })
      .borderRadius(14)
      .margin({ top: 12, left: 12, right: 12 })

      Row() {
        Text('茶器精选')
          .fontSize(15)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Column()
          .layoutWeight(1)
        Text('工艺优先 ▾')
          .fontSize(12)
          .fontColor('#4E342E')
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 14 })

      ForEach(this.teaList, (item: TeaItem) => {
        Row() {
          Column() {
            Text('🍵')
              .fontSize(26)
          }
          .width(66)
          .height(66)
          .justifyContent(FlexAlign.Center)
          .backgroundColor('#EFE7D8')
          .borderRadius(12)

          Column() {
            Text(item.name)
              .fontSize(13)
              .fontWeight(FontWeight.Medium)
              .fontColor('#3E2723')
              .maxLines(1)
              .textOverflow({ overflow: TextOverflow.Ellipsis })
            Row() {
              Text(item.brand)
                .fontSize(9)
                .fontColor('#4E342E')
                .padding({ left: 6, right: 6, top: 2, bottom: 2 })
                .backgroundColor('#E3D5BC')
                .borderRadius(4)
              Text('· ' + item.city)
                .fontSize(9)
                .fontColor('#9A8C7A')
                .margin({ left: 6 })
            }
            .margin({ top: 4 })
            Row() {
              Text('品相')
                .fontSize(9)
                .fontColor('#9A8C7A')
              Column()
                .width(item.craft / 100 * 55)
                .height(3)
                .backgroundColor(item.craft > 90 ? '#558B2F' : '#B08D57')
                .borderRadius(2)
                .margin({ left: 4 })
              Text(item.craft + '%')
                .fontSize(9)
                .fontColor('#9A8C7A')
                .margin({ left: 4 })
            }
            .margin({ top: 5 })
          }
          .alignItems(HorizontalAlign.Start)
          .layoutWeight(1)
          .margin({ left: 10 })

          Column() {
            Text('¥' + item.price)
              .fontSize(15)
              .fontWeight(FontWeight.Bold)
              .fontColor('#B07D2B')
            Text('¥' + item.originPrice)
              .fontSize(9)
              .fontColor('#C4B49A')
              .decoration({ type: TextDecorationType.LineThrough })
              .margin({ top: 2 })
            Text(item.tag)
              .fontSize(9)
              .fontColor('#FFFFFF')
              .padding({ left: 5, right: 5, top: 1, bottom: 1 })
              .backgroundColor('#558B2F')
              .borderRadius(3)
              .margin({ top: 3 })
          }
          .alignItems(HorizontalAlign.End)
        }
        .width('100%')
        .padding(11)
        .backgroundColor('#FFFFFF')
        .borderRadius(12)
        .margin({ left: 12, right: 12, top: 5 })
        .onClick(() => {
          this.selectedItem = item
          this.showDetail = true
        })
      }, (item: TeaItem) => item.name)

      Column()
        .height(20)
    }
    .width('100%')
  }

横幅区域使用linearGradient创建从茶褐#4E342E到铜金#B08D57的横向渐变,茶壶emoji通过scaleanimation实现持续缩放呼吸动画。列表卡片采用三栏布局:左侧66x66图标区使用米宣色#EFE7D8背景;中间区域展示名称、品牌标签和品相进度条;右侧显示价格、原价和标签。品相进度条的设计是本Tab的核心特色:宽度计算为item.craft / 100 * 55,即以100为满刻度、55为基准像素宽度。进度条颜色根据品相分值动态变化:大于90分使用竹青#558B2F表示品相优秀,低于90分使用铜金#B08D57表示品相尚可。标签使用竹青背景和白色文字,与茶褐主题协调。价格使用铜金色#B07D2B显示,比前两个应用的橙色/珊瑚橙更加沉稳内敛,符合茶器交易的审美调性。

Tab2 紫砂壶专区:容量柱状图与养成年限

在这里插入图片描述

紫砂壶Tab的核心特色是容量柱状图和养成年限进度条,双重可视化让收藏者从容量和养成两个维度评估紫砂壶。

  // Tab2 紫砂壶:容量柱状图 + 养成年限
  @Builder
  ZishaTab() {
    Column() {
      Row() {
        Text('🫖 紫砂壶专区')
          .fontSize(15)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Column()
          .layoutWeight(1)
        Text('共 ' + this.zishaList.length + ' 把')
          .fontSize(11)
          .fontColor('#9A8C7A')
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 14 })

      Text('壶身容量分布(ml)')
        .fontSize(14)
        .fontWeight(FontWeight.Bold)
        .fontColor('#3E2723')
        .margin({ top: 12, left: 16 })

      Row() {
        ForEach(this.zishaList, (item: ZishaItem) => {
          Column() {
            Column()
              .width(22)
              .height(item.capacity / this.maxCapacity() * 115)
              .backgroundColor(item.capacity > 250 ? '#4E342E' : (item.capacity > 180 ? '#B08D57' : '#558B2F'))
              .borderRadius(5)
            Text(item.capacity + '')
              .fontSize(8)
              .fontColor('#9A8C7A')
              .margin({ top: 3 })
          }
          .justifyContent(FlexAlign.End)
          .alignItems(HorizontalAlign.Center)
          .layoutWeight(1)
        }, (item: ZishaItem) => item.name)
      }
      .width('100%')
      .height(155)
      .padding({ left: 10, right: 10 })
      .backgroundColor('#FFFFFF')
      .borderRadius(12)
      .margin({ top: 8, left: 12, right: 12 })
      .justifyContent(FlexAlign.End)

      ForEach(this.zishaList, (item: ZishaItem) => {
        Row() {
          Column() {
            Text('🫖')
              .fontSize(26)
          }
          .width(54)
          .height(54)
          .justifyContent(FlexAlign.Center)
          .backgroundColor('#EFE7D8')
          .borderRadius(10)

          Column() {
            Text(item.name)
              .fontSize(13)
              .fontWeight(FontWeight.Medium)
              .fontColor('#3E2723')
              .maxLines(1)
            Row() {
              Text(item.clay)
                .fontSize(9)
                .fontColor('#FFFFFF')
                .padding({ left: 5, right: 5, top: 1, bottom: 1 })
                .backgroundColor('#4E342E')
                .borderRadius(3)
              Text(item.capacity + 'ml')
                .fontSize(10)
                .fontColor('#9A8C7A')
                .margin({ left: 6 })
            }
            .margin({ top: 4 })
            Row() {
              Text('养成')
                .fontSize(9)
                .fontColor('#9A8C7A')
              Column()
                .width(item.aged / this.maxAge() * 55)
                .height(3)
                .backgroundColor('#558B2F')
                .borderRadius(2)
                .margin({ left: 4 })
              Text(item.aged + ' 年')
                .fontSize(9)
                .fontColor('#9A8C7A')
                .margin({ left: 4 })
            }
            .margin({ top: 5 })
          }
          .alignItems(HorizontalAlign.Start)
          .layoutWeight(1)
          .margin({ left: 10 })

          Text('¥' + item.price)
            .fontSize(15)
            .fontWeight(FontWeight.Bold)
            .fontColor('#B07D2B')
        }
        .width('100%')
        .padding(11)
        .backgroundColor('#FFFFFF')
        .borderRadius(12)
        .margin({ left: 12, right: 12, top: 6 })
      }, (item: ZishaItem) => item.name)

      Column()
        .height(20)
    }
    .width('100%')
  }

容量柱状图整体高度155,每根柱子宽度22,高度通过item.capacity / this.maxCapacity() * 115计算——以数据集中最大容量值为满刻度。柱子颜色三段分级:容量大于250ml的大壶使用茶褐#4E342E,大于180ml的中壶使用铜金#B08D57,低于180ml的小壶使用竹青#558B2F。列表卡片中展示壶名、泥料标签和容量数值,泥料标签使用茶褐背景突出显示。养成年限进度条宽度为item.aged / this.maxAge() * 55,统一使用竹青色#558B2F表示养成时间——在紫砂壶收藏领域,养成年限越长通常意味着壶的润感越好、价值越高,因此使用代表生机的竹青色作为正向标识。这种"柱状图+进度条"的双重可视化设计,让收藏者可以从容量和养成两个维度快速评估紫砂壶。

Tab3 盖碗茶杯:双列网格与窑口标签

盖碗茶杯Tab采用双列网格布局,每张卡片展示窑口标签和品相状态。特别的是,该Tab使用if-else条件渲染展示"全品无瑕疵"或"微磕已修复"两种状态文字。

  // Tab3 盖碗茶杯:双列网格 + 窑口
  @Builder
  CupTab() {
    Column() {
      Row() {
        Text('🍵 盖碗茶杯专区')
          .fontSize(15)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Column()
          .layoutWeight(1)
        Text('共 ' + this.cupList.length + ' 只')
          .fontSize(11)
          .fontColor('#9A8C7A')
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 14 })

      Row() {
        ForEach(this.cupList, (item: CupItem) => {
          Column() {
            Column() {
              Text('🍵')
                .fontSize(34)
                .scale({ x: 1.08, y: 1.08 })
                .animation({ duration: 1100, iterations: -1, curve: Curve.EaseInOut })
            }
            .width('100%')
            .height(72)
            .justifyContent(FlexAlign.Center)
            .backgroundColor('#EFE7D8')
            .borderRadius(10)

            Text(item.name)
              .fontSize(12)
              .fontWeight(FontWeight.Medium)
              .fontColor('#3E2723')
              .margin({ top: 6 })
              .maxLines(1)
            Row() {
              Text(item.kiln)
                .fontSize(9)
                .fontColor('#FFFFFF')
                .padding({ left: 5, right: 5, top: 1, bottom: 1 })
                .backgroundColor('#B08D57')
                .borderRadius(3)
              Text(item.size)
                .fontSize(9)
                .fontColor('#9A8C7A')
                .margin({ left: 5 })
            }
            .margin({ top: 4 })
            if (item.flawless) {
              Text('全品无瑕疵')
                .fontSize(9)
                .fontColor('#2E7D32')
                .margin({ top: 3 })
            } else {
              Text('微磕已修复')
                .fontSize(9)
                .fontColor('#EF6C00')
                .margin({ top: 3 })
            }
            Text('¥' + item.price)
              .fontSize(14)
              .fontWeight(FontWeight.Bold)
              .fontColor('#B07D2B')
              .margin({ top: 4 })
          }
          .width('47%')
          .padding(10)
          .backgroundColor('#FFFFFF')
          .borderRadius(12)
          .margin({ top: 8 })
          .alignItems(HorizontalAlign.Start)
        }, (item: CupItem) => item.name)
      }
      .width('94%')
      .margin({ left: 12 })
      .flexWrap(FlexWrap.Wrap)

      Column()
        .height(20)
    }
    .width('100%')
  }

双列网格卡片区域外层Row设置flexWrap(FlexWrap.Wrap)和宽度94%,每张卡片宽度47%。卡片顶部72高度的图标区域中,茶杯emoji附带scaleanimation实现持续缩放动画。窑口标签使用铜金#B08D57背景,这是本应用独特的标签配色——窑口是盖碗茶杯最重要的产地标识,如建阳窑、晓芳窑、龙泉窑、定窑、哥窑、钧窑、吉州窑等,每个窑口都有独特的工艺特征和收藏价值。品相状态文字使用if (item.flawless)条件渲染:全品无瑕疵显示绿色#2E7D32,微磕已修复显示橙色#EF6C00,让买家一眼了解器物的品相状况。这种布尔值驱动的条件渲染在茶器交易中具有特殊意义——茶器的瑕疵状况直接影响价格和价值判断。

Tab4 茶饼老茶:年份柱状图与鉴定入口

茶饼老茶Tab展示年份柱状图和茶叶列表,区域标题旁设置"免费鉴定"入口按钮,点击后打开老茶鉴定弹窗。

  // Tab4 茶饼老茶:年份柱状图 + 列表
  @Builder
  CakeTab() {
    Column() {
      Row() {
        Text('🧱 茶饼老茶专区')
          .fontSize(15)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Column()
          .layoutWeight(1)
        Text('免费鉴定')
          .fontSize(12)
          .fontColor('#FFFFFF')
          .padding({ left: 12, right: 12, top: 4, bottom: 4 })
          .backgroundColor('#558B2F')
          .borderRadius(12)
          .onClick(() => {
            this.showVerify = true
          })
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 14 })

      Text('茶饼年份分布(年)')
        .fontSize(14)
        .fontWeight(FontWeight.Bold)
        .fontColor('#3E2723')
        .margin({ top: 12, left: 16 })

      Row() {
        ForEach(this.cakeList, (item: CakeItem) => {
          Column() {
            Column()
              .width(22)
              .height((item.year - 1996) / 28 * 115)
              .backgroundColor(item.year < 2016 ? '#4E342E' : (item.year < 2021 ? '#B08D57' : '#558B2F'))
              .borderRadius(5)
            Text(item.year + '')
              .fontSize(8)
              .fontColor('#9A8C7A')
              .margin({ top: 3 })
          }
          .justifyContent(FlexAlign.End)
          .alignItems(HorizontalAlign.Center)
          .layoutWeight(1)
        }, (item: CakeItem) => item.name)
      }
      .width('100%')
      .height(155)
      .padding({ left: 10, right: 10 })
      .backgroundColor('#FFFFFF')
      .borderRadius(12)
      .margin({ top: 8, left: 12, right: 12 })
      .justifyContent(FlexAlign.End)

      ForEach(this.cakeList, (item: CakeItem) => {
        Row() {
          Column() {
            Text('🧱')
              .fontSize(26)
          }
          .width(54)
          .height(54)
          .justifyContent(FlexAlign.Center)
          .backgroundColor('#EFE7D8')
          .borderRadius(10)

          Column() {
            Text(item.name)
              .fontSize(13)
              .fontWeight(FontWeight.Medium)
              .fontColor('#3E2723')
              .maxLines(1)
            Row() {
              Text(item.kind)
                .fontSize(9)
                .fontColor('#FFFFFF')
                .padding({ left: 5, right: 5, top: 1, bottom: 1 })
                .backgroundColor('#4E342E')
                .borderRadius(3)
              Text(item.year + ' 年 · ' + item.weight + 'g')
                .fontSize(10)
                .fontColor('#9A8C7A')
                .margin({ left: 6 })
            }
            .margin({ top: 4 })
          }
          .alignItems(HorizontalAlign.Start)
          .layoutWeight(1)
          .margin({ left: 10 })

          Text('¥' + item.price)
            .fontSize(15)
            .fontWeight(FontWeight.Bold)
            .fontColor('#B07D2B')
        }
        .width('100%')
        .padding(11)
        .backgroundColor('#FFFFFF')
        .borderRadius(12)
        .margin({ left: 12, right: 12, top: 6 })
      }, (item: CakeItem) => item.name)

      Column()
        .height(20)
    }
    .width('100%')
  }

年份柱状图的高度计算方式独特:(item.year - 1996) / 28 * 115。这里以1996年作为基准年份(数据集中最早年份为1998年),以28年(1996到2024年)作为跨度范围,将年份值映射为柱子高度。这种将时间数据映射为空间高度的设计,让老茶的年份陈化程度一目了然——柱子越高表示年份越老、陈化越深。柱子颜色三段分级:2016年以前的老茶使用茶褐#4E342E,2016-2020年的中期茶使用铜金#B08D57,2021年及以后的新茶使用竹青#558B2F。列表卡片展示茶名、茶类标签、年份和重量,茶类标签使用茶褐背景。区域标题旁的"免费鉴定"按钮使用竹青背景,点击后打开老茶鉴定弹窗。

Tab5 茶友会:社区帖子列表

茶友会Tab是本应用区别于其他两个同类应用的独特功能模块,展示社区帖子列表,包含发帖人信息、帖子标题、正文和互动数据。

  // Tab5 茶友会:帖子列表
  @Builder
  ClubTab() {
    Column() {
      Row() {
        Text('👨‍🌾 茶友会')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Column()
          .layoutWeight(1)
        Text('+ 发帖')
          .fontSize(12)
          .fontColor('#FFFFFF')
          .padding({ left: 12, right: 12, top: 4, bottom: 4 })
          .backgroundColor('#B08D57')
          .borderRadius(12)
          .onClick(() => {
            this.showPublish = true
          })
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 14 })

      ForEach(this.clubList, (item: ClubItem) => {
        Column() {
          Row() {
            Column() {
              Text('🍵')
                .fontSize(24)
            }
            .width(44)
            .height(44)
            .justifyContent(FlexAlign.Center)
            .backgroundColor('#EFE7D8')
            .borderRadius(22)

            Column() {
              Text(item.user)
                .fontSize(12)
                .fontWeight(FontWeight.Medium)
                .fontColor('#4E342E')
              Text('资深茶友 · 粉丝 3.2k')
                .fontSize(9)
                .fontColor('#9A8C7A')
                .margin({ top: 2 })
            }
            .alignItems(HorizontalAlign.Start)
            .layoutWeight(1)
            .margin({ left: 10 })
          }
          .width('100%')

          Text(item.title)
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
            .margin({ top: 8 })
            .width('100%')
          Text(item.content)
            .fontSize(11)
            .fontColor('#8D6E63')
            .margin({ top: 4 })
            .width('100%')
            .maxLines(2)

          Row() {
            Text('💬 ' + item.replies)
              .fontSize(11)
              .fontColor('#9A8C7A')
            Text('❤️ ' + item.likes)
              .fontSize(11)
              .fontColor('#B07D2B')
              .margin({ left: 16 })
              .scale({ x: 1.05, y: 1.05 })
              .animation({ duration: 900, iterations: -1, curve: Curve.EaseInOut })
            Column()
              .layoutWeight(1)
            Text('查看')
              .fontSize(10)
              .fontColor('#4E342E')
          }
          .width('100%')
          .margin({ top: 8 })
        }
        .width('100%')
        .padding(12)
        .backgroundColor('#FFFFFF')
        .borderRadius(12)
        .margin({ left: 12, right: 12, top: 6 })
      }, (item: ClubItem) => item.title)

      Column()
        .height(20)
    }
    .width('100%')
  }

帖子卡片采用上中下三段布局:上段是发帖人头像(44x44圆形)、用户名和身份标签(“资深茶友·粉丝3.2k”);中段是帖子标题(14号粗体)和正文(11号,最多两行maxLines(2));下段是互动数据行,包含回复数和点赞数。点赞数使用铜金色#B07D2B并附带scaleanimation实现持续脉冲缩放动画,吸引用户关注互动热度。区域标题旁的"+发帖"按钮使用铜金背景,点击后复用发布弹窗(this.showPublish = true)。ForEach的键值使用item.title,因为帖子标题在数据集中是唯一的。社区帖子列表的引入使应用从单纯的交易平台升级为"交易+社交"复合型平台,为茶友提供了知识分享和经验交流的空间。

应用交互流程图

茶器集市

紫砂壶

盖碗茶杯

茶饼老茶

茶友会

消息

我的

点击列表项

点击发布/发帖

点击养护入口

点击鉴定入口

点击消息项

点击求鉴定

点击聊卖家

点击遮罩/关闭

选择类别+输入价格

点击遮罩/关闭

选择类型+频次

点击获取方案

点击取消/遮罩

选择年份+茶类

点击提交鉴定

点击取消/遮罩

应用启动

渲染头部+Tab导航+内容区

用户选择Tab

MarketTab: 渐变横幅+茶器列表+品相进度

ZishaTab: 容量柱状图+养成年限进度

CupTab: 双列网格+窑口标签+品相状态

CakeTab: 年份柱状图+茶类标签+鉴定入口

ClubTab: 社区帖子列表+互动数据

NoteTab: 消息列表+未读徽标

MineTab: 用户卡片+统计+服务入口

用户操作

打开详情弹窗 DetailDialog

打开发布弹窗 PublishDialog

打开养护弹窗 CareDialog

打开老茶鉴定弹窗 VerifyDialog

打开删除确认弹窗 DeleteDialog

详情弹窗操作

关闭详情弹窗

发布弹窗操作

点击立即发布

养护弹窗操作

组合生成养护方案+脉冲动画

关闭弹窗

鉴定弹窗操作

输出鉴定结果+脉冲动画

弹窗系统:发布茶器与茶器养护

发布弹窗从底部滑出,茶器养护弹窗居中显示并提供类型与频次的双维度选择。

  // 弹框1:发布茶器(底部)
  @Builder
  PublishDialog() {
    Column() {
      Column() {
        Row() {
          Text('发布闲置茶器')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
          Column()
            .layoutWeight(1)
          Text('✕')
            .fontSize(16)
            .fontColor('#9E9E9E')
            .onClick(() => {
              this.showPublish = false
            })
        }
        .width('100%')

        Text('器物类别')
          .fontSize(12)
          .fontColor('#9A8C7A')
          .margin({ top: 14 })
        Row() {
          ForEach(this.itemKinds, (k: string, index: number) => {
            Text(k)
              .fontSize(11)
              .fontColor(this.itemKind === index ? '#FFFFFF' : '#4E342E')
              .padding({ left: 10, right: 10, top: 5, bottom: 5 })
              .backgroundColor(this.itemKind === index ? '#4E342E' : '#EFE7D8')
              .borderRadius(13)
              .margin({ right: 8 })
              .onClick(() => {
                this.itemKind = index
              })
          }, (k: string) => k)
        }
        .margin({ top: 8 })

        Text('期望售价(¥)')
          .fontSize(12)
          .fontColor('#9A8C7A')
          .margin({ top: 14 })
        Text(this.priceField === '' ? '输入价格,如 880' : '¥ ' + this.priceField)
          .fontSize(14)
          .fontColor(this.priceField === '' ? '#C4B49A' : '#3E2723')
          .padding(12)
          .backgroundColor('#EFE7D8')
          .borderRadius(10)
          .width('100%')
          .margin({ top: 8 })
          .onClick(() => {
            this.priceField = '880'
          })

        Text('名匠紫砂支持鉴定复核 · 茶饼附仓储记录')
          .fontSize(10)
          .fontColor('#9A8C7A')
          .margin({ top: 10 })

        Text('立即发布')
          .fontSize(15)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
          .width('100%')
          .textAlign(TextAlign.Center)
          .padding({ top: 12, bottom: 12 })
          .backgroundColor('#558B2F')
          .borderRadius(24)
          .margin({ top: 16 })
          .onClick(() => {
            this.showPublish = false
          })
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#FFFFFF')
      .borderRadius({ topLeft: 20, topRight: 20 })
      .constraintSize({ maxHeight: '80%' })
      .onClick((event: ClickEvent) => {
        event.stopPropagation()
      })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.End)
    .backgroundColor('#66000000')
    .onClick(() => {
      this.showPublish = false
    })
  }

发布弹窗采用底部滑出式,器物类别选择区遍历itemKinds数组(紫砂壶、盖碗茶杯、茶饼老茶、茶盘壶承、煮水器具、茶道配件)渲染标签。选中状态使用茶褐背景,未选中使用米宣色#EFE7D8。价格输入区使用Text模拟输入框,点击后填充预设值880。发布按钮使用竹青#558B2F背景和白色文字。内容卡片设置stopPropagation阻止事件冒泡。

  // 弹框2:茶器养护(类型 + 频次)
  @Builder
  CareDialog() {
    Column() {
      Column() {
        Text('🫖 茶器养护指导')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')

        Text('选择器物类型')
          .fontSize(12)
          .fontColor('#9A8C7A')
          .margin({ top: 14 })
          .width('100%')

        Row() {
          ForEach(this.careKinds, (k: string, index: number) => {
            Text(k)
              .fontSize(10)
              .fontColor(this.careKind === index ? '#FFFFFF' : '#4E342E')
              .padding({ left: 8, right: 8, top: 5, bottom: 5 })
              .backgroundColor(this.careKind === index ? '#4E342E' : '#EFE7D8')
              .borderRadius(12)
              .margin({ right: 6, top: 6 })
              .onClick(() => {
                this.careKind = index
              })
          }, (k: string) => k)
        }
        .width('100%')
        .flexWrap(FlexWrap.Wrap)

        Text('养护频次')
          .fontSize(12)
          .fontColor('#9A8C7A')
          .margin({ top: 14 })
          .width('100%')

        Row() {
          ForEach(this.careFreqs, (f: string, index: number) => {
            Text(f)
              .fontSize(10)
              .fontColor(this.careFreq === index ? '#FFFFFF' : '#558B2F')
              .padding({ left: 8, right: 8, top: 5, bottom: 5 })
              .backgroundColor(this.careFreq === index ? '#558B2F' : '#E7E9D8')
              .borderRadius(12)
              .margin({ right: 6, top: 6 })
              .onClick(() => {
                this.careFreq = index
              })
          }, (f: string) => f)
        }
        .width('100%')
        .flexWrap(FlexWrap.Wrap)

        Row() {
          Column() {
            Row() {
              Text('方案:')
                .fontSize(12)
                .fontColor('#8D6E63')
              Text(this.careKinds[this.careKind] + ' · ' + this.careFreqs[this.careFreq])
                .fontSize(13)
                .fontWeight(FontWeight.Bold)
                .fontColor('#B07D2B')
                .scale({ x: 1.06, y: 1.06 })
                .animation({ duration: 800, iterations: -1, curve: Curve.EaseInOut })
            }
          }
          .alignItems(HorizontalAlign.Start)
        }
        .margin({ top: 12 })

        Row() {
          Text('获取方案')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFFFFF')
            .padding({ left: 22, right: 22, top: 10, bottom: 10 })
            .backgroundColor('#4E342E')
            .borderRadius(20)
            .onClick(() => {
              this.showCare = false
            })
          Text('取消')
            .fontSize(14)
            .fontColor('#9A8C7A')
            .padding({ left: 22, right: 22, top: 10, bottom: 10 })
            .backgroundColor('#EFE7D8')
            .borderRadius(20)
            .margin({ left: 12 })
            .onClick(() => {
              this.showCare = false
            })
        }
        .margin({ top: 18 })
      }
      .width('86%')
      .padding(20)
      .backgroundColor('#FFFFFF')
      .borderRadius(16)
      .onClick((event: ClickEvent) => {
        event.stopPropagation()
      })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#66000000')
    .onClick(() => {
      this.showCare = false
    })
  }

茶器养护弹窗是本应用最具特色的弹窗之一,采用双维度选择模式:第一维度是器物类型(careKinds数组:紫砂壶、建盏、青瓷盖碗、银壶、铸铁壶),第二维度是养护频次(careFreqs数组:每周一次、半月一次、每月一次、季度一次)。两个选择区均使用flexWrap(FlexWrap.Wrap)自动换行。选择结果通过this.careKinds[this.careKind] + ' · ' + this.careFreqs[this.careFreq]组合显示为完整方案文字,如"紫砂壶·半月一次"。方案文字附带scaleanimation实现持续脉冲缩放动画。类型标签选中时使用茶褐背景,频次标签选中时使用竹青背景,通过颜色区分两个维度的选择状态。底部操作区包含"获取方案"(茶褐背景)和"取消"(米宣色背景)两个按钮。

弹窗系统:老茶鉴定与详情侧滑

  // 弹框3:老茶鉴定(年份 + 茶种)
  @Builder
  VerifyDialog() {
    Column() {
      Column() {
        Text('🏺 老茶鉴定')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')

        Text('生产年份')
          .fontSize(12)
          .fontColor('#9A8C7A')
          .margin({ top: 14 })
          .width('100%')

        Row() {
          ForEach([1998, 2005, 2012, 2015, 2018, 2020], (y: number) => {
            Text(y + ' 年')
              .fontSize(10)
              .fontColor(this.verifyYear === y ? '#FFFFFF' : '#B07D2B')
              .padding({ left: 8, right: 8, top: 5, bottom: 5 })
              .backgroundColor(this.verifyYear === y ? '#B08D57' : '#EFE7D8')
              .borderRadius(12)
              .margin({ right: 6, top: 6 })
              .onClick(() => {
                this.verifyYear = y
              })
          }, (y: number) => y + '')
        }
        .width('100%')
        .flexWrap(FlexWrap.Wrap)

        Text('茶类')
          .fontSize(12)
          .fontColor('#9A8C7A')
          .margin({ top: 14 })
          .width('100%')

        Row() {
          ForEach(this.verifyKinds, (k: string, index: number) => {
            Text(k)
              .fontSize(10)
              .fontColor(this.verifyKind === index ? '#FFFFFF' : '#558B2F')
              .padding({ left: 8, right: 8, top: 5, bottom: 5 })
              .backgroundColor(this.verifyKind === index ? '#558B2F' : '#E7E9D8')
              .borderRadius(12)
              .margin({ right: 6, top: 6 })
              .onClick(() => {
                this.verifyKind = index
              })
          }, (k: string) => k)
        }
        .width('100%')
        .flexWrap(FlexWrap.Wrap)

        Column() {
          Row() {
            Text('鉴定结果:')
              .fontSize(12)
              .fontColor('#8D6E63')
            Text('仓储干净 ✓ 年份相符')
              .fontSize(12)
              .fontWeight(FontWeight.Bold)
              .fontColor('#2E7D32')
              .scale({ x: 1.06, y: 1.06 })
              .animation({ duration: 800, iterations: -1, curve: Curve.EaseInOut })
          }
          .width('100%')
          Text('棉纸包装 · 内飞齐全 · 干仓无异味')
            .fontSize(10)
            .fontColor('#9A8C7A')
            .margin({ top: 6 })
        }
        .width('100%')
        .padding(12)
        .backgroundColor('#F5F1E8')
        .borderRadius(10)
        .margin({ top: 14 })

        Row() {
          Text('提交鉴定')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFFFFF')
            .padding({ left: 22, right: 22, top: 10, bottom: 10 })
            .backgroundColor('#558B2F')
            .borderRadius(20)
            .onClick(() => {
              this.showVerify = false
            })
          Text('取消')
            .fontSize(14)
            .fontColor('#9A8C7A')
            .padding({ left: 22, right: 22, top: 10, bottom: 10 })
            .backgroundColor('#EFE7D8')
            .borderRadius(20)
            .margin({ left: 12 })
            .onClick(() => {
              this.showVerify = false
            })
        }
        .margin({ top: 18 })
      }
      .width('86%')
      .padding(20)
      .backgroundColor('#FFFFFF')
      .borderRadius(16)
      .onClick((event: ClickEvent) => {
        event.stopPropagation()
      })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#66000000')
    .onClick(() => {
      this.showVerify = false
    })
  }

老茶鉴定弹窗与养护弹窗结构相似,同样是双维度选择模式:第一维度是生产年份(内联数组[1998, 2005, 2012, 2015, 2018, 2020]),第二维度是茶类(verifyKinds数组:普洱生茶、普洱熟茶、福鼎白茶、安化黑茶、武夷岩茶)。年份标签选中时使用铜金#B08D57背景,茶类标签选中时使用竹青#558B2F背景。鉴定结果区域显示"仓储干净✓年份相符"(绿色文字#2E7D32)和辅助信息"棉纸包装·内飞齐全·干仓无异味",这些是老茶鉴定中的专业术语——"内飞"是普洱茶饼内嵌的防伪标识,"干仓"指干燥仓储环境。结果文字附带脉冲动画,吸引用户关注。

  // 弹框5:茶器详情(右侧滑出)
  @Builder
  DetailDialog() {
    Column() {
      Column() {
        Row() {
          Text('茶器详情')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
          Column()
            .layoutWeight(1)
          Text('✕')
            .fontSize(16)
            .fontColor('#9E9E9E')
            .onClick(() => {
              this.showDetail = false
            })
        }
        .width('100%')

        Column() {
          Text('🫖')
            .fontSize(54)
        }
        .width('100%')
        .height(120)
        .justifyContent(FlexAlign.Center)
        .backgroundColor('#EFE7D8')
        .borderRadius(12)
        .margin({ top: 14 })

        Text(this.selName())
          .fontSize(17)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
          .margin({ top: 12 })
          .width('100%')

        Row() {
          Text(this.selBrand())
            .fontSize(11)
            .fontColor('#4E342E')
            .padding({ left: 8, right: 8, top: 3, bottom: 3 })
            .backgroundColor('#E3D5BC')
            .borderRadius(4)
          Text('· ' + this.selCity())
            .fontSize(11)
            .fontColor('#9A8C7A')
        }
        .margin({ top: 8 })

        Row() {
          Text('¥' + this.selPrice())
            .fontSize(26)
            .fontWeight(FontWeight.Bold)
            .fontColor('#B07D2B')
          Text('原价 ¥' + this.selOrigin())
            .fontSize(12)
            .fontColor('#C4B49A')
            .decoration({ type: TextDecorationType.LineThrough })
            .margin({ left: 10 })
        }
        .margin({ top: 12 })

        Column() {
          Row() {
            Text('品相完好度')
              .fontSize(12)
              .fontColor('#9A8C7A')
            Column()
              .layoutWeight(1)
            Text(this.selCraft() + '%')
              .fontSize(12)
              .fontWeight(FontWeight.Bold)
              .fontColor(this.selCraft() > 90 ? '#2E7D32' : '#EF6C00')
          }
          .width('100%')
          Stack({ alignContent: Alignment.Start }) {
            Column()
              .width('100%')
              .height(6)
              .backgroundColor('#EFE7D8')
              .borderRadius(3)
            Column()
              .width(this.selCraft() + '%')
              .height(6)
              .backgroundColor(this.selCraft() > 90 ? '#66BB6A' : '#B08D57')
              .borderRadius(3)
          }
          .width('100%')
          .height(6)
          .margin({ top: 6 })
          Text('平台实物拍摄 · 支持七天无理由退换')
            .fontSize(9)
            .fontColor('#9A8C7A')
            .margin({ top: 4 })
        }
        .width('100%')
        .padding(12)
        .backgroundColor('#F5F1E8')
        .borderRadius(10)
        .margin({ top: 14 })

        Row() {
          Text('求鉴定')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#558B2F')
            .padding({ left: 24, right: 24, top: 10, bottom: 10 })
            .backgroundColor('#FFFFFF')
            .border({ width: 1, color: '#558B2F', radius: 20 })
            .onClick(() => {
              this.showDetail = false
              this.showVerify = true
            })
          Text('聊卖家')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFFFFF')
            .padding({ left: 24, right: 24, top: 10, bottom: 10 })
            .backgroundColor('#4E342E')
            .borderRadius(20)
            .margin({ left: 12 })
            .onClick(() => {
              this.showDetail = false
            })
        }
        .margin({ top: 16 })
      }
      .width('88%')
      .height('100%')
      .padding(16)
      .backgroundColor('#FFFFFF')
      .borderRadius({ topLeft: 20, bottomLeft: 20 })
      .constraintSize({ maxHeight: '80%' })
      .onClick((event: ClickEvent) => {
        event.stopPropagation()
      })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.End)
    .backgroundColor('#66000000')
    .onClick(() => {
      this.showDetail = false
    })
  }

详情弹窗采用右侧滑出设计,品相完好度进度条使用Stack叠放,填充宽度为this.selCraft() + '%',颜色根据品相分值动态变化:大于90分使用绿色#66BB6A,低于90分使用铜金#B08D57。底部操作区包含"求鉴定"和"聊卖家"两个按钮。"求鉴定"按钮使用竹青边框样式(border属性),"聊卖家"使用茶褐实心背景。与前两个应用的详情弹窗不同,本应用的"求鉴定"按钮onClick事件先关闭详情弹窗再打开老茶鉴定弹窗,形成了详情-鉴定的弹窗跳转联动。这种设计使得用户在浏览器物详情后可以直接发起鉴定请求,缩短了操作路径。

技术点对比表

技术点实现方式适用场景优势分析注意事项
@State状态管理十一个状态变量驱动交互弹窗开关、Tab切换、双维度选择响应式更新,逻辑集中状态较多需注意命名规范
@Builder视图构建七Tab+五弹窗用@Builder多页面应用的视图拆分结构清晰,可读性强Builder内不能修改外部状态
ForEach列表渲染遍历数据数组生成项茶器列表、帖子列表、Tab导航支持键值diff优化键值需唯一
柱状图可视化Column高度按比例计算容量分布、年份分布纯原生实现,无依赖年份映射需处理时间跨度
进度条可视化Stack叠放两层Column品相完好度、养成年限轻量高效,样式灵活宽度百分比与像素转换
双列网格布局flexWrap+47%宽度盖碗茶杯卡片展示空间利用率高需注意卡片高度一致
布尔条件渲染if(item.flawless)品相状态文字展示语义清晰,二元判断仅适用布尔值场景
双维度组合弹窗类型选择+频次选择茶器养护、老茶鉴定多维度参数组合输出需处理两个选择状态联动
脉冲动画scale+animation无限循环方案文字、鉴定结果吸引用户关注结果无限循环需关注性能
linearGradient渐变colors数组定义色值头部、横幅、用户卡片主题氛围感强颜色格式需统一
社区帖子列表ForEach+多段布局茶友会Tab交易+社交复合功能帖子数据结构较复杂
flexWrap自动换行FlexWrap.Wrap标签选择区、养护弹窗自适应内容长度需设置合理间距
stopPropagationonClick中阻止冒泡弹窗内容区标准弹窗交互模式仅ClickEvent回调可用
constraintSize约束maxHeight限制高度发布弹窗、详情弹窗防止内容超出屏幕需配合内部滚动

安装DevEco Studio程序

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

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

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

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

在这里插入图片描述


完整代码:

// 茶褐竹青风格:茶褐 #4E342E / 竹青 #558B2F / 米宣 #F5F1E8 / 铜金 #B08D57
// 7 Tab:茶器集市 / 紫砂壶 / 盖碗茶杯 / 茶饼老茶 / 茶友会 / 消息 / 我的
// 5 弹框:发布茶器(底部) / 茶器养护(类型+频次) / 老茶鉴定(年份+茶种) / 删除确认(警示) / 茶器详情(右侧滑出)

interface TeaItem {
  name: string
  brand: string
  price: number
  originPrice: number
  craft: number
  city: string
  tag: string
}

interface ZishaItem {
  name: string
  clay: string
  capacity: number
  price: number
  aged: number
}

interface CupItem {
  name: string
  kiln: string
  size: string
  price: number
  flawless: boolean
}

interface CakeItem {
  name: string
  year: number
  kind: string
  price: number
  weight: number
}

interface ClubItem {
  user: string
  title: string
  content: string
  replies: number
  likes: number
}

interface NoteItem {
  name: string
  content: string
  time: string
  unread: number
  type: string
}

@Entry
@Component
struct TeaLoopApp {
  @State currentTab: number = 0
  @State showPublish: boolean = false
  @State showCare: boolean = false
  @State showVerify: boolean = false
  @State showDelete: boolean = false
  @State showDetail: boolean = false
  @State selectedItem: TeaItem | null = null
  @State careKind: number = 0
  @State careFreq: number = 0
  @State verifyYear: number = 2
  @State verifyKind: number = 0
  @State itemKind: number = 0
  @State priceField: string = ''

  private tabs: string[] = ['茶器集市', '紫砂壶', '盖碗茶杯', '茶饼老茶', '茶友会', '消息', '我的']

  private teaList: TeaItem[] = [
    { name: '顾景亭款 石瓢壶 280ml', brand: '宜兴紫砂', price: 6800, originPrice: 12800, craft: 98, city: '宜兴', tag: '收藏' },
    { name: '朱泥西施壶 150ml', brand: '茗岭陶坊', price: 880, originPrice: 1980, craft: 92, city: '潮州', tag: '热门' },
    { name: '段泥仿古壶 220ml', brand: '黄龙山', price: 1280, originPrice: 2800, craft: 90, city: '苏州', tag: '包邮' },
    { name: '天青泥井栏壶 200ml', brand: '宜兴紫砂', price: 2280, originPrice: 4600, craft: 95, city: '无锡', tag: '精品' },
    { name: '建盏 曜变束口盏', brand: '建阳窑', price: 1580, originPrice: 3200, craft: 96, city: '南平', tag: '精品' },
    { name: '汝窑天青开片盖碗', brand: '晓芳窑', price: 980, originPrice: 2100, craft: 93, city: '景德镇', tag: '95新' },
    { name: '青瓷莲花盖碗 140ml', brand: '龙泉窑', price: 320, originPrice: 720, craft: 88, city: '丽水', tag: '包邮' },
    { name: '白瓷手绘兰草盖碗', brand: '景德镇', price: 260, originPrice: 580, craft: 90, city: '景德镇', tag: '急出' },
    { name: '老白茶 2015 寿眉饼', brand: '福鼎磻溪', price: 480, originPrice: 980, craft: 100, city: '福鼎', tag: '干仓' },
    { name: '普洱生茶 2018 冰岛', brand: '勐库大寨', price: 2680, originPrice: 5200, craft: 100, city: '临沧', tag: '收藏' },
    { name: '普洱熟茶 2012 中茶', brand: '中茶牌', price: 1280, originPrice: 2600, craft: 100, city: '昆明', tag: '热门' },
    { name: '武夷岩茶 老丛水仙 50g', brand: '慧苑坑', price: 380, originPrice: 820, craft: 100, city: '武夷山', tag: '急出' },
    { name: '凤凰单丛 蜜兰香 100g', brand: '乌岽山', price: 220, originPrice: 480, craft: 100, city: '潮州', tag: '包邮' },
    { name: '整架电木茶盘 1米', brand: '茶人坊', price: 580, originPrice: 1280, craft: 85, city: '广州', tag: '自提' },
    { name: '老竹茶盘 端石壶承', brand: '手作社', price: 260, originPrice: 560, craft: 90, city: '杭州', tag: '95新' },
    { name: '锡制茶叶罐 500g', brand: '个旧锡器', price: 320, originPrice: 680, craft: 94, city: '个旧', tag: '包邮' },
    { name: '粗陶侧把壶 300ml', brand: '柴烧坊', price: 380, originPrice: 820, craft: 87, city: '景洪', tag: '急出' },
    { name: '玻璃公道杯 加厚', brand: '无印良品', price: 45, originPrice: 98, craft: 95, city: '上海', tag: '全新' },
    { name: '竹制茶则 六件套', brand: '竹艺斋', price: 88, originPrice: 198, craft: 92, city: '安吉', tag: '包邮' },
    { name: '铸铁壶 南部铁器 1L', brand: '岩手堂', price: 1280, originPrice: 2680, craft: 91, city: '大连', tag: '热门' },
    { name: '银壶 手工锤纹 800ml', brand: '大理银坊', price: 3680, originPrice: 7200, craft: 97, city: '大理', tag: '精品' },
    { name: '养壶笔 茶巾 套装', brand: '茶人坊', price: 38, originPrice: 88, craft: 100, city: '厦门', tag: '全新' },
    { name: '柴烧主人杯 束口', brand: '建水窑', price: 168, originPrice: 380, craft: 89, city: '建水', tag: '急出' },
    { name: '老青砖茶 1998 茯砖', brand: '安化茶厂', price: 880, originPrice: 1800, craft: 100, city: '安化', tag: '收藏' }
  ]

  private zishaList: ZishaItem[] = [
    { name: '顾款石瓢壶', clay: '紫泥', capacity: 280, price: 6800, aged: 18 },
    { name: '朱泥西施壶', clay: '朱泥', capacity: 150, price: 880, aged: 3 },
    { name: '段泥仿古壶', clay: '段泥', capacity: 220, price: 1280, aged: 6 },
    { name: '天青井栏壶', clay: '天青泥', capacity: 200, price: 2280, aged: 10 },
    { name: '底槽清掇球壶', clay: '底槽清', capacity: 260, price: 1680, aged: 8 },
    { name: '红皮龙龙蛋壶', clay: '红皮龙', capacity: 180, price: 980, aged: 4 },
    { name: '本绿潘壶', clay: '本山绿泥', capacity: 240, price: 1380, aged: 7 },
    { name: '紫泥德钟壶', clay: '紫泥', capacity: 300, price: 1180, aged: 5 },
    { name: '降坡泥水平壶', clay: '降坡泥', capacity: 160, price: 780, aged: 3 },
    { name: '朱泥思亭壶', clay: '朱泥', capacity: 140, price: 680, aged: 2 }
  ]

  private cupList: CupItem[] = [
    { name: '建盏 曜变束口', kiln: '建阳窑', size: '9cm', price: 1580, flawless: true },
    { name: '汝窑天青开片盏', kiln: '晓芳窑', size: '8cm', price: 580, flawless: true },
    { name: '龙泉青瓷莲花杯', kiln: '龙泉窑', size: '7cm', price: 180, flawless: false },
    { name: '景德镇白瓷兰草杯', kiln: '景德镇', size: '6cm', price: 120, flawless: true },
    { name: '建水柴烧束口杯', kiln: '建水窑', size: '8cm', price: 168, flawless: false },
    { name: '定窑白瓷印花盏', kiln: '定窑', size: '9cm', price: 680, flawless: true },
    { name: '哥窑金丝开片杯', kiln: '哥窑', size: '7cm', price: 480, flawless: true },
    { name: '钧窑窑变红斑盏', kiln: '钧窑', size: '8cm', price: 880, flawless: false },
    { name: '青花缠枝莲杯', kiln: '景德镇', size: '6cm', price: 220, flawless: true },
    { name: '黑釉木叶盏', kiln: '吉州窑', size: '9cm', price: 760, flawless: true }
  ]

  private cakeList: CakeItem[] = [
    { name: '冰岛老寨生茶', year: 2018, kind: '生普', price: 2680, weight: 357 },
    { name: '班章大树生茶', year: 2017, kind: '生普', price: 1880, weight: 357 },
    { name: '易武麻黑生茶', year: 2019, kind: '生普', price: 980, weight: 357 },
    { name: '中茶熟茶 7572', year: 2012, kind: '熟普', price: 1280, weight: 357 },
    { name: '福鼎寿眉老白茶', year: 2015, kind: '白茶', price: 480, weight: 350 },
    { name: '荒野牡丹王', year: 2020, kind: '白茶', price: 680, weight: 300 },
    { name: '安化黑茶茯砖', year: 1998, kind: '黑茶', price: 880, weight: 1000 },
    { name: '武夷老丛水仙', year: 2021, kind: '岩茶', price: 380, weight: 50 },
    { name: '凤凰单丛蜜兰香', year: 2022, kind: '单丛', price: 220, weight: 100 },
    { name: '正岩肉桂', year: 2020, kind: '岩茶', price: 520, weight: 50 },
    { name: '太平猴魁传统尖茶', year: 2021, kind: '绿茶', price: 260, weight: 100 },
    { name: '祁门红茶特茗级', year: 2022, kind: '红茶', price: 240, weight: 250 }
  ]

  private clubList: ClubItem[] = [
    { user: '壶里乾坤', title: '十年养壶心得分享', content: '一把紫砂壶养十年是什么效果,附全过程记录图。', replies: 86, likes: 320 },
    { user: '茶山行者', title: '今年春茶山行实录', content: '跑了三个山头,今年头采品质整体好于去年。', replies: 64, likes: 258 },
    { user: '盏友阿哲', title: '建盏开箱与养彩记录', content: '入手的曜变束口盏养彩三个月对比,惊喜。', replies: 42, likes: 190 },
    { user: '白茶岁月', title: '老白茶避坑指南', content: '做旧茶辨别六要点,建议收藏。', replies: 118, likes: 402 },
    { user: '岩骨花香', title: '正岩茶拼配的秘密', content: '聊拼配不是贬义,好拼配是艺术。', replies: 55, likes: 210 },
    { user: '器物之美', title: '雨天用玻璃壶煮老白茶', content: '咕嘟咕嘟的治愈感,推荐给茶友们。', replies: 38, likes: 168 },
    { user: '普洱老倌', title: '我的中期茶仓打理法', content: '昆明仓三年,转化记录和心得都在这。', replies: 92, likes: 356 },
    { user: '青瓷雅集', title: '龙泉窑口实地探访', content: '窑口师傅现场开窑,第一手视频记录。', replies: 47, likes: 198 },
    { user: '茶器阁掌柜', title: '本周茶器精选捡漏清单', content: '整理了 8 件性价比极高的闲置茶器。', replies: 73, likes: 289 },
    { user: '壶友小满', title: '新手第一把壶怎么选', content: '容量、泥料、器型三条建议,拒绝踩坑。', replies: 105, likes: 367 }
  ]

  private noteList: NoteItem[] = [
    { name: '壶里乾坤', content: '石瓢壶能约面验吗', time: '12:12', unread: 2, type: '买家' },
    { name: '系统通知', content: '老茶鉴定报告已生成', time: '11:38', unread: 1, type: '官方' },
    { name: '盏友阿哲', content: '建盏盏托原装吗', time: '10:22', unread: 0, type: '买家' },
    { name: '茶友会秘书', content: '本周六茶会照常举行', time: '昨天', unread: 1, type: '官方' },
    { name: '茗器严选铺', content: '回收闲置紫砂茶器', time: '昨天', unread: 0, type: '商家' },
    { name: '白茶岁月', content: '茶饼转化得不错', time: '昨天', unread: 0, type: '买家' },
    { name: '交易助手', content: '请确认收货并评价', time: '前天', unread: 0, type: '官方' },
    { name: '普洱老倌', content: '仓味退得差不多了', time: '前天', unread: 0, type: '买家' },
    { name: '安全提醒', content: '贵重茶器请选保价物流', time: '3天前', unread: 0, type: '官方' },
    { name: '养护工坊', content: '您的养壶指导已排期', time: '3天前', unread: 0, type: '官方' },
    { name: '茶山直供', content: '秋茶预订通道开启', time: '4天前', unread: 0, type: '商家' },
    { name: '青瓷雅集', content: '窑口直发已发货', time: '4天前', unread: 0, type: '买家' }
  ]

  private careKinds: string[] = ['紫砂壶', '建盏', '青瓷盖碗', '银壶', '铸铁壶']

  private careFreqs: string[] = ['每週一次', '半月一次', '每月一次', '季度一次']

  private verifyKinds: string[] = ['普洱生茶', '普洱熟茶', '福鼎白茶', '安化黑茶', '武夷岩茶']

  private itemKinds: string[] = ['紫砂壶', '盖碗茶杯', '茶饼老茶', '茶盘壶承', '煮水器具', '茶道配件']

  private maxCapacity(): number {
    let max: number = 0
    for (let i = 0; i < this.zishaList.length; i++) {
      if (this.zishaList[i].capacity > max) {
        max = this.zishaList[i].capacity
      }
    }
    return max
  }

  private maxAge(): number {
    let max: number = 0
    for (let i = 0; i < this.zishaList.length; i++) {
      if (this.zishaList[i].aged > max) {
        max = this.zishaList[i].aged
      }
    }
    return max
  }

  private selName(): string {
    if (this.selectedItem === null) {
      return ''
    }
    return this.selectedItem.name
  }

  private selPrice(): string {
    if (this.selectedItem === null) {
      return ''
    }
    return this.selectedItem.price + ''
  }

  private selBrand(): string {
    if (this.selectedItem === null) {
      return ''
    }
    return this.selectedItem.brand
  }

  private selCraft(): number {
    if (this.selectedItem === null) {
      return 0
    }
    return this.selectedItem.craft
  }

  private selCity(): string {
    if (this.selectedItem === null) {
      return ''
    }
    return this.selectedItem.city
  }

  private selOrigin(): string {
    if (this.selectedItem === null) {
      return ''
    }
    return this.selectedItem.originPrice + ''
  }

  build() {
    Column() {
      Column() {
        // 头部:静态茶器电商风
        Column() {
          Row() {
            Column() {
              Text('茶器流转阁')
                .fontSize(20)
                .fontWeight(FontWeight.Bold)
                .fontColor('#FFFFFF')
              Text('二手茶器老茶 一盏流转')
                .fontSize(11)
                .fontColor('#E8DCC8')
                .margin({ top: 2 })
            }
            .alignItems(HorizontalAlign.Start)

            Row() {
              Text('🍵')
                .fontSize(15)
              Text('搜茶器 / 泥料')
                .fontSize(12)
                .fontColor('#9A8C7A')
                .margin({ left: 6 })
            }
            .width(145)
            .height(32)
            .justifyContent(FlexAlign.Center)
            .backgroundColor('#F5F1E8')
            .borderRadius(16)
            .margin({ left: 12 })

            Text('发布')
              .fontSize(13)
              .fontWeight(FontWeight.Medium)
              .fontColor('#FFFFFF')
              .width(52)
              .height(30)
              .textAlign(TextAlign.Center)
              .backgroundColor('#558B2F')
              .borderRadius(15)
              .margin({ left: 10 })
              .onClick(() => {
                this.showPublish = true
              })
          }
          .width('100%')
          .padding({ left: 16, right: 16, top: 12, bottom: 10 })

          Row() {
            Text('🏺 老货鉴定')
              .fontSize(11)
              .fontColor('#E8DCC8')
              .margin({ right: 12 })
            Text('📜 溯源可查')
              .fontSize(11)
              .fontColor('#E8DCC8')
              .margin({ right: 12 })
            Text('🍵 茶样试饮')
              .fontSize(11)
              .fontColor('#E8DCC8')
          }
          .width('100%')
          .padding({ left: 16, bottom: 12 })
        }
        .width('100%')
        .backgroundColor('#4E342E')
        .borderRadius({ bottomLeft: 18, bottomRight: 18 })

        Scroll() {
          Column() {
            if (this.currentTab === 0) {
              this.MarketTab()
            } else if (this.currentTab === 1) {
              this.ZishaTab()
            } else if (this.currentTab === 2) {
              this.CupTab()
            } else if (this.currentTab === 3) {
              this.CakeTab()
            } else if (this.currentTab === 4) {
              this.ClubTab()
            } else if (this.currentTab === 5) {
              this.NoteTab()
            } else {
              this.MineTab()
            }
          }
          .width('100%')
          .alignItems(HorizontalAlign.Start)
        }
        .layoutWeight(1)
        .scrollBar(BarState.Off)
        .width('100%')

        Row() {
          ForEach(this.tabs, (tab: string, index: number) => {
            Column() {
              Text(this.tabIcon(index))
                .fontSize(18)
                .fontColor(this.currentTab === index ? '#4E342E' : '#9E9E9E')
              Text(tab)
                .fontSize(10)
                .fontColor(this.currentTab === index ? '#4E342E' : '#9E9E9E')
                .margin({ top: 2 })
              if (this.currentTab === index) {
                Column()
                  .width(20)
                  .height(3)
                  .backgroundColor('#B08D57')
                  .borderRadius(2)
                  .margin({ top: 3 })
              } else {
                Column()
                  .width(20)
                  .height(3)
                  .backgroundColor('#00000000')
                  .margin({ top: 3 })
              }
            }
            .justifyContent(FlexAlign.Center)
            .layoutWeight(1)
            .onClick(() => {
              this.currentTab = index
            })
          }, (tab: string) => tab)
        }
        .width('100%')
        .height(58)
        .backgroundColor('#FFFFFF')
        .border({ width: 1, color: '#E8DCC8', radius: 0 })
      }
      .width('100%')
      .height('100%')
      .backgroundColor('#F5F1E8')

      if (this.showPublish) {
        this.PublishDialog()
      }
      if (this.showCare) {
        this.CareDialog()
      }
      if (this.showVerify) {
        this.VerifyDialog()
      }
      if (this.showDelete) {
        this.DeleteDialog()
      }
      if (this.showDetail) {
        this.DetailDialog()
      }
    }
  }

  private tabIcon(index: number): string {
    let icons: string[] = ['🛍️', '🫖', '🍵', '🧱', '👨‍🌾', '💬', '👤']
    return icons[index]
  }

  // Tab1 茶器集市:行式列表
  @Builder
  MarketTab() {
    Column() {
      Row() {
        Column() {
          Text('🍵 秋茶季 · 器物焕新')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFFFFF')
          Text('闲置茶器折抵新器 立减 200')
            .fontSize(11)
            .fontColor('#E8DCC8')
            .margin({ top: 4 })
        }
        .alignItems(HorizontalAlign.Start)
        .layoutWeight(1)

        Text('🫖')
          .fontSize(36)
          .scale({ x: 1.12, y: 1.12 })
          .animation({ duration: 1000, iterations: -1, curve: Curve.EaseInOut })
      }
      .width('100%')
      .padding(16)
      .linearGradient({
        direction: GradientDirection.Right,
        colors: [['#4E342E', 0], ['#B08D57', 1]]
      })
      .borderRadius(14)
      .margin({ top: 12, left: 12, right: 12 })

      Row() {
        Text('茶器精选')
          .fontSize(15)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Column()
          .layoutWeight(1)
        Text('工艺优先 ▾')
          .fontSize(12)
          .fontColor('#4E342E')
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 14 })

      ForEach(this.teaList, (item: TeaItem) => {
        Row() {
          Column() {
            Text('🍵')
              .fontSize(26)
          }
          .width(66)
          .height(66)
          .justifyContent(FlexAlign.Center)
          .backgroundColor('#EFE7D8')
          .borderRadius(12)

          Column() {
            Text(item.name)
              .fontSize(13)
              .fontWeight(FontWeight.Medium)
              .fontColor('#3E2723')
              .maxLines(1)
              .textOverflow({ overflow: TextOverflow.Ellipsis })
            Row() {
              Text(item.brand)
                .fontSize(9)
                .fontColor('#4E342E')
                .padding({ left: 6, right: 6, top: 2, bottom: 2 })
                .backgroundColor('#E3D5BC')
                .borderRadius(4)
              Text('· ' + item.city)
                .fontSize(9)
                .fontColor('#9A8C7A')
                .margin({ left: 6 })
            }
            .margin({ top: 4 })
            Row() {
              Text('品相')
                .fontSize(9)
                .fontColor('#9A8C7A')
              Column()
                .width(item.craft / 100 * 55)
                .height(3)
                .backgroundColor(item.craft > 90 ? '#558B2F' : '#B08D57')
                .borderRadius(2)
                .margin({ left: 4 })
              Text(item.craft + '%')
                .fontSize(9)
                .fontColor('#9A8C7A')
                .margin({ left: 4 })
            }
            .margin({ top: 5 })
          }
          .alignItems(HorizontalAlign.Start)
          .layoutWeight(1)
          .margin({ left: 10 })

          Column() {
            Text('¥' + item.price)
              .fontSize(15)
              .fontWeight(FontWeight.Bold)
              .fontColor('#B07D2B')
            Text('¥' + item.originPrice)
              .fontSize(9)
              .fontColor('#C4B49A')
              .decoration({ type: TextDecorationType.LineThrough })
              .margin({ top: 2 })
            Text(item.tag)
              .fontSize(9)
              .fontColor('#FFFFFF')
              .padding({ left: 5, right: 5, top: 1, bottom: 1 })
              .backgroundColor('#558B2F')
              .borderRadius(3)
              .margin({ top: 3 })
          }
          .alignItems(HorizontalAlign.End)
        }
        .width('100%')
        .padding(11)
        .backgroundColor('#FFFFFF')
        .borderRadius(12)
        .margin({ left: 12, right: 12, top: 5 })
        .onClick(() => {
          this.selectedItem = item
          this.showDetail = true
        })
      }, (item: TeaItem) => item.name)

      Column()
        .height(20)
    }
    .width('100%')
  }

  // Tab2 紫砂壶:容量柱状图 + 养成年限
  @Builder
  ZishaTab() {
    Column() {
      Row() {
        Text('🫖 紫砂壶专区')
          .fontSize(15)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Column()
          .layoutWeight(1)
        Text('共 ' + this.zishaList.length + ' 把')
          .fontSize(11)
          .fontColor('#9A8C7A')
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 14 })

      Text('壶身容量分布(ml)')
        .fontSize(14)
        .fontWeight(FontWeight.Bold)
        .fontColor('#3E2723')
        .margin({ top: 12, left: 16 })

      Row() {
        ForEach(this.zishaList, (item: ZishaItem) => {
          Column() {
            Column()
              .width(22)
              .height(item.capacity / this.maxCapacity() * 115)
              .backgroundColor(item.capacity > 250 ? '#4E342E' : (item.capacity > 180 ? '#B08D57' : '#558B2F'))
              .borderRadius(5)
            Text(item.capacity + '')
              .fontSize(8)
              .fontColor('#9A8C7A')
              .margin({ top: 3 })
          }
          .justifyContent(FlexAlign.End)
          .alignItems(HorizontalAlign.Center)
          .layoutWeight(1)
        }, (item: ZishaItem) => item.name)
      }
      .width('100%')
      .height(155)
      .padding({ left: 10, right: 10 })
      .backgroundColor('#FFFFFF')
      .borderRadius(12)
      .margin({ top: 8, left: 12, right: 12 })
      .justifyContent(FlexAlign.End)

      ForEach(this.zishaList, (item: ZishaItem) => {
        Row() {
          Column() {
            Text('🫖')
              .fontSize(26)
          }
          .width(54)
          .height(54)
          .justifyContent(FlexAlign.Center)
          .backgroundColor('#EFE7D8')
          .borderRadius(10)

          Column() {
            Text(item.name)
              .fontSize(13)
              .fontWeight(FontWeight.Medium)
              .fontColor('#3E2723')
              .maxLines(1)
            Row() {
              Text(item.clay)
                .fontSize(9)
                .fontColor('#FFFFFF')
                .padding({ left: 5, right: 5, top: 1, bottom: 1 })
                .backgroundColor('#4E342E')
                .borderRadius(3)
              Text(item.capacity + 'ml')
                .fontSize(10)
                .fontColor('#9A8C7A')
                .margin({ left: 6 })
            }
            .margin({ top: 4 })
            Row() {
              Text('养成')
                .fontSize(9)
                .fontColor('#9A8C7A')
              Column()
                .width(item.aged / this.maxAge() * 55)
                .height(3)
                .backgroundColor('#558B2F')
                .borderRadius(2)
                .margin({ left: 4 })
              Text(item.aged + ' 年')
                .fontSize(9)
                .fontColor('#9A8C7A')
                .margin({ left: 4 })
            }
            .margin({ top: 5 })
          }
          .alignItems(HorizontalAlign.Start)
          .layoutWeight(1)
          .margin({ left: 10 })

          Text('¥' + item.price)
            .fontSize(15)
            .fontWeight(FontWeight.Bold)
            .fontColor('#B07D2B')
        }
        .width('100%')
        .padding(11)
        .backgroundColor('#FFFFFF')
        .borderRadius(12)
        .margin({ left: 12, right: 12, top: 6 })
      }, (item: ZishaItem) => item.name)

      Column()
        .height(20)
    }
    .width('100%')
  }

  // Tab3 盖碗茶杯:双列网格 + 窑口
  @Builder
  CupTab() {
    Column() {
      Row() {
        Text('🍵 盖碗茶杯专区')
          .fontSize(15)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Column()
          .layoutWeight(1)
        Text('共 ' + this.cupList.length + ' 只')
          .fontSize(11)
          .fontColor('#9A8C7A')
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 14 })

      Row() {
        ForEach(this.cupList, (item: CupItem) => {
          Column() {
            Column() {
              Text('🍵')
                .fontSize(34)
                .scale({ x: 1.08, y: 1.08 })
                .animation({ duration: 1100, iterations: -1, curve: Curve.EaseInOut })
            }
            .width('100%')
            .height(72)
            .justifyContent(FlexAlign.Center)
            .backgroundColor('#EFE7D8')
            .borderRadius(10)

            Text(item.name)
              .fontSize(12)
              .fontWeight(FontWeight.Medium)
              .fontColor('#3E2723')
              .margin({ top: 6 })
              .maxLines(1)
            Row() {
              Text(item.kiln)
                .fontSize(9)
                .fontColor('#FFFFFF')
                .padding({ left: 5, right: 5, top: 1, bottom: 1 })
                .backgroundColor('#B08D57')
                .borderRadius(3)
              Text(item.size)
                .fontSize(9)
                .fontColor('#9A8C7A')
                .margin({ left: 5 })
            }
            .margin({ top: 4 })
            if (item.flawless) {
              Text('全品无瑕疵')
                .fontSize(9)
                .fontColor('#2E7D32')
                .margin({ top: 3 })
            } else {
              Text('微磕已修复')
                .fontSize(9)
                .fontColor('#EF6C00')
                .margin({ top: 3 })
            }
            Text('¥' + item.price)
              .fontSize(14)
              .fontWeight(FontWeight.Bold)
              .fontColor('#B07D2B')
              .margin({ top: 4 })
          }
          .width('47%')
          .padding(10)
          .backgroundColor('#FFFFFF')
          .borderRadius(12)
          .margin({ top: 8 })
          .alignItems(HorizontalAlign.Start)
        }, (item: CupItem) => item.name)
      }
      .width('94%')
      .margin({ left: 12 })

      Column()
        .height(20)
    }
    .width('100%')
  }

  // Tab4 茶饼老茶:年份柱状图 + 列表
  @Builder
  CakeTab() {
    Column() {
      Row() {
        Text('🧱 茶饼老茶专区')
          .fontSize(15)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Column()
          .layoutWeight(1)
        Text('免费鉴定')
          .fontSize(12)
          .fontColor('#FFFFFF')
          .padding({ left: 12, right: 12, top: 4, bottom: 4 })
          .backgroundColor('#558B2F')
          .borderRadius(12)
          .onClick(() => {
            this.showVerify = true
          })
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 14 })

      Text('茶饼年份分布(年)')
        .fontSize(14)
        .fontWeight(FontWeight.Bold)
        .fontColor('#3E2723')
        .margin({ top: 12, left: 16 })

      Row() {
        ForEach(this.cakeList, (item: CakeItem) => {
          Column() {
            Column()
              .width(22)
              .height((item.year - 1996) / 28 * 115)
              .backgroundColor(item.year < 2016 ? '#4E342E' : (item.year < 2021 ? '#B08D57' : '#558B2F'))
              .borderRadius(5)
            Text(item.year + '')
              .fontSize(8)
              .fontColor('#9A8C7A')
              .margin({ top: 3 })
          }
          .justifyContent(FlexAlign.End)
          .alignItems(HorizontalAlign.Center)
          .layoutWeight(1)
        }, (item: CakeItem) => item.name)
      }
      .width('100%')
      .height(155)
      .padding({ left: 10, right: 10 })
      .backgroundColor('#FFFFFF')
      .borderRadius(12)
      .margin({ top: 8, left: 12, right: 12 })
      .justifyContent(FlexAlign.End)

      ForEach(this.cakeList, (item: CakeItem) => {
        Row() {
          Column() {
            Text('🧱')
              .fontSize(26)
          }
          .width(54)
          .height(54)
          .justifyContent(FlexAlign.Center)
          .backgroundColor('#EFE7D8')
          .borderRadius(10)

          Column() {
            Text(item.name)
              .fontSize(13)
              .fontWeight(FontWeight.Medium)
              .fontColor('#3E2723')
              .maxLines(1)
            Row() {
              Text(item.kind)
                .fontSize(9)
                .fontColor('#FFFFFF')
                .padding({ left: 5, right: 5, top: 1, bottom: 1 })
                .backgroundColor('#4E342E')
                .borderRadius(3)
              Text(item.year + ' 年 · ' + item.weight + 'g')
                .fontSize(10)
                .fontColor('#9A8C7A')
                .margin({ left: 6 })
            }
            .margin({ top: 4 })
          }
          .alignItems(HorizontalAlign.Start)
          .layoutWeight(1)
          .margin({ left: 10 })

          Text('¥' + item.price)
            .fontSize(15)
            .fontWeight(FontWeight.Bold)
            .fontColor('#B07D2B')
        }
        .width('100%')
        .padding(11)
        .backgroundColor('#FFFFFF')
        .borderRadius(12)
        .margin({ left: 12, right: 12, top: 6 })
      }, (item: CakeItem) => item.name)

      Column()
        .height(20)
    }
    .width('100%')
  }

  // Tab5 茶友会:帖子列表
  @Builder
  ClubTab() {
    Column() {
      Row() {
        Text('👨‍🌾 茶友会')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Column()
          .layoutWeight(1)
        Text('+ 发帖')
          .fontSize(12)
          .fontColor('#FFFFFF')
          .padding({ left: 12, right: 12, top: 4, bottom: 4 })
          .backgroundColor('#B08D57')
          .borderRadius(12)
          .onClick(() => {
            this.showPublish = true
          })
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 14 })

      ForEach(this.clubList, (item: ClubItem) => {
        Column() {
          Row() {
            Column() {
              Text('🍵')
                .fontSize(24)
            }
            .width(44)
            .height(44)
            .justifyContent(FlexAlign.Center)
            .backgroundColor('#EFE7D8')
            .borderRadius(22)

            Column() {
              Text(item.user)
                .fontSize(12)
                .fontWeight(FontWeight.Medium)
                .fontColor('#4E342E')
              Text('资深茶友 · 粉丝 3.2k')
                .fontSize(9)
                .fontColor('#9A8C7A')
                .margin({ top: 2 })
            }
            .alignItems(HorizontalAlign.Start)
            .layoutWeight(1)
            .margin({ left: 10 })
          }
          .width('100%')

          Text(item.title)
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
            .margin({ top: 8 })
            .width('100%')
          Text(item.content)
            .fontSize(11)
            .fontColor('#8D6E63')
            .margin({ top: 4 })
            .width('100%')
            .maxLines(2)

          Row() {
            Text('💬 ' + item.replies)
              .fontSize(11)
              .fontColor('#9A8C7A')
            Text('❤️ ' + item.likes)
              .fontSize(11)
              .fontColor('#B07D2B')
              .margin({ left: 16 })
              .scale({ x: 1.05, y: 1.05 })
              .animation({ duration: 900, iterations: -1, curve: Curve.EaseInOut })
            Column()
              .layoutWeight(1)
            Text('查看')
              .fontSize(10)
              .fontColor('#4E342E')
          }
          .width('100%')
          .margin({ top: 8 })
        }
        .width('100%')
        .padding(12)
        .backgroundColor('#FFFFFF')
        .borderRadius(12)
        .margin({ left: 12, right: 12, top: 6 })
      }, (item: ClubItem) => item.title)

      Column()
        .height(20)
    }
    .width('100%')
  }

  // Tab6 消息
  @Builder
  NoteTab() {
    Column() {
      Text('消息')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .fontColor('#3E2723')
        .margin({ top: 14, left: 16 })

      ForEach(this.noteList, (item: NoteItem) => {
        Row() {
          Column() {
            Text(this.noteIcon(item.type))
              .fontSize(22)
          }
          .width(46)
          .height(46)
          .justifyContent(FlexAlign.Center)
          .backgroundColor(item.type === '官方' ? '#EFE7D8' : '#E7E9D8')
          .borderRadius(23)

          Column() {
            Row() {
              Text(item.name)
                .fontSize(13)
                .fontWeight(FontWeight.Medium)
                .fontColor('#3E2723')
                .maxLines(1)
              Column()
                .layoutWeight(1)
              Text(item.time)
                .fontSize(10)
                .fontColor('#C4B49A')
            }
            .width('100%')
            Row() {
              Text(item.content)
                .fontSize(11)
                .fontColor('#9A8C7A')
                .maxLines(1)
                .layoutWeight(1)
              if (item.unread > 0) {
                Text(item.unread + '')
                  .fontSize(9)
                  .fontColor('#FFFFFF')
                  .padding({ left: 5, right: 5, top: 1, bottom: 1 })
                  .backgroundColor('#B07D2B')
                  .borderRadius(8)
                  .margin({ left: 6 })
              }
            }
            .width('100%')
            .margin({ top: 3 })
          }
          .layoutWeight(1)
          .margin({ left: 10 })
        }
        .width('100%')
        .padding(12)
        .backgroundColor('#FFFFFF')
        .borderRadius(12)
        .margin({ left: 12, right: 12, top: 5 })
        .onClick(() => {
          this.showDelete = true
        })
      }, (item: NoteItem) => item.name)

      Column()
        .height(20)
    }
    .width('100%')
  }

  private noteIcon(type: string): string {
    if (type === '官方') {
      return '📢'
    }
    if (type === '商家') {
      return '🏪'
    }
    return '💬'
  }

  // Tab7 我的
  @Builder
  MineTab() {
    Column() {
      Row() {
        Column() {
          Text('🍵')
            .fontSize(36)
        }
        .width(64)
        .height(64)
        .justifyContent(FlexAlign.Center)
        .backgroundColor('#E3D5BC')
        .borderRadius(32)

        Column() {
          Text('壶里乾坤')
            .fontSize(17)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFFFFF')
          Text('玩壶十二年 · 信用极好')
            .fontSize(11)
            .fontColor('#E8DCC8')
            .margin({ top: 4 })
        }
        .alignItems(HorizontalAlign.Start)
        .margin({ left: 12 })
      }
      .width('100%')
      .padding(16)
      .linearGradient({
        direction: GradientDirection.Right,
        colors: [['#4E342E', 0], ['#B08D57', 1]]
      })
      .borderRadius(14)
      .margin({ top: 12, left: 12, right: 12 })

      Row() {
        Column() {
          Text('9')
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
          Text('在售')
            .fontSize(10)
            .fontColor('#9A8C7A')
            .margin({ top: 2 })
        }
        .layoutWeight(1)
        Column() {
          Text('28')
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
          Text('卖出')
            .fontSize(10)
            .fontColor('#9A8C7A')
            .margin({ top: 2 })
        }
        .layoutWeight(1)
        Column() {
          Text('5.0')
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
          Text('评分')
            .fontSize(10)
            .fontColor('#9A8C7A')
            .margin({ top: 2 })
        }
        .layoutWeight(1)
        Column() {
          Text('5 把')
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor('#B07D2B')
          Text('在养好壶')
            .fontSize(10)
            .fontColor('#9A8C7A')
            .margin({ top: 2 })
        }
        .layoutWeight(1)
      }
      .width('100%')
      .padding(14)
      .backgroundColor('#FFFFFF')
      .borderRadius(12)
      .margin({ top: 10, left: 12, right: 12 })

      Text('我的服务')
        .fontSize(14)
        .fontWeight(FontWeight.Bold)
        .fontColor('#3E2723')
        .margin({ top: 16, left: 16 })

      Column() {
        Row() {
          Text('🏺')
            .fontSize(18)
          Text('老茶鉴定')
            .fontSize(13)
            .fontColor('#3E2723')
            .margin({ left: 10 })
          Column()
            .layoutWeight(1)
          Text('已鉴定 3 件')
            .fontSize(11)
            .fontColor('#2E7D32')
            .margin({ right: 6 })
          Text('>')
            .fontSize(14)
            .fontColor('#C4B49A')
        }
        .padding(14)
        .onClick(() => {
          this.showVerify = true
        })
        Row() {
          Text('🫖')
            .fontSize(18)
          Text('茶器养护指导')
            .fontSize(13)
            .fontColor('#3E2723')
            .margin({ left: 10 })
          Column()
            .layoutWeight(1)
          Text('在养 5 把')
            .fontSize(11)
            .fontColor('#558B2F')
            .margin({ right: 6 })
          Text('>')
            .fontSize(14)
            .fontColor('#C4B49A')
        }
        .padding(14)
        .onClick(() => {
          this.showCare = true
        })
        Row() {
          Text('💰')
            .fontSize(18)
          Text('我的钱包')
            .fontSize(13)
            .fontColor('#3E2723')
            .margin({ left: 10 })
          Column()
            .layoutWeight(1)
          Text('¥2,180.00')
            .fontSize(12)
            .fontColor('#B07D2B')
            .margin({ right: 6 })
          Text('>')
            .fontSize(14)
            .fontColor('#C4B49A')
        }
        .padding(14)
        Row() {
          Text('📦')
            .fontSize(18)
          Text('我的订单')
            .fontSize(13)
            .fontColor('#3E2723')
            .margin({ left: 10 })
          Column()
            .layoutWeight(1)
          Text('1 待发货')
            .fontSize(11)
            .fontColor('#EF6C00')
            .margin({ right: 6 })
          Text('>')
            .fontSize(14)
            .fontColor('#C4B49A')
        }
        .padding(14)
      }
      .width('100%')
      .backgroundColor('#FFFFFF')
      .borderRadius(12)
      .margin({ top: 8, left: 12, right: 12 })

      Column()
        .height(20)
    }
    .width('100%')
  }

  // 弹框1:发布茶器(底部)
  @Builder
  PublishDialog() {
    Column() {
      Column() {
        Row() {
          Text('发布闲置茶器')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
          Column()
            .layoutWeight(1)
          Text('✕')
            .fontSize(16)
            .fontColor('#9E9E9E')
            .onClick(() => {
              this.showPublish = false
            })
        }
        .width('100%')

        Text('器物类别')
          .fontSize(12)
          .fontColor('#9A8C7A')
          .margin({ top: 14 })
        Row() {
          ForEach(this.itemKinds, (k: string, index: number) => {
            Text(k)
              .fontSize(11)
              .fontColor(this.itemKind === index ? '#FFFFFF' : '#4E342E')
              .padding({ left: 10, right: 10, top: 5, bottom: 5 })
              .backgroundColor(this.itemKind === index ? '#4E342E' : '#EFE7D8')
              .borderRadius(13)
              .margin({ right: 8 })
              .onClick(() => {
                this.itemKind = index
              })
          }, (k: string) => k)
        }
        .margin({ top: 8 })

        Text('期望售价(¥)')
          .fontSize(12)
          .fontColor('#9A8C7A')
          .margin({ top: 14 })
        Text(this.priceField === '' ? '输入价格,如 880' : '¥ ' + this.priceField)
          .fontSize(14)
          .fontColor(this.priceField === '' ? '#C4B49A' : '#3E2723')
          .padding(12)
          .backgroundColor('#EFE7D8')
          .borderRadius(10)
          .width('100%')
          .margin({ top: 8 })
          .onClick(() => {
            this.priceField = '880'
          })

        Text('名匠紫砂支持鉴定复核 · 茶饼附仓储记录')
          .fontSize(10)
          .fontColor('#9A8C7A')
          .margin({ top: 10 })

        Text('立即发布')
          .fontSize(15)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
          .width('100%')
          .textAlign(TextAlign.Center)
          .padding({ top: 12, bottom: 12 })
          .backgroundColor('#558B2F')
          .borderRadius(24)
          .margin({ top: 16 })
          .onClick(() => {
            this.showPublish = false
          })
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#FFFFFF')
      .borderRadius({ topLeft: 20, topRight: 20 })
      .constraintSize({ maxHeight: '80%' })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.End)
    .backgroundColor('#66000000')
    .onClick(() => {
      this.showPublish = false
    })
  }

  // 弹框2:茶器养护(类型 + 频次)
  @Builder
  CareDialog() {
    Column() {
      Column() {
        Text('🫖 茶器养护指导')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')

        Text('选择器物类型')
          .fontSize(12)
          .fontColor('#9A8C7A')
          .margin({ top: 14 })
          .width('100%')

        Row() {
          ForEach(this.careKinds, (k: string, index: number) => {
            Text(k)
              .fontSize(10)
              .fontColor(this.careKind === index ? '#FFFFFF' : '#4E342E')
              .padding({ left: 8, right: 8, top: 5, bottom: 5 })
              .backgroundColor(this.careKind === index ? '#4E342E' : '#EFE7D8')
              .borderRadius(12)
              .margin({ right: 6, top: 6 })
              .onClick(() => {
                this.careKind = index
              })
          }, (k: string) => k)
        }
        .width('100%')

        Text('养护频次')
          .fontSize(12)
          .fontColor('#9A8C7A')
          .margin({ top: 14 })
          .width('100%')

        Row() {
          ForEach(this.careFreqs, (f: string, index: number) => {
            Text(f)
              .fontSize(10)
              .fontColor(this.careFreq === index ? '#FFFFFF' : '#558B2F')
              .padding({ left: 8, right: 8, top: 5, bottom: 5 })
              .backgroundColor(this.careFreq === index ? '#558B2F' : '#E7E9D8')
              .borderRadius(12)
              .margin({ right: 6, top: 6 })
              .onClick(() => {
                this.careFreq = index
              })
          }, (f: string) => f)
        }
        .width('100%')

        Row() {
          Column() {
            Row() {
              Text('方案:')
                .fontSize(12)
                .fontColor('#8D6E63')
              Text(this.careKinds[this.careKind] + ' · ' + this.careFreqs[this.careFreq])
                .fontSize(13)
                .fontWeight(FontWeight.Bold)
                .fontColor('#B07D2B')
                .scale({ x: 1.06, y: 1.06 })
                .animation({ duration: 800, iterations: -1, curve: Curve.EaseInOut })
            }
          }
          .alignItems(HorizontalAlign.Start)
        }
        .margin({ top: 12 })

        Row() {
          Text('获取方案')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFFFFF')
            .padding({ left: 22, right: 22, top: 10, bottom: 10 })
            .backgroundColor('#4E342E')
            .borderRadius(20)
            .onClick(() => {
              this.showCare = false
            })
          Text('取消')
            .fontSize(14)
            .fontColor('#9A8C7A')
            .padding({ left: 22, right: 22, top: 10, bottom: 10 })
            .backgroundColor('#EFE7D8')
            .borderRadius(20)
            .margin({ left: 12 })
            .onClick(() => {
              this.showCare = false
            })
        }
        .margin({ top: 18 })
      }
      .width('86%')
      .padding(20)
      .backgroundColor('#FFFFFF')
      .borderRadius(16)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#66000000')
    .onClick(() => {
      this.showCare = false
    })
  }

  // 弹框3:老茶鉴定(年份 + 茶种)
  @Builder
  VerifyDialog() {
    Column() {
      Column() {
        Text('🏺 老茶鉴定')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')

        Text('生产年份')
          .fontSize(12)
          .fontColor('#9A8C7A')
          .margin({ top: 14 })
          .width('100%')

        Row() {
          ForEach([1998, 2005, 2012, 2015, 2018, 2020], (y: number) => {
            Text(y + ' 年')
              .fontSize(10)
              .fontColor(this.verifyYear === y ? '#FFFFFF' : '#B07D2B')
              .padding({ left: 8, right: 8, top: 5, bottom: 5 })
              .backgroundColor(this.verifyYear === y ? '#B08D57' : '#EFE7D8')
              .borderRadius(12)
              .margin({ right: 6, top: 6 })
              .onClick(() => {
                this.verifyYear = y
              })
          }, (y: number) => y + '')
        }
        .width('100%')

        Text('茶类')
          .fontSize(12)
          .fontColor('#9A8C7A')
          .margin({ top: 14 })
          .width('100%')

        Row() {
          ForEach(this.verifyKinds, (k: string, index: number) => {
            Text(k)
              .fontSize(10)
              .fontColor(this.verifyKind === index ? '#FFFFFF' : '#558B2F')
              .padding({ left: 8, right: 8, top: 5, bottom: 5 })
              .backgroundColor(this.verifyKind === index ? '#558B2F' : '#E7E9D8')
              .borderRadius(12)
              .margin({ right: 6, top: 6 })
              .onClick(() => {
                this.verifyKind = index
              })
          }, (k: string) => k)
        }
        .width('100%')

        Column() {
          Row() {
            Text('鉴定结果:')
              .fontSize(12)
              .fontColor('#8D6E63')
            Text('仓储干净 ✓ 年份相符')
              .fontSize(12)
              .fontWeight(FontWeight.Bold)
              .fontColor('#2E7D32')
              .scale({ x: 1.06, y: 1.06 })
              .animation({ duration: 800, iterations: -1, curve: Curve.EaseInOut })
          }
          .width('100%')
          Text('棉纸包装 · 内飞齐全 · 干仓无异味')
            .fontSize(10)
            .fontColor('#9A8C7A')
            .margin({ top: 6 })
        }
        .width('100%')
        .padding(12)
        .backgroundColor('#F5F1E8')
        .borderRadius(10)
        .margin({ top: 14 })

        Row() {
          Text('提交鉴定')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFFFFF')
            .padding({ left: 22, right: 22, top: 10, bottom: 10 })
            .backgroundColor('#558B2F')
            .borderRadius(20)
            .onClick(() => {
              this.showVerify = false
            })
          Text('取消')
            .fontSize(14)
            .fontColor('#9A8C7A')
            .padding({ left: 22, right: 22, top: 10, bottom: 10 })
            .backgroundColor('#EFE7D8')
            .borderRadius(20)
            .margin({ left: 12 })
            .onClick(() => {
              this.showVerify = false
            })
        }
        .margin({ top: 18 })
      }
      .width('86%')
      .padding(20)
      .backgroundColor('#FFFFFF')
      .borderRadius(16)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#66000000')
    .onClick(() => {
      this.showVerify = false
    })
  }

  // 弹框4:删除确认(警示小卡)
  @Builder
  DeleteDialog() {
    Column() {
      Column() {
        Text('🍵')
          .fontSize(36)
          .margin({ top: 8 })
        Text('删除该会话?')
          .fontSize(15)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
          .margin({ top: 10 })
        Text('删除后聊天记录不可恢复哦')
          .fontSize(12)
          .fontColor('#9A8C7A')
          .margin({ top: 6 })

        Row() {
          Text('取消')
            .fontSize(14)
            .fontColor('#9A8C7A')
            .padding({ left: 24, right: 24, top: 9, bottom: 9 })
            .backgroundColor('#EFE7D8')
            .borderRadius(18)
            .onClick(() => {
              this.showDelete = false
            })
          Text('删除')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFFFFF')
            .padding({ left: 24, right: 24, top: 9, bottom: 9 })
            .backgroundColor('#4E342E')
            .borderRadius(18)
            .margin({ left: 12 })
            .onClick(() => {
              this.showDelete = false
            })
        }
        .margin({ top: 16 })
      }
      .width('70%')
      .padding(20)
      .backgroundColor('#FFFFFF')
      .borderRadius(16)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#66000000')
    .onClick(() => {
      this.showDelete = false
    })
  }

  // 弹框5:茶器详情(右侧滑出)
  @Builder
  DetailDialog() {
    Column() {
      Column() {
        Row() {
          Text('茶器详情')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
          Column()
            .layoutWeight(1)
          Text('✕')
            .fontSize(16)
            .fontColor('#9E9E9E')
            .onClick(() => {
              this.showDetail = false
            })
        }
        .width('100%')

        Column() {
          Text('🫖')
            .fontSize(54)
        }
        .width('100%')
        .height(120)
        .justifyContent(FlexAlign.Center)
        .backgroundColor('#EFE7D8')
        .borderRadius(12)
        .margin({ top: 14 })

        Text(this.selName())
          .fontSize(17)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
          .margin({ top: 12 })
          .width('100%')

        Row() {
          Text(this.selBrand())
            .fontSize(11)
            .fontColor('#4E342E')
            .padding({ left: 8, right: 8, top: 3, bottom: 3 })
            .backgroundColor('#E3D5BC')
            .borderRadius(4)
          Text('· ' + this.selCity())
            .fontSize(11)
            .fontColor('#9A8C7A')
        }
        .margin({ top: 8 })

        Row() {
          Text('¥' + this.selPrice())
            .fontSize(26)
            .fontWeight(FontWeight.Bold)
            .fontColor('#B07D2B')
          Text('原价 ¥' + this.selOrigin())
            .fontSize(12)
            .fontColor('#C4B49A')
            .decoration({ type: TextDecorationType.LineThrough })
            .margin({ left: 10 })
        }
        .margin({ top: 12 })

        Column() {
          Row() {
            Text('品相完好度')
              .fontSize(12)
              .fontColor('#9A8C7A')
            Column()
              .layoutWeight(1)
            Text(this.selCraft() + '%')
              .fontSize(12)
              .fontWeight(FontWeight.Bold)
              .fontColor(this.selCraft() > 90 ? '#2E7D32' : '#EF6C00')
          }
          .width('100%')
          Stack({ alignContent: Alignment.Start }) {
            Column()
              .width('100%')
              .height(6)
              .backgroundColor('#EFE7D8')
              .borderRadius(3)
            Column()
              .width(this.selCraft() + '%')
              .height(6)
              .backgroundColor(this.selCraft() > 90 ? '#66BB6A' : '#B08D57')
              .borderRadius(3)
          }
          .width('100%')
          .height(6)
          .margin({ top: 6 })
          Text('平台实物拍摄 · 支持七天无理由退换')
            .fontSize(9)
            .fontColor('#9A8C7A')
            .margin({ top: 4 })
        }
        .width('100%')
        .padding(12)
        .backgroundColor('#F5F1E8')
        .borderRadius(10)
        .margin({ top: 14 })

        Row() {
          Text('求鉴定')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#558B2F')
            .padding({ left: 24, right: 24, top: 10, bottom: 10 })
            .backgroundColor('#FFFFFF')
            .border({ width: 1, color: '#558B2F', radius: 20 })
            .onClick(() => {
              this.showDetail = false
              this.showVerify = true
            })
          Text('聊卖家')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFFFFF')
            .padding({ left: 24, right: 24, top: 10, bottom: 10 })
            .backgroundColor('#4E342E')
            .borderRadius(20)
            .margin({ left: 12 })
            .onClick(() => {
              this.showDetail = false
            })
        }
        .margin({ top: 16 })
      }
      .width('88%')
      .height('100%')
      .padding(16)
      .backgroundColor('#FFFFFF')
      .borderRadius({ topLeft: 20, bottomLeft: 20 })
      .constraintSize({ maxHeight: '80%' })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.End)
    .backgroundColor('#66000000')
    .onClick(() => {
      this.showDetail = false
    })
  }
}


总结

在这里插入图片描述

本文深入解析了一个基于HarmonyOS ArkTS声明式UI范式开发的二手茶器与老茶交易平台"茶器流转阁"。该应用以单一@Entry @Component组件为核心,通过十一个@State状态变量驱动全部交互逻辑,七个@Builder方法构建七个功能Tab页面,五个@Builder方法实现五种弹窗交互。整体架构遵循"数据模型-视图渲染-逻辑计算"三层分离设计原则。六个接口类型完整覆盖了紫砂壶、盖碗茶杯、茶饼老茶、社区帖子等茶器交易全品类数据需求。茶褐竹青配色方案贯穿于头部、Tab导航、列表卡片和弹窗的每一个细节,营造出与茶器文化高度契合的古典雅致氛围。

在数据可视化方面,本应用展现了ArkTS原生组件的多样化布局能力。通过Column高度按比例计算实现了容量分布和年份分布两种柱状图——其中年份柱状图采用(item.year - 1996) / 28 * 115的独特时间映射公式,将年份跨度转化为空间高度,让老茶陈化程度一目了然。通过Stack叠放实现了品相完好度和养成年限两种进度条。盖碗茶杯Tab采用双列网格布局(flexWrap(FlexWrap.Wrap)+47%宽度),配合if (item.flawless)布尔条件渲染展示"全品无瑕疵"或"微磕已修复"状态,这些设计都是针对茶器交易特点的定制化可视化方案。茶友会Tab引入了社区帖子列表的概念,包含发帖人、标题、正文、回复数和点赞数,使应用从单纯交易平台升级为"交易+社交"复合型平台。

在弹窗交互设计方面,五个弹窗各具特色且专业性突出。发布弹窗底部滑出提供器物类别选择,养护弹窗采用"类型+频次"双维度组合选择模式并动态输出养护方案,老茶鉴定弹窗采用"年份+茶类"双维度组合选择模式并输出鉴定结果,详情弹窗右侧滑出展示品相进度条并提供"求鉴定"入口跳转到鉴定弹窗。这种多维度组合式弹窗设计是本应用的核心创新点——它将茶器领域的专业参数(器物类型、养护频次、生产年份、茶类)转化为用户可操作的标签选择,降低了专业鉴定的门槛。弹窗间通过状态变量联动实现跳转(如详情弹窗"求鉴定"跳转到鉴定弹窗),通过stopPropagation事件控制实现标准弹窗交互模式。这些设计模式为HarmonyOS应用开发中复杂垂直领域交互提供了完整的参考范式。

Logo

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

更多推荐