HarmonyOS 6.1.1云鉴宝直播应用代码拆解——主播带你从青铜棕与鎏金的配色搭配聊到Grid四宫格连麦窗口,深入ArkTS声明式布局如何用bindSheet抽屉管理藏品鉴定全流程

欢迎收听本期技术播客,今天主播要带大家拆解一个非常有意思的 HarmonyOS 应用——一个仿照腾讯会议式直播连麦架构的线上鉴宝平台。配色用的是青铜棕 #5D4037 搭配鎏金 #D4AF37,整个界面有种故宫文物展厅的质感,特别带感。

这个应用最让我兴奋的地方是它的底部导航——每个 Tab 格子像博物馆的展柜一样,选中时暖光打底加顶部金线,图标圆底如展品。而且弹层交互分了抽屉和全屏遮罩两种,日常表单走抽屉,关键确认走弹窗,这个交互层级设计得特别讲究。

如果你之前做过直播类应用,你一定会对"连麦"这个交互场景很熟悉。今天我们就来聊聊,ArkTS 的声明式 UI 怎么把鉴定、弹幕、档期预约这些复杂的直播业务流程,用优雅的代码结构组织起来。


引言:今天聊什么

在这里插入图片描述

哈喽大家好,欢迎回来。今天主播想跟大家聊一个我最近看到特别有意思的 HarmonyOS 应用源码——它叫"鎏光·云鉴宝",简单来说就是一个线上鉴宝直播平台。场景是这样的:藏友通过视频会议室连麦专家,专家在直播间里直播鉴定瓷器、玉器、书画这些藏品。你可以理解成"直播版的鉴宝节目",藏友不用扛着花瓶去电视台排队了,直接线上连麦就行。

从技术角度来说,这个应用基于 HarmonyOS ArkTS 声明式 UI 范式构建。整体架构是一个 @Entry 主入口组件 Index192,下面挂了六个子 Tab 组件——鉴宝台、藏品阁、送鉴单、行家团、档期、我的。主组件管所有可变状态,子组件通过构造参数和回调函数跟主组件通信。配色方案很讲究,青铜棕做主色调,鎏金做高亮色,米宣白 #F7F3EA 做背景,整个视觉语言就是博物馆的展柜美学。

从业务设计来说呢,这个应用把鉴宝直播的全流程拆得特别细。直播台有四宫格连麦窗口——主鉴专家视角、藏宝人视角、微距镜头、款识特写,四个画面同屏。藏品阁可以管理自己的藏品档案,送鉴单可以提交新的鉴定申请,行家团展示各位专家的信息和口碑评分,档期页可以预约未来的直播专场。每个模块都有独立的弹层表单——申请连麦、新增送鉴、编辑藏品、撤销送鉴、藏品详情,五个弹层各有分工。来,我们一段段拆开看。


第一段:数据模型——鉴宝的"藏品档案柜"

在这里插入图片描述

1.1 接口定义

好,咱们先看数据结构。做任何一个应用,数据模型是地基,先把地基打好才能往上盖楼。这个应用定义了一组接口来描述鉴宝业务的所有实体类型。

interface TabItem192 {
  name: string;
  icon: string;
  badge: number;
}

interface Lot192 {
  id: string;
  name: string;
  cate: string;
  era: string;
  grade: string;
  value: number;
  owner: string;
  verdict: string;
  color: string;
  hot: number;
  state: string
}

interface VerdictRow192 {
  id: string;
  lot: string;
  expert: string;
  result: string;
  date: string;
  conf: number;
  color: string;
}

interface Expert192 {
  id: string;
  name: string;
  field: string;
  title: string;
  rating: number;
  sessions: number;
  emoji: string;
  online: boolean;
}

interface Session192 {
  id: string;
  date: string;
  week: string;
  time: string;
  theme: string;
  expert: string;
  joined: number;
  seats: number;
  state: string;
}

interface Danmaku192 {
  id: string;
  user: string;
  text: string;
  color: string;
}

interface Attend192 {
  day: string;
  cnt: number;
}

interface Share192 {
  label: string;
  w: number;
  c: string;
}

interface HotCate192 {
  name: string;
  pct: number;
  color: string;
}

interface Record192 {
  id: string;
  date: string;
  lot: string;
  result: string;
  color: string;
}

大家注意看 TabItem192 这个接口,它比一般 Tab 多了一个 badge 字段——这是未读消息的角标数字。这个设计说明底部导航不是单纯的路由切换,它还承担了消息通知的功能。再看 Lot192,字段特别丰富:cate 是品类(瓷器、玉器、书画等),era 是年代,grade 是品级,verdict 是鉴定结论,hot 是热度值,state 是鉴定状态。一个藏品有这么多维度,说明这个应用的业务复杂度不低。

Expert192 里有 rating(评分)和 sessions(主持场次)两个字段,这两个数据在后面的行家团 Tab 里会被用来做口碑可视化——评分高的行家,进度条就长。Session192joinedseats 两个数值,它们的比值就是上座率,在上座率超过阈值时,预约按钮会变成"已满"灰色态。

主播小贴士:在 ArkTS 里,接口定义纯粹是类型约束,不占运行时内存。但好的接口设计能让你的数据流更清晰——每个字段的类型都在告诉读代码的人"这个数据是干嘛的"。比如 conf: number 一看就知道是置信度,pct: number 就是百分比。

1.2 辅助函数和静态数据

在这里插入图片描述

接下来看几组辅助函数和静态数据,这些是整个应用的"素材库"。

const CATES192: string[] = ['瓷器', '玉器', '书画', '铜器', '印石', '文房'];
const URGENTS192: string[] = ['普通排期', '加急48h', '特急24h'];
const PRIVACY192: string[] = ['公开展示', '仅自己可见'];

function gradeColor192(g: string): string {
  if (g === '珍品') {
    return '#D4AF37';
  }
  if (g === '上品') {
    return '#5D4037';
  }
  return '#9E9E9E';
}

function stateColor192(s: string): string {
  if (s === '可预约') {
    return '#00695C';
  }
  if (s === '即将满员') {
    return '#EF6C00';
  }
  return '#9E9E9E';
}

function verdictColor192(v: string): string {
  if (v === '真品') {
    return '#00695C';
  }
  if (v === '后仿') {
    return '#C62828';
  }
  return '#EF6C00';
}

这三个颜色函数特别有意思,它们把业务语义翻译成了视觉语言。gradeColor192 里"珍品"返回鎏金色 #D4AF37——因为珍品就是镇馆之宝,必须用最贵的颜色。"上品"用青铜棕,普通品用灰色。stateColor192 里"可预约"是绿色、"即将满员"是橙色、"已满员"是灰色。verdictColor192 里"真品"绿色、"后仿"红色、"待考"橙色。你看,这三个函数覆盖了鉴宝的三个核心判断维度:品级、档期状态、鉴定结论。每个维度都有自己的颜色映射逻辑。

主播吐槽一下:很多新手开发者喜欢把颜色直接写死在 UI 代码里,比如 fontColor('#D4AF37')。但你品品,如果将来要改"珍品"的颜色,你得全局搜索替换。用函数封装后,只需要改一个地方。这就是"DRY 原则"——Don’t Repeat Yourself。

1.3 藏品和专家数据

在这里插入图片描述

const LOTS192: Lot192[] = [
  { id: 'l1', name: '青花缠枝纹梅瓶', cate: '瓷器', era: '明永乐', grade: '珍品', value: 128, owner: '金陵藏家·陆先生', verdict: '真品', color: '#5D4037', hot: 97, state: '待定' },
  { id: 'l2', name: '和田白玉把件', cate: '玉器', era: '清中期', grade: '上品', value: 66, owner: '姑苏·沈女士', verdict: '真品', color: '#00695C', hot: 89, state: '待定' },
  { id: 'l3', name: '田黄石瑞兽钮章', cate: '印石', era: '民国', grade: '上品', value: 92, owner: '榕城·林先生', verdict: '真品', color: '#AD1457', hot: 93, state: '待定' },
  { id: 'l4', name: '仿哥窑贯耳瓶', cate: '瓷器', era: '清晚期', grade: '普品', value: 8, owner: '蜀中·赵女士', verdict: '后仿', color: '#757575', hot: 64, state: '待定' },
  { id: 'l5', name: '吴门山水扇面', cate: '书画', era: '明', grade: '珍品', value: 210, owner: '沪上·程先生', verdict: '待考', color: '#283593', hot: 99, state: '待定' },
  { id: 'l6', name: '鎏金铜坐佛', cate: '铜器', era: '明早期', grade: '上品', value: 155, owner: '长安·马先生', verdict: '真品', color: '#EF6C00', hot: 91, state: '待定' },
  { id: 'l7', name: '端砚云月池', cate: '文房', era: '清', grade: '上品', value: 48, owner: '肇庆·梁先生', verdict: '真品', color: '#00838F', hot: 72, state: '待定' },
  { id: 'l8', name: '粉彩百蝶瓶', cate: '瓷器', era: '清光绪', grade: '珍品', value: 178, owner: '津门·周女士', verdict: '真品', color: '#827717', hot: 95, state: '待定' }
];

const EXPERTS192: Expert192[] = [
  { id: 'e1', name: '程汝言', field: '瓷器', title: '副研究馆员', rating: 4.9, sessions: 486, emoji: '🏺', online: true },
  { id: 'e2', name: '白玛', field: '玉器', title: '高级鉴定师', rating: 4.8, sessions: 352, emoji: '🪨', online: true },
  { id: 'e3', name: '林半尺', field: '印石', title: '民间藏家代表', rating: 4.7, sessions: 298, emoji: '🖊️', online: false },
  { id: 'e4', name: '顾墨白', field: '书画', title: '美术学院特聘', rating: 5.0, sessions: 275, emoji: '🖼️', online: true },
  { id: 'e5', name: '马远声', field: '铜器', title: '考古队顾问', rating: 4.8, sessions: 231, emoji: '🔔', online: false },
  { id: 'e6', name: '梁砚秋', field: '文房', title: '端砚研究会理事', rating: 4.6, sessions: 189, emoji: '📖', online: true }
];

八件藏品覆盖了瓷器、玉器、印石、书画、铜器、文房六大品类。注意那个"仿哥窑贯耳瓶"——它的 verdict 是"后仿",品级是"普品",估值只有 8 万,跟旁边的 128 万梅瓶形成了鲜明对比。这种"真品和后仿混在一起"的数据设计,让鉴定结论的颜色差异在 UI 上有直观的视觉冲击——绿色"真品"和红色"后仿"并列时,用户一眼就能区分。六位专家各有专攻,rating 从 4.6 到 5.0,顾墨白老师 5.0 满分,书画专场估计很难约。


第二段:主入口组件——直播间的"总控台"

在这里插入图片描述

2.1 状态声明

好,接下来看主入口组件。这是整个应用的总控台,所有状态都在这里统一管理。

@Entry
@Component
struct Index192 {
  @State curTab: number = 0;
  @State showLianmaiSheet: boolean = false;
  @State showSendSheet: boolean = false;
  @State showEditSheet: boolean = false;
  @State showDelDialog: boolean = false;
  @State showDetailDialog: boolean = false;
  @State editIdx: number = 0;
  @State editName: string = '';
  @State delIdx: number = 0;
  @State detailIdx: number = 0;
  @State cateIdx: number = 0;
  @State urgentIdx: number = 0;
  @State privacyIdx: number = 0;
  @State hopeExpert: number = 0;
  @State estValue: number = 10;
  @State insureOn: boolean = true;
  @State pickupOn: boolean = false;
  @State keepRecordOn: boolean = true;
  @State myLots: Lot192[] = [
    { id: 'm1', name: '青花缠枝纹梅瓶', cate: '瓷器', era: '明永乐', grade: '珍品', value: 128, owner: '我', verdict: '真品', color: '#5D4037', hot: 97, state: '待定' },
    { id: 'm2', name: '端砚云月池', cate: '文房', era: '清', grade: '上品', value: 48, owner: '我', verdict: '真品', color: '#00838F', hot: 72, state: '待定' },
    { id: 'm3', name: '铜鎏金小坐像', cate: '铜器', era: '明早期', grade: '上品', value: 155, owner: '我', verdict: '真品', color: '#EF6C00', hot: 90, state: '待定' }
  ];
  @State myOrders: Session192[] = [
    { id: 'o1', date: '08-26', week: '周三', time: '19:30', theme: '瓷器专场·永宣青花', expert: '程汝言', joined: 412, seats: 500, state: '已预约' },
    { id: 'o2', date: '08-28', week: '周五', time: '19:00', theme: '书画专场·吴门画派', expert: '顾墨白', joined: 488, seats: 500, state: '已预约' }
  ];

大家看,这里的状态分为几组:curTab 管当前 Tab;五个 show* 布尔管弹层显隐;editIdxdelIdxdetailIdx 是被操作的藏品或订单的索引;cateIdxurgentIdxprivacyIdxhopeExpertestValue 是连麦申请表单的选择状态;insureOnpickupOnkeepRecordOn 是三个 Toggle 开关。最关键的是 myLotsmyOrders 两个数组状态——它们是用户的个人藏品和预约订单,是整个应用中真正"可变"的数据。其他静态数据如 LOTS192EXPERTS192 都是全局只读常量。

主播知识点:在 ArkTS 里,@State 标记的变量如果赋了新值,UI 会自动刷新。但如果 myLots 是数组,你必须给它赋一个全新的数组(比如用 mapfilter 生成新数组),直接 pushsplice 修改原数组是触发不了响应式更新的。这就是为什么后面的删除和编辑操作都用 filtermap

2.2 顶部直播状态栏

在这里插入图片描述

  build() {
    Column() {
      Column() {
        Text('鎏光·云鉴宝')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
        Row() {
          Row() {
            Text('●')
              .fontSize(9)
              .fontColor('#FFD54F')
            Text('直播中 · 第128场')
              .fontSize(11)
              .fontColor('#FFD54F')
              .margin({ left: 4 })
          }
          .padding({ left: 10, right: 10, top: 3, bottom: 3 })
          .borderRadius(10)
          .backgroundColor('#4D000000')
          Text('')
            .layoutWeight(1)
          Text('6.8万人看过')
            .fontSize(11)
            .fontColor('#E8DCC8')
        }
        .width('100%')
        .margin({ top: 10 })

        Row() {
          ForEach(LOTS192, (l: Lot192, idx: number) => {
            Column() {
              Text(l.era)
                .fontSize(9)
                .fontColor(idx === 0 ? '#5D4037' : '#E8DCC8')
              Text(idx === 0 ? '正在鉴定' : l.verdict)
                .fontSize(10)
                .fontWeight(idx === 0 ? FontWeight.Bold : FontWeight.Normal)
                .fontColor(idx === 0 ? '#FFD54F' : '#E8DCC8')
            }
            .padding({ left: 8, right: 8, top: 4, bottom: 4 })
            .borderRadius(8)
            .backgroundColor(idx === 0 ? '#D4AF37' : '#33000000')
          }, (l: Lot192) => l.id)
        }
        .width('100%')
        .margin({ top: 10 })
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 14, bottom: 14 })
      .linearGradient({
        angle: 160,
        colors: [['#4E342E', 0], ['#5D4037', 0.5], ['#3E2723', 1]]
      })

这个顶部栏特别有直播质感。首先"直播中"用了一个闪烁感的鎏金小圆点 #FFD54F 文字,放在半透明黑底 #4D000000(约 30% 不透明度)的胶囊里——这就是直播 APP 标配的"直播指示器"。下面那排藏品列表用 ForEach 遍历所有 LOTS192,第一个藏品用鎏金底色标记"正在鉴定",其余用半透明黑底显示鉴定结论。整个顶部栏用 160 度三段渐变 #4E342E → #5D4037 → #3E2723,从深棕到标准棕再到更深棕,制造出一种青铜器表面的光泽质感。

主播点评:这个三段渐变比两段渐变更有"材质感"。两段渐变像塑料,三段渐变像金属。做古风应用的同学可以学一下这个套路。

2.3 内容路由和底部导航

在这里插入图片描述

      Scroll() {
        Column() {
          if (this.curTab === 0) {
            LiveTab192({
              onLianmai: () => {
                this.showLianmaiSheet = true;
              }
            })
          }
          if (this.curTab === 1) {
            CollectionTab192({
              lots: this.myLots,
              onEdit: (idx: number) => {
                this.editIdx = idx;
                this.editName = this.myLots[idx].name;
                this.showEditSheet = true;
              },
              onDetail: (idx: number) => {
                this.detailIdx = idx;
                this.showDetailDialog = true;
              }
            })
          }
          if (this.curTab === 2) {
            OrderTab192({
              orders: this.myOrders,
              onSend: () => {
                this.showSendSheet = true;
              },
              onDel: (idx: number) => {
                this.delIdx = idx;
                this.showDelDialog = true;
              }
            })
          }
          if (this.curTab === 3) {
            ExpertTab192()
          }
          if (this.curTab === 4) {
            ScheduleTab192({
              onLianmai: () => {
                this.showLianmaiSheet = true;
              }
            })
          }
          if (this.curTab === 5) {
            MineTab192({
              records: RECORDS192,
              onDetail: () => {
                this.detailIdx = 0;
                this.showDetailDialog = true;
              }
            })
          }
        }
        .width('100%')
      }
      .layoutWeight(1)
      .scrollBar(BarState.Off)
      .backgroundColor('#F7F3EA')

内容路由用了一串 if 而不是 if-else if——注意这个区别。在 ArkTS 中,连续 if 意味着每次只渲染匹配的那一个,但如果条件同时满足理论上可以渲染多个。不过这里因为 curTab 只有一个值,实际效果跟 if-else if 一样。子组件的传参模式值得注意:CollectionTab192 接收 lots: this.myLots 和两个回调 onEditonDetailOrderTab192 接收 orders: this.myOrdersonSendonDel。回调用箭头函数传递,在回调内部修改主组件的状态变量——这是 ArkTS 中子组件向父组件"上报事件"的标准模式。

      Row() {
        ForEach(TABS192, (t: TabItem192, idx: number) => {
          Column() {
            Text('')
              .width(idx === this.curTab ? 26 : 0)
              .height(2)
              .backgroundColor('#D4AF37')
              .borderRadius(1)
              .margin({ top: 6 })
            Row() {
              Text(t.icon)
                .fontSize(18)
                .width(38)
                .height(38)
                .textAlign(TextAlign.Center)
                .borderRadius(19)
                .backgroundColor(idx === this.curTab ? '#FFFFFF' : '#F1E8D8')
            }
            Column() {
              Text(t.name)
                .fontSize(10)
                .fontWeight(idx === this.curTab ? FontWeight.Bold : FontWeight.Normal)
                .fontColor(idx === this.curTab ? '#5D4037' : '#8D7B6E')
              if (t.badge > 0) {
                Text(t.badge + '')
                  .fontSize(8)
                  .fontColor('#FFFFFF')
                  .padding({ left: 5, right: 5, top: 1, bottom: 1 })
                  .borderRadius(7)
                  .backgroundColor('#C62828')
                  .margin({ top: 2 })
              }
            }
            .margin({ left: 6 })
          }
          .padding({ left: 10, right: 10, top: 6, bottom: 6 })
          .borderRadius(14)
          .backgroundColor(idx === this.curTab ? '#F9EED2' : '#F7F3EA')
          .layoutWeight(1)
          .alignItems(HorizontalAlign.Center)
          .onClick(() => {
            this.curTab = idx;
          })
        }, (t: TabItem192, idx: number) => t.name + idx)
      }
      .width('100%')
      .padding({ left: 6, right: 6, bottom: 8 })
      .backgroundColor('#FFFFFF')

底部导航是"展柜格"风格。选中时顶部有一根 26 像素宽的金线 #D4AF37,背景变为暖光底 #F9EED2,图标圆底变白。未选中时金线宽度为 0(不可见),背景与页面底色一致。badge > 0 时显示红色角标——比如"送鉴单"有 3 条未读。这个底部导航的设计语言是整个应用最统一的地方:暖色调、圆角、展柜感,跟青铜棕+鎏金的主题完全一致。

主播闲聊:你们有没有发现,这个底部导航跟前面 190 的均衡器条、193 的气泡底都不一样?每个应用都在底部导航上做"主题化"——190 是音乐所以用均衡器条,193 是脱口秀所以用气泡,这里是鉴宝所以用展柜金线。这就是 UI 设计跟业务场景的深度绑定。


第三段:连麦申请抽屉——鉴宝的"挂号窗口"

3.1 连麦申请表单

接下来看一个很有代表性的弹层——连麦鉴宝申请抽屉。这个表单模拟的是"藏友向直播间申请连麦"的流程。

  @Builder
  lianmaiSheet192() {
    Column() {
      Row() {
        Text('')
          .width(36)
          .height(4)
          .borderRadius(2)
          .backgroundColor('#D9D2C5')
      }
      .width('100%')
      .justifyContent(FlexAlign.Center)
      .margin({ top: 10 })

      Text('申请连麦鉴宝')
        .fontSize(17)
        .fontWeight(FontWeight.Bold)
        .fontColor('#3E2723')
        .margin({ top: 12 })

      Column() {
        Text('藏品名称')
          .fontSize(12)
          .fontColor('#8D7B6E')
        Row() {
          Text('请输入藏品全名(选填)')
            .fontSize(13)
            .fontColor('#B8AC9C')
          Text('')
            .layoutWeight(1)
        }
        .width('100%')
        .padding(12)
        .borderRadius(10)
        .backgroundColor('#F7F3EA')
        .margin({ top: 8 })
      }
      .width('100%')
      .margin({ top: 16 })
      .alignItems(HorizontalAlign.Start)

      Column() {
        Text('藏品品类')
          .fontSize(12)
          .fontColor('#8D7B6E')
        Row() {
          ForEach(CATES192, (c: string, i: number) => {
            Text(c)
              .fontSize(12)
              .fontColor(i === this.cateIdx ? '#FFFFFF' : '#5D4037')
              .padding({ left: 14, right: 14, top: 7, bottom: 7 })
              .borderRadius(16)
              .backgroundColor(i === this.cateIdx ? '#5D4037' : '#F1E8D8')
              .margin({ right: 8 })
              .onClick(() => {
                this.cateIdx = i;
              })
          }, (c: string) => c)
        }
        .margin({ top: 8 })
      }
      .width('100%')
      .margin({ top: 16 })
      .alignItems(HorizontalAlign.Start)

这个抽屉的拖拽条用了 #D9D2C5——一个比页面底色 #F7F3EA 更深的暖灰色,低调但能看见。品类选择器用 ForEach 渲染六个胶囊按钮,选中态是青铜棕底白字,未选中是浅米色底棕字。注意这里所有表单标签的颜色都是 #8D7B6E——一种去饱和的棕色,比正文颜色 #3E2723 更柔和。这种"标签用浅色、内容用深色"的层次设计,让表单不会满屏都是重色字,视觉上更有呼吸感。

      Column() {
        Text('期望行家')
          .fontSize(12)
          .fontColor('#8D7B6E')
        Row() {
          ForEach(EXPERTS192, (e: Expert192, i: number) => {
            Column() {
              Text(e.emoji)
                .fontSize(18)
                .width(42)
                .height(42)
                .textAlign(TextAlign.Center)
                .borderRadius(21)
                .backgroundColor(i === this.hopeExpert ? '#D4AF37' : '#F1E8D8')
              Text(e.name)
                .fontSize(9)
                .fontColor(i === this.hopeExpert ? '#5D4037' : '#8D7B6E')
                .margin({ top: 4 })
            }
            .margin({ right: 12 })
            .onClick(() => {
              this.hopeExpert = i;
            })
          }, (e: Expert192) => e.id)
        }
        .margin({ top: 8 })
      }
      .width('100%')
      .margin({ top: 16 })
      .alignItems(HorizontalAlign.Start)

期望行家选择器是整个表单里最有趣的交互。六个专家用 emoji 头像+姓名的方式横排展示,选中时头像底色变为鎏金 #D4AF37,姓名变为青铜棕。这种"头像选择"比纯文字选择更有温度——用户在选的是一个"人",不是一个"选项"。

      Row() {
        Column() {
          Text('加急通道')
            .fontSize(13)
            .fontColor('#3E2723')
          Text('优先排入当前直播队列')
            .fontSize(10)
            .fontColor('#8D7B6E')
            .margin({ top: 2 })
        }
        .alignItems(HorizontalAlign.Start)
        Text('')
          .layoutWeight(1)
        ForEach(URGENTS192, (u: string, i: number) => {
          Text(u)
            .fontSize(11)
            .fontColor(i === this.urgentIdx ? '#FFFFFF' : '#5D4037')
            .padding({ left: 10, right: 10, top: 6, bottom: 6 })
            .borderRadius(12)
            .backgroundColor(i === this.urgentIdx ? '#C62828' : '#F1E8D8')
            .margin({ left: 6 })
            .onClick(() => {
              this.urgentIdx = i;
            })
        }, (u: string) => u)
      }
      .width('100%')
      .margin({ top: 18 })

      Row() {
        Text('申请连麦')
          .fontSize(15)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
          .textAlign(TextAlign.Center)
          .width('100%')
          .padding({ top: 13, bottom: 13 })
          .borderRadius(22)
          .linearGradient({
            angle: 90,
            colors: [['#5D4037', 0], ['#8D6E63', 1]]
          })
      }
      .width('100%')
      .margin({ top: 20 })
      .onClick(() => {
        this.showLianmaiSheet = false;
      })
    }
    .width('100%')
    .padding({ left: 20, right: 20, bottom: 24 })
    .constraintSize({ maxHeight: '85%' })
  }

加急通道的选择器用红色 #C62828 标记选中态——因为"特急24h"是紧急操作,红色传递紧迫感。提交按钮用青铜棕到浅棕的渐变,constraintSize({ maxHeight: '85%' }) 限制抽屉最大高度不超过屏幕的 85%——防止表单内容过多时撑满全屏。

主播总结:这个连麦申请表单展示了 ArkTS 表单交互的三个经典模式——文本输入、胶囊选择器、头像选择器。每种选择器都用"反转色"做选中反馈:选中时深色底白字,未选中时浅色底深色字。统一的设计语言让用户在不同表单中有一致的交互预期。


第四段:直播台 Tab——四宫格连麦窗口

4.1 LiveTab192 组件

好,接下来看整个应用最核心的组件——直播台。这里是用户进入应用后看到的第一个页面,也是信息密度最高的页面。

@Component
struct LiveTab192 {
  onLianmai: () => void = () => {
  };
  @State liked: boolean[] = [false, true, false, false, true, false, false];

  build() {
    Column() {
      Column() {
        Row() {
          Text('🔴')
            .fontSize(14)
          Text('第 128 场 · 瓷器专场')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
            .margin({ left: 6 })
          Text('')
            .layoutWeight(1)
          Text('在线 412')
            .fontSize(10)
            .fontColor('#8D7B6E')
        }
        .width('100%')

        Grid() {
          GridItem() {
            Column() {
              Text('🧑‍🏫')
                .fontSize(28)
              Text('程汝言 · 主鉴')
                .fontSize(9)
                .fontColor('#FFFFFF')
                .margin({ top: 4 })
            }
            .width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Center)
            .backgroundColor('#5D4037')
          }
          GridItem() {
            Column() {
              Text('🪞')
                .fontSize(28)
              Text('藏宝人视角')
                .fontSize(9)
                .fontColor('#FFFFFF')
                .margin({ top: 4 })
            }
            .width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Center)
            .backgroundColor('#8D6E63')
          }
          GridItem() {
            Column() {
              Text('🔍')
                .fontSize(28)
              Text('微距镜头')
                .fontSize(9)
                .fontColor('#FFFFFF')
                .margin({ top: 4 })
            }
            .width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Center)
            .backgroundColor('#4E342E')
          }
          GridItem() {
            Column() {
              Text('📜')
                .fontSize(28)
              Text('款识特写')
                .fontSize(9)
                .fontColor('#FFFFFF')
                .margin({ top: 4 })
            }
            .width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Center)
            .backgroundColor('#6D4C41')
          }
        }
        .columnsTemplate('1fr 1fr')
        .rowsTemplate('1fr 1fr')
        .width('100%')
        .height(210)
        .borderRadius(12)
        .columnsGap(2)
        .rowsGap(2)
        .margin({ top: 10 })

四宫格连麦窗口是整个应用的视觉焦点。Grid 组件用 columnsTemplate('1fr 1fr')rowsTemplate('1fr 1fr') 定义为 2x2 网格,高度 210 像素,间距 2 像素。四个格子分别是:主鉴专家视角(深棕 #5D4037)、藏宝人视角(中棕 #8D6E63)、微距镜头(更深棕 #4E342E)、款识特写(浅棕 #6D4C41)。四个格子的背景色是同一色系的不同明度,制造出青铜展柜中四盏不同角度射灯的效果。columnsGap(2)rowsGap(2) 让格子之间有 2 像素的缝隙——模拟视频会议中多窗口之间的分隔线。

主播知识点:GridcolumnsTemplaterowsTemplatefr 单位可以做等分网格。1fr 1fr 就是两等分。如果你要 3 列就用 1fr 1fr 1fr。这个比用 Row + layoutWeight 更适合做网格布局,因为 Grid 原生支持行列间距。

4.2 藏品列表和弹幕区

      Column() {
        ForEach(LOTS192, (l: Lot192, i: number) => {
          Row() {
            Column() {
              Text(l.cate.charAt(0))
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor('#FFFFFF')
            }
            .width(44)
            .height(44)
            .justifyContent(FlexAlign.Center)
            .borderRadius(10)
            .backgroundColor(l.color)

            Column() {
              Row() {
                Text(l.name)
                  .fontSize(13)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#3E2723')
                Text(l.grade)
                  .fontSize(8)
                  .fontColor('#FFFFFF')
                  .padding({ left: 6, right: 6, top: 2, bottom: 2 })
                  .borderRadius(8)
                  .backgroundColor(gradeColor192(l.grade))
                  .margin({ left: 6 })
                Text('')
                  .layoutWeight(1)
                Text(l.verdict)
                  .fontSize(10)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(verdictColor192(l.verdict))
              }
              .width('100%')
              Text(l.era + ' · ' + l.owner + ' · 估值 ' + l.value + ' 万')
                .fontSize(10)
                .fontColor('#8D7B6E')
                .margin({ top: 4 })
              Row() {
                Text('')
                  .width('60%')
                  .height(3)
                  .borderRadius(2)
                  .backgroundColor('#F1E8D8')
                Text('')
                  .layoutWeight(1)
              }
              .width('100%')
              .margin({ top: 6 })
              Row() {
                Text('热度')
                  .fontSize(9)
                  .fontColor('#8D7B6E')
                Text('')
                  .width(l.hot * 0.9)
                  .height(4)
                  .borderRadius(2)
                  .backgroundColor(l.color)
                  .margin({ left: 6 })
                Text('')
                  .layoutWeight(1)
              }
              .width('100%')
              .margin({ top: 4 })
            }
            .layoutWeight(1)
            .margin({ left: 10 })
            .alignItems(HorizontalAlign.Start)

            Column() {
              Text(i === 0 ? '鉴定中' : '已完成')
                .fontSize(9)
                .fontColor(i === 0 ? '#C62828' : '#00695C')
              Text(this.liked[i] ? '🔥' : '👍')
                .fontSize(16)
                .margin({ top: 6 })
                .onClick(() => {
                  this.liked[i] = !this.liked[i];
                })
            }
            .margin({ left: 8 })
          }
          .width('100%')
          .padding(12)
          .borderRadius(12)
          .backgroundColor(i === 0 ? '#FBF4E4' : '#FFFFFF')
          .margin({ top: 8 })
        }, (l: Lot192) => l.id)
      }
      .width('100%')
      .padding({ left: 12, right: 12 })
      .margin({ top: 8 })

藏品列表用 l.cate.charAt(0) 取品类首字作为图标——"瓷"字白底棕底、"玉"字绿底、"书"字靛蓝底。这种用品类首字做图标的做法在资源受限时很实用。第一条藏品(正在鉴定的)用 #FBF4E4 暖底色标记,其他白底。热度进度条用 l.hot * 0.9 作为宽度——97 的热度对应 87.3 像素的进度条宽度。点赞按钮用 @State liked: boolean[] 数组管理每条藏品的点赞状态,点击切换 🔥👍

主播发现:这个 liked 数组直接在 onClick 中用 this.liked[i] = !this.liked[i] 修改——这是直接修改数组元素而非生成新数组。在 ArkTS 中,@State 标记的数组如果直接修改元素,在某些版本中可能不触发响应式更新。这是这个代码中一个潜在的风险点。更安全的做法是用 map 生成新数组。

4.3 弹幕和结论占比

      Column() {
        Text('本场鉴定结论构成')
          .fontSize(13)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Row() {
          ForEach(SHARE192, (s: Share192) => {
            Column() {
            }
            .layoutWeight(s.w)
            .height(10)
            .backgroundColor(s.c)
          }, (s: Share192) => s.label)
        }
        .width('100%')
        .clip(true)
        .borderRadius(5)
        .margin({ top: 10 })
        Row() {
          ForEach(SHARE192, (s: Share192) => {
            Row() {
              Text('●')
                .fontSize(8)
                .fontColor(s.c)
              Text(s.label + ' ' + s.w + '%')
                .fontSize(9)
                .fontColor('#8D7B6E')
                .margin({ left: 4 })
            }
            .margin({ right: 10 })
          }, (s: Share192) => s.label)
        }
        .width('100%')
        .margin({ top: 8 })
      }
      .width('100%')
      .padding(14)
      .borderRadius(14)
      .backgroundColor('#FFFFFF')
      .margin({ left: 12, right: 12, top: 16 })

结论构成占比用 layoutWeight(s.w) 实现比例分配——真品 46%、后仿 22%、待考 18%、普品 14%。clip(true) + borderRadius(5) 让堆叠条的两端有圆角。下方图例每项用圆点 对应颜色加文字标注。这种"堆叠条 + 图例"是数据可视化的经典组合,在直播场景中能快速传达"本场鉴定的整体结果分布"。


第五段:藏品编辑和数据更新

5.1 编辑藏品抽屉

  @Builder
  editSheet192() {
    Column() {
      Row() {
        Text('')
          .width(36)
          .height(4)
          .borderRadius(2)
          .backgroundColor('#D9D2C5')
      }
      .width('100%')
      .justifyContent(FlexAlign.Center)
      .margin({ top: 10 })

      Text('编辑藏品')
        .fontSize(17)
        .fontWeight(FontWeight.Bold)
        .fontColor('#3E2723')
        .margin({ top: 12 })

      Column() {
        Text('藏品名称')
          .fontSize(12)
          .fontColor('#8D7B6E')
        Row() {
          Text(this.editName)
            .fontSize(13)
            .fontColor('#3E2723')
          Text('')
            .layoutWeight(1)
        }
        .width('100%')
        .padding(12)
        .borderRadius(10)
        .backgroundColor('#F7F3EA')
        .margin({ top: 8 })
      }
      .width('100%')
      .margin({ top: 16 })
      .alignItems(HorizontalAlign.Start)

      Column() {
        Text('展示密级')
          .fontSize(12)
          .fontColor('#8D7B6E')
        Row() {
          ForEach(PRIVACY192, (p: string, i: number) => {
            Text(p)
              .fontSize(12)
              .fontColor(i === this.privacyIdx ? '#FFFFFF' : '#5D4037')
              .padding({ left: 14, right: 14, top: 7, bottom: 7 })
              .borderRadius(16)
              .backgroundColor(i === this.privacyIdx ? '#5D4037' : '#F1E8D8')
              .margin({ right: 8 })
              .onClick(() => {
                this.privacyIdx = i;
              })
          }, (p: string) => p)
        }
        .margin({ top: 8 })
      }
      .width('100%')
      .margin({ top: 16 })
      .alignItems(HorizontalAlign.Start)

      Column() {
        Text('最新估值(万元)')
          .fontSize(12)
          .fontColor('#8D7B6E')
        Row() {
          Text('−')
            .fontSize(18)
            .fontColor('#5D4037')
            .width(34)
            .height(34)
            .textAlign(TextAlign.Center)
            .borderRadius(17)
            .backgroundColor('#F1E8D8')
            .onClick(() => {
              if (this.estValue > 1) {
                this.estValue -= 1;
              }
            })
          Text(this.estValue + ' 万')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
            .layoutWeight(1)
            .textAlign(TextAlign.Center)
          Text('+')
            .fontSize(18)
            .fontColor('#5D4037')
            .width(34)
            .height(34)
            .textAlign(TextAlign.Center)
            .borderRadius(17)
            .backgroundColor('#F1E8D8')
            .onClick(() => {
              if (this.estValue < 99) {
                this.estValue += 1;
              }
            })
        }
        .width('100%')
        .margin({ top: 8 })
      }
      .width('100%')
      .margin({ top: 16 })
      .alignItems(HorizontalAlign.Start)

编辑藏品抽屉的"展示密级"选择器只有两个选项——“公开展示"和"仅自己可见”。这是藏品隐私管理的核心开关。估值步进器用加减按钮,范围 1-99 万,每次增减 1 万。注意加减按钮的视觉权重相同——都是浅色底棕色字,不像其他应用中加号用深色、减号用浅色。这里的考量是:估值增减都是中性的编辑操作,不需要视觉上偏向"增加"。

5.2 保存逻辑

      .onClick(() => {
        this.myLots = this.myLots.map((l: Lot192, i: number) => {
          if (i === this.editIdx) {
            return {
              id: l.id, name: this.editName, cate: l.cate, era: l.era,
              grade: l.grade, value: this.estValue, owner: l.owner,
              verdict: l.verdict, color: l.color, hot: l.hot, state: l.state
            };
          }
          return l;
        });
        this.showEditSheet = false;
      })

保存逻辑用 map 遍历 myLots 数组,匹配到 editIdx 时用新对象替换(只改 namevalue),其余保持不变。新数组赋值给 this.myLots 触发 @State 响应式更新,CollectionTab192 收到新数据后自动重新渲染。这种"不可变更新"模式是 ArkTS 中修改数组状态的标准做法——永远不直接修改原数组,而是生成新数组赋值回去。

5.3 撤销送鉴单

      .onClick(() => {
        this.myOrders = this.myOrders.filter((o: Session192, i: number) => i !== this.delIdx);
        this.showDelDialog = false;
      })

撤销送鉴单用 filter 过滤掉 delIdx 对应的订单,生成新数组赋值给 myOrders。这跟编辑用的 map 是同一类操作——不可变数据更新。filter 保留不匹配的元素,map 替换匹配的元素,两者配合可以覆盖数组的增删改三种操作。

主播划重点:在 ArkTS 里操作数组状态,记住三个法宝——增用 concat,删用 filter,改用 map。永远不要用 pushsplicearr[i] = x 这种直接修改方式。这不是语法限制,是响应式系统的设计要求。


流程图:从浏览到连麦申请的完整路径

0

1

2

3

4

5

应用启动

顶部直播状态栏 渐变青铜棕

底部6 Tab展柜导航

curTab 判断

LiveTab 鉴宝台

CollectionTab 藏品阁

OrderTab 送鉴单

ExpertTab 行家团

ScheduleTab 档期

MineTab 我的

四宫格Grid连麦窗口

藏品列表+弹幕区

点击申请连麦

bindSheet 连麦抽屉

选品类/行家/估值/加急

点击申请连麦

关闭抽屉

点击编辑藏品

bindSheet 编辑抽屉

map替换 生成新myLots

点击撤销送鉴

bindContentCover 撤销确认

确认撤销?

filter过滤 生成新myOrders

关闭弹窗

点击预约档期


技术要素对比

技术要素 实现方式 主播点评
四宫格连麦窗口 Grid + columnsTemplate('1fr 1fr') 比用 Row+Column 更原生,原生支持行列间距
展柜导航 选中时金线+暖光底+白底图标 设计语言与"鉴宝"业务场景深度绑定
颜色映射函数 gradeColor192/stateColor192/verdictColor192 把业务语义翻译为视觉语言,DRY 原则
不可变数组更新 concat/删filter/改map ArkTS 响应式系统的核心要求
连麦申请表单 胶囊选择器+头像选择器+步进器 三种选择器统一用反转色做选中反馈
弹层分工 bindSheet抽屉 + bindContentCover遮罩 日常表单走抽屉,破坏性确认走全屏遮罩
热度可视化 width(l.hot * 0.9) 像素宽度 数值直接映射像素,简单直接
结论占比条 layoutWeight(s.w) + clip(true) 比例权重自动分配,clip裁切圆角
品类首字图标 l.cate.charAt(0) 无图标资源时的巧妙替代方案
直播指示器 + #FFD54F + 半透明黑底 直播 APP 标配的视觉模式

安装DevEco Studio程序

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

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

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

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

在这里插入图片描述


完整代码:

// 场景:藏友通过视频会议室连麦专家,直播鉴定瓷器玉器书画等藏品
// 配色:青铜棕 #5D4037 × 鎏金 #D4AF37 × 米宣白 #F7F3EA
// Tab布局:底部6 tab「展柜格」导航(选中格子暖光底+顶部细金线,图标圆底如展品)
// 弹框:申请连麦鉴宝(抽屉)/新增送鉴单(抽屉)/编辑藏品(抽屉)/删除送鉴单(居中)/藏品详情(居中)

interface TabItem192 {
  name: string;
  icon: string;
  badge: number;
}

interface Lot192 {
  id: string;
  name: string;
  cate: string;
  era: string;
  grade: string;
  value: number;
  owner: string;
  verdict: string;
  color: string;
  hot: number;
  state: string
}

interface VerdictRow192 {
  id: string;
  lot: string;
  expert: string;
  result: string;
  date: string;
  conf: number;
  color: string;
}

interface Expert192 {
  id: string;
  name: string;
  field: string;
  title: string;
  rating: number;
  sessions: number;
  emoji: string;
  online: boolean;
}

interface Session192 {
  id: string;
  date: string;
  week: string;
  time: string;
  theme: string;
  expert: string;
  joined: number;
  seats: number;
  state: string;
}

interface Danmaku192 {
  id: string;
  user: string;
  text: string;
  color: string;
}

interface Attend192 {
  day: string;
  cnt: number;
}

interface Share192 {
  label: string;
  w: number;
  c: string;
}

interface HotCate192 {
  name: string;
  pct: number;
  color: string;
}

interface Record192 {
  id: string;
  date: string;
  lot: string;
  result: string;
  color: string;
}

const TABS192: TabItem192[] = [
  { name: '鉴宝台', icon: '🏺', badge: 1 },
  { name: '藏品阁', icon: '🗂️', badge: 0 },
  { name: '送鉴单', icon: '📋', badge: 3 },
  { name: '行家团', icon: '🧑‍🏫', badge: 0 },
  { name: '档期', icon: '📅', badge: 0 },
  { name: '我的', icon: '👤', badge: 0 }
];

const LOTS192: Lot192[] = [
  { id: 'l1', name: '青花缠枝纹梅瓶', cate: '瓷器', era: '明永乐', grade: '珍品', value: 128, owner: '金陵藏家·陆先生', verdict: '真品', color: '#5D4037', hot: 97, state: '待定' },
  { id: 'l2', name: '和田白玉把件', cate: '玉器', era: '清中期', grade: '上品', value: 66, owner: '姑苏·沈女士', verdict: '真品', color: '#00695C', hot: 89, state: '待定' },
  { id: 'l3', name: '田黄石瑞兽钮章', cate: '印石', era: '民国', grade: '上品', value: 92, owner: '榕城·林先生', verdict: '真品', color: '#AD1457', hot: 93, state: '待定' },
  { id: 'l4', name: '仿哥窑贯耳瓶', cate: '瓷器', era: '清晚期', grade: '普品', value: 8, owner: '蜀中·赵女士', verdict: '后仿', color: '#757575', hot: 64, state: '待定' },
  { id: 'l5', name: '吴门山水扇面', cate: '书画', era: '明', grade: '珍品', value: 210, owner: '沪上·程先生', verdict: '待考', color: '#283593', hot: 99, state: '待定' },
  { id: 'l6', name: '鎏金铜坐佛', cate: '铜器', era: '明早期', grade: '上品', value: 155, owner: '长安·马先生', verdict: '真品', color: '#EF6C00', hot: 91, state: '待定' },
  { id: 'l7', name: '端砚云月池', cate: '文房', era: '清', grade: '上品', value: 48, owner: '肇庆·梁先生', verdict: '真品', color: '#00838F', hot: 72, state: '待定' },
  { id: 'l8', name: '粉彩百蝶瓶', cate: '瓷器', era: '清光绪', grade: '珍品', value: 178, owner: '津门·周女士', verdict: '真品', color: '#827717', hot: 95, state: '待定' }
];

const VERDICTS192: VerdictRow192[] = [
  { id: 'v1', lot: '青花缠枝纹梅瓶', expert: '瓷器组·程汝言', result: '真品·明永乐本朝', date: '08-24', conf: 96, color: '#5D4037' },
  { id: 'v2', lot: '和田白玉把件', expert: '玉器组·白玛老师', result: '真品·清中期工', date: '08-24', conf: 91, color: '#00695C' },
  { id: 'v3', lot: '仿哥窑贯耳瓶', expert: '瓷器组·程汝言', result: '后仿·晚清仿宋', date: '08-23', conf: 88, color: '#757575' },
  { id: 'v4', lot: '田黄石瑞兽钮章', expert: '印石组·林半尺', result: '真品·民国老料', date: '08-23', conf: 93, color: '#AD1457' },
  { id: 'v5', lot: '端砚云月池', expert: '文房组·梁砚秋', result: '真品·清坑仔岩', date: '08-22', conf: 90, color: '#00838F' },
  { id: 'v6', lot: '粉彩百蝶瓶', expert: '瓷器组·程汝言', result: '真品·光绪官样', date: '08-22', conf: 94, color: '#827717' }
];

const EXPERTS192: Expert192[] = [
  { id: 'e1', name: '程汝言', field: '瓷器', title: '副研究馆员', rating: 4.9, sessions: 486, emoji: '🏺', online: true },
  { id: 'e2', name: '白玛', field: '玉器', title: '高级鉴定师', rating: 4.8, sessions: 352, emoji: '🪨', online: true },
  { id: 'e3', name: '林半尺', field: '印石', title: '民间藏家代表', rating: 4.7, sessions: 298, emoji: '🖊️', online: false },
  { id: 'e4', name: '顾墨白', field: '书画', title: '美术学院特聘', rating: 5.0, sessions: 275, emoji: '🖼️', online: true },
  { id: 'e5', name: '马远声', field: '铜器', title: '考古队顾问', rating: 4.8, sessions: 231, emoji: '🔔', online: false },
  { id: 'e6', name: '梁砚秋', field: '文房', title: '端砚研究会理事', rating: 4.6, sessions: 189, emoji: '📖', online: true }
];

const SESSIONS192: Session192[] = [
  { id: 's1', date: '08-26', week: '周三', time: '19:30', theme: '瓷器专场·永宣青花', expert: '程汝言', joined: 412, seats: 500, state: '可预约' },
  { id: 's2', date: '08-27', week: '周四', time: '20:00', theme: '玉器专场·和田籽料', expert: '白玛', joined: 356, seats: 500, state: '可预约' },
  { id: 's3', date: '08-28', week: '周五', time: '19:00', theme: '书画专场·吴门画派', expert: '顾墨白', joined: 488, seats: 500, state: '即将满员' },
  { id: 's4', date: '08-29', week: '周六', time: '14:00', theme: '综合场·藏友互评', expert: '全组行家', joined: 500, seats: 500, state: '已满员' },
  { id: 's5', date: '08-30', week: '周日', time: '10:00', theme: '铜器专场·金铜佛像', expert: '马远声', joined: 274, seats: 500, state: '可预约' },
  { id: 's6', date: '08-31', week: '周一', time: '19:30', theme: '文房专场·砚墨纸笔', expert: '梁砚秋', joined: 198, seats: 500, state: '可预约' },
  { id: 's7', date: '09-01', week: '周二', time: '20:00', theme: '印石专场·寿山田黄', expert: '林半尺', joined: 321, seats: 500, state: '可预约' }
];

const DANMAKU192: Danmaku192[] = [
  { id: 'd1', user: '捡漏王', text: '这件梅瓶釉水一看就是本朝的!', color: '#5D4037' },
  { id: 'd2', user: '苏工坊', text: '专家上手上手,让让镜头~', color: '#AD1457' },
  { id: 'd3', user: '青花控', text: '缠枝纹画工太流畅了', color: '#283593' },
  { id: 'd4', user: '老物件', text: '我家也有个类似的,蹲个结论', color: '#00695C' },
  { id: 'd5', user: '文玩小生', text: '灯光再给个侧光看气泡', color: '#EF6C00' },
  { id: 'd6', user: '古巷', text: '这个底足修胎对味儿', color: '#827717' },
  { id: 'd7', user: '藏珠阁', text: '128万估值保守了', color: '#00838F' }
];

const ATTEND192: Attend192[] = [
  { day: '一', cnt: 46 },
  { day: '二', cnt: 52 },
  { day: '三', cnt: 38 },
  { day: '四', cnt: 61 },
  { day: '五', cnt: 74 },
  { day: '六', cnt: 92 },
  { day: '日', cnt: 85 }
];

const SHARE192: Share192[] = [
  { label: '真品', w: 46, c: '#5D4037' },
  { label: '后仿', w: 22, c: '#B08968' },
  { label: '待考', w: 18, c: '#D4AF37' },
  { label: '普品', w: 14, c: '#C8B7A6' }
];

const HOTCATES192: HotCate192[] = [
  { name: '瓷器', pct: 96, color: '#5D4037' },
  { name: '书画', pct: 88, color: '#283593' },
  { name: '玉器', pct: 82, color: '#00695C' },
  { name: '铜器', pct: 71, color: '#EF6C00' },
  { name: '印石', pct: 63, color: '#AD1457' },
  { name: '文房', pct: 55, color: '#00838F' }
];

const RECORDS192: Record192[] = [
  { id: 'r1', date: '08-24 21:02', lot: '青花缠枝纹梅瓶', result: '真品·明永乐', color: '#5D4037' },
  { id: 'r2', date: '08-23 20:45', lot: '仿哥窑贯耳瓶', result: '后仿·晚清仿宋', color: '#757575' },
  { id: 'r3', date: '08-22 19:58', lot: '端砚云月池', result: '真品·清坑仔岩', color: '#00838F' },
  { id: 'r4', date: '08-21 20:30', lot: '粉彩百蝶瓶', result: '真品·光绪官样', color: '#827717' },
  { id: 'r5', date: '08-20 19:47', lot: '铜鎏金小坐像', result: '真品·明早期', color: '#EF6C00' }
];

const CATES192: string[] = ['瓷器', '玉器', '书画', '铜器', '印石', '文房'];
const URGENTS192: string[] = ['普通排期', '加急48h', '特急24h'];
const PRIVACY192: string[] = ['公开展示', '仅自己可见'];

function gradeColor192(g: string): string {
  if (g === '珍品') {
    return '#D4AF37';
  }
  if (g === '上品') {
    return '#5D4037';
  }
  return '#9E9E9E';
}

function stateColor192(s: string): string {
  if (s === '可预约') {
    return '#00695C';
  }
  if (s === '即将满员') {
    return '#EF6C00';
  }
  return '#9E9E9E';
}

function verdictColor192(v: string): string {
  if (v === '真品') {
    return '#00695C';
  }
  if (v === '后仿') {
    return '#C62828';
  }
  return '#EF6C00';
}

@Entry
@Component
struct Index192 {
  @State curTab: number = 0;
  @State showLianmaiSheet: boolean = false;
  @State showSendSheet: boolean = false;
  @State showEditSheet: boolean = false;
  @State showDelDialog: boolean = false;
  @State showDetailDialog: boolean = false;
  @State editIdx: number = 0;
  @State editName: string = '';
  @State delIdx: number = 0;
  @State detailIdx: number = 0;
  @State cateIdx: number = 0;
  @State urgentIdx: number = 0;
  @State privacyIdx: number = 0;
  @State hopeExpert: number = 0;
  @State estValue: number = 10;
  @State insureOn: boolean = true;
  @State pickupOn: boolean = false;
  @State keepRecordOn: boolean = true;
  @State myLots: Lot192[] = [
    { id: 'm1', name: '青花缠枝纹梅瓶', cate: '瓷器', era: '明永乐', grade: '珍品', value: 128, owner: '我', verdict: '真品', color: '#5D4037', hot: 97, state: '待定' },
    { id: 'm2', name: '端砚云月池', cate: '文房', era: '清', grade: '上品', value: 48, owner: '我', verdict: '真品', color: '#00838F', hot: 72, state: '待定' },
    { id: 'm3', name: '铜鎏金小坐像', cate: '铜器', era: '明早期', grade: '上品', value: 155, owner: '我', verdict: '真品', color: '#EF6C00', hot: 90, state: '待定' }
  ];
  @State myOrders: Session192[] = [
    { id: 'o1', date: '08-26', week: '周三', time: '19:30', theme: '瓷器专场·永宣青花', expert: '程汝言', joined: 412, seats: 500, state: '已预约' },
    { id: 'o2', date: '08-28', week: '周五', time: '19:00', theme: '书画专场·吴门画派', expert: '顾墨白', joined: 488, seats: 500, state: '已预约' }
  ];

  build() {
    Column() {
      Column() {
        Text('鎏光·云鉴宝')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
        Row() {
          Row() {
            Text('●')
              .fontSize(9)
              .fontColor('#FFD54F')
            Text('直播中 · 第128场')
              .fontSize(11)
              .fontColor('#FFD54F')
              .margin({ left: 4 })
          }
          .padding({ left: 10, right: 10, top: 3, bottom: 3 })
          .borderRadius(10)
          .backgroundColor('#4D000000')
          Text('')
            .layoutWeight(1)
          Text('6.8万人看过')
            .fontSize(11)
            .fontColor('#E8DCC8')
        }
        .width('100%')
        .margin({ top: 10 })

        Row() {
          ForEach(LOTS192, (l: Lot192, idx: number) => {
            Column() {
              Text(l.era)
                .fontSize(9)
                .fontColor(idx === 0 ? '#5D4037' : '#E8DCC8')
              Text(idx === 0 ? '正在鉴定' : l.verdict)
                .fontSize(10)
                .fontWeight(idx === 0 ? FontWeight.Bold : FontWeight.Normal)
                .fontColor(idx === 0 ? '#FFD54F' : '#E8DCC8')
            }
            .padding({ left: 8, right: 8, top: 4, bottom: 4 })
            .borderRadius(8)
            .backgroundColor(idx === 0 ? '#D4AF37' : '#33000000')
          }, (l: Lot192) => l.id)
        }
        .width('100%')
        .margin({ top: 10 })
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 14, bottom: 14 })
      .linearGradient({
        angle: 160,
        colors: [['#4E342E', 0], ['#5D4037', 0.5], ['#3E2723', 1]]
      })

      Scroll() {
        Column() {
          if (this.curTab === 0) {
            LiveTab192({
              onLianmai: () => {
                this.showLianmaiSheet = true;
              }
            })
          }
          if (this.curTab === 1) {
            CollectionTab192({
              lots: this.myLots,
              onEdit: (idx: number) => {
                this.editIdx = idx;
                this.editName = this.myLots[idx].name;
                this.showEditSheet = true;
              },
              onDetail: (idx: number) => {
                this.detailIdx = idx;
                this.showDetailDialog = true;
              }
            })
          }
          if (this.curTab === 2) {
            OrderTab192({
              orders: this.myOrders,
              onSend: () => {
                this.showSendSheet = true;
              },
              onDel: (idx: number) => {
                this.delIdx = idx;
                this.showDelDialog = true;
              }
            })
          }
          if (this.curTab === 3) {
            ExpertTab192()
          }
          if (this.curTab === 4) {
            ScheduleTab192({
              onLianmai: () => {
                this.showLianmaiSheet = true;
              }
            })
          }
          if (this.curTab === 5) {
            MineTab192({
              records: RECORDS192,
              onDetail: () => {
                this.detailIdx = 0;
                this.showDetailDialog = true;
              }
            })
          }
        }
        .width('100%')
      }
      .layoutWeight(1)
      .scrollBar(BarState.Off)
      .backgroundColor('#F7F3EA')

      Row() {
        ForEach(TABS192, (t: TabItem192, idx: number) => {
          Column() {
            Text('')
              .width(idx === this.curTab ? 26 : 0)
              .height(2)
              .backgroundColor('#D4AF37')
              .borderRadius(1)
              .margin({ top: 6 })
            Row() {
              Text(t.icon)
                .fontSize(18)
                .width(38)
                .height(38)
                .textAlign(TextAlign.Center)
                .borderRadius(19)
                .backgroundColor(idx === this.curTab ? '#FFFFFF' : '#F1E8D8')
              Column() {
                Text(t.name)
                  .fontSize(10)
                  .fontWeight(idx === this.curTab ? FontWeight.Bold : FontWeight.Normal)
                  .fontColor(idx === this.curTab ? '#5D4037' : '#8D7B6E')
                if (t.badge > 0) {
                  Text(t.badge + '')
                    .fontSize(8)
                    .fontColor('#FFFFFF')
                    .padding({ left: 5, right: 5, top: 1, bottom: 1 })
                    .borderRadius(7)
                    .backgroundColor('#C62828')
                    .margin({ top: 2 })
                }
              }
              .margin({ left: 6 })
            }
            .padding({ left: 10, right: 10, top: 6, bottom: 6 })
            .borderRadius(14)
            .backgroundColor(idx === this.curTab ? '#F9EED2' : '#F7F3EA')
          }
          .layoutWeight(1)
          .alignItems(HorizontalAlign.Center)
          .onClick(() => {
            this.curTab = idx;
          })
        }, (t: TabItem192, idx: number) => t.name + idx)
      }
      .width('100%')
      .padding({ left: 6, right: 6, bottom: 8 })
      .backgroundColor('#FFFFFF')
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F7F3EA')
    .bindSheet($$this.showLianmaiSheet, this.lianmaiSheet192(), {
      height: 580,
      dragBar: true,
      showClose: false,
      backgroundColor: '#FFFFFF'
    })
    .bindSheet($$this.showSendSheet, this.sendSheet192(), {
      height: 560,
      dragBar: true,
      showClose: false,
      backgroundColor: '#FFFFFF'
    })
    .bindSheet($$this.showEditSheet, this.editSheet192(), {
      height: 520,
      dragBar: true,
      showClose: false,
      backgroundColor: '#FFFFFF'
    })
    .bindContentCover($$this.showDelDialog, this.delDialog192(), {
    })
    .bindContentCover($$this.showDetailDialog, this.detailDialog192(), {
    })
  }

  @Builder
  lianmaiSheet192() {
    Column() {
      Row() {
        Text('')
          .width(36)
          .height(4)
          .borderRadius(2)
          .backgroundColor('#D9D2C5')
      }
      .width('100%')
      .justifyContent(FlexAlign.Center)
      .margin({ top: 10 })

      Text('申请连麦鉴宝')
        .fontSize(17)
        .fontWeight(FontWeight.Bold)
        .fontColor('#3E2723')
        .margin({ top: 12 })

      Column() {
        Text('藏品名称')
          .fontSize(12)
          .fontColor('#8D7B6E')
        Row() {
          Text('请输入藏品全名(选填)')
            .fontSize(13)
            .fontColor('#B8AC9C')
          Text('')
            .layoutWeight(1)
        }
        .width('100%')
        .padding(12)
        .borderRadius(10)
        .backgroundColor('#F7F3EA')
        .margin({ top: 8 })
      }
      .width('100%')
      .margin({ top: 16 })
      .alignItems(HorizontalAlign.Start)

      Column() {
        Text('藏品品类')
          .fontSize(12)
          .fontColor('#8D7B6E')
        Row() {
          ForEach(CATES192, (c: string, i: number) => {
            Text(c)
              .fontSize(12)
              .fontColor(i === this.cateIdx ? '#FFFFFF' : '#5D4037')
              .padding({ left: 14, right: 14, top: 7, bottom: 7 })
              .borderRadius(16)
              .backgroundColor(i === this.cateIdx ? '#5D4037' : '#F1E8D8')
              .margin({ right: 8 })
              .onClick(() => {
                this.cateIdx = i;
              })
          }, (c: string) => c)
        }
        .margin({ top: 8 })
      }
      .width('100%')
      .margin({ top: 16 })
      .alignItems(HorizontalAlign.Start)

      Column() {
        Text('期望行家')
          .fontSize(12)
          .fontColor('#8D7B6E')
        Row() {
          ForEach(EXPERTS192, (e: Expert192, i: number) => {
            Column() {
              Text(e.emoji)
                .fontSize(18)
                .width(42)
                .height(42)
                .textAlign(TextAlign.Center)
                .borderRadius(21)
                .backgroundColor(i === this.hopeExpert ? '#D4AF37' : '#F1E8D8')
              Text(e.name)
                .fontSize(9)
                .fontColor(i === this.hopeExpert ? '#5D4037' : '#8D7B6E')
                .margin({ top: 4 })
            }
            .margin({ right: 12 })
            .onClick(() => {
              this.hopeExpert = i;
            })
          }, (e: Expert192) => e.id)
        }
        .margin({ top: 8 })
      }
      .width('100%')
      .margin({ top: 16 })
      .alignItems(HorizontalAlign.Start)

      Column() {
        Text('心理估值(万元)')
          .fontSize(12)
          .fontColor('#8D7B6E')
        Row() {
          Text('−')
            .fontSize(18)
            .fontColor('#5D4037')
            .width(34)
            .height(34)
            .textAlign(TextAlign.Center)
            .borderRadius(17)
            .backgroundColor('#F1E8D8')
            .onClick(() => {
              if (this.estValue > 1) {
                this.estValue -= 1;
              }
            })
          Text(this.estValue + ' 万')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
            .layoutWeight(1)
            .textAlign(TextAlign.Center)
          Text('+')
            .fontSize(18)
            .fontColor('#5D4037')
            .width(34)
            .height(34)
            .textAlign(TextAlign.Center)
            .borderRadius(17)
            .backgroundColor('#F1E8D8')
            .onClick(() => {
              if (this.estValue < 99) {
                this.estValue += 1;
              }
            })
        }
        .width('100%')
        .margin({ top: 8 })
      }
      .width('100%')
      .margin({ top: 16 })
      .alignItems(HorizontalAlign.Start)

      Row() {
        Column() {
          Text('加急通道')
            .fontSize(13)
            .fontColor('#3E2723')
          Text('优先排入当前直播队列')
            .fontSize(10)
            .fontColor('#8D7B6E')
            .margin({ top: 2 })
        }
        .alignItems(HorizontalAlign.Start)
        Text('')
          .layoutWeight(1)
        ForEach(URGENTS192, (u: string, i: number) => {
          Text(u)
            .fontSize(11)
            .fontColor(i === this.urgentIdx ? '#FFFFFF' : '#5D4037')
            .padding({ left: 10, right: 10, top: 6, bottom: 6 })
            .borderRadius(12)
            .backgroundColor(i === this.urgentIdx ? '#C62828' : '#F1E8D8')
            .margin({ left: 6 })
            .onClick(() => {
              this.urgentIdx = i;
            })
        }, (u: string) => u)
      }
      .width('100%')
      .margin({ top: 18 })

      Row() {
        Text('申请连麦')
          .fontSize(15)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
          .textAlign(TextAlign.Center)
          .width('100%')
          .padding({ top: 13, bottom: 13 })
          .borderRadius(22)
          .linearGradient({
            angle: 90,
            colors: [['#5D4037', 0], ['#8D6E63', 1]]
          })
      }
      .width('100%')
      .margin({ top: 20 })
      .onClick(() => {
        this.showLianmaiSheet = false;
      })
    }
    .width('100%')
    .padding({ left: 20, right: 20, bottom: 24 })
    .constraintSize({ maxHeight: '85%' })
  }

  @Builder
  sendSheet192() {
    Column() {
      Row() {
        Text('')
          .width(36)
          .height(4)
          .borderRadius(2)
          .backgroundColor('#D9D2C5')
      }
      .width('100%')
      .justifyContent(FlexAlign.Center)
      .margin({ top: 10 })

      Text('新增送鉴单')
        .fontSize(17)
        .fontWeight(FontWeight.Bold)
        .fontColor('#3E2723')
        .margin({ top: 12 })

      Column() {
        Text('藏品名称')
          .fontSize(12)
          .fontColor('#8D7B6E')
        Row() {
          Text('如:青花缠枝纹梅瓶')
            .fontSize(13)
            .fontColor('#B8AC9C')
          Text('')
            .layoutWeight(1)
        }
        .width('100%')
        .padding(12)
        .borderRadius(10)
        .backgroundColor('#F7F3EA')
        .margin({ top: 8 })
      }
      .width('100%')
      .margin({ top: 16 })
      .alignItems(HorizontalAlign.Start)

      Column() {
        Text('藏品品类')
          .fontSize(12)
          .fontColor('#8D7B6E')
        Row() {
          ForEach(CATES192, (c: string, i: number) => {
            Text(c)
              .fontSize(12)
              .fontColor(i === this.cateIdx ? '#FFFFFF' : '#5D4037')
              .padding({ left: 14, right: 14, top: 7, bottom: 7 })
              .borderRadius(16)
              .backgroundColor(i === this.cateIdx ? '#5D4037' : '#F1E8D8')
              .margin({ right: 8 })
              .onClick(() => {
                this.cateIdx = i;
              })
          }, (c: string) => c)
        }
        .margin({ top: 8 })
      }
      .width('100%')
      .margin({ top: 16 })
      .alignItems(HorizontalAlign.Start)

      Column() {
        Text('保价金额(万元)')
          .fontSize(12)
          .fontColor('#8D7B6E')
        Row() {
          Text('−')
            .fontSize(18)
            .fontColor('#5D4037')
            .width(34)
            .height(34)
            .textAlign(TextAlign.Center)
            .borderRadius(17)
            .backgroundColor('#F1E8D8')
            .onClick(() => {
              if (this.estValue > 1) {
                this.estValue -= 1;
              }
            })
          Text(this.estValue + ' 万')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
            .layoutWeight(1)
            .textAlign(TextAlign.Center)
          Text('+')
            .fontSize(18)
            .fontColor('#5D4037')
            .width(34)
            .height(34)
            .textAlign(TextAlign.Center)
            .borderRadius(17)
            .backgroundColor('#F1E8D8')
            .onClick(() => {
              if (this.estValue < 99) {
                this.estValue += 1;
              }
            })
        }
        .width('100%')
        .margin({ top: 8 })
      }
      .width('100%')
      .margin({ top: 16 })
      .alignItems(HorizontalAlign.Start)

      Row() {
        Column() {
          Text('上门取件')
            .fontSize(13)
            .fontColor('#3E2723')
          Text('顺丰保价 · 专业防震包装')
            .fontSize(10)
            .fontColor('#8D7B6E')
            .margin({ top: 2 })
        }
        .alignItems(HorizontalAlign.Start)
        Text('')
          .layoutWeight(1)
        Toggle({ type: ToggleType.Switch, isOn: this.pickupOn })
          .onChange((on: boolean) => {
            this.pickupOn = on;
          })
      }
      .width('100%')
      .margin({ top: 18 })

      Row() {
        Column() {
          Text('购买运输保险')
            .fontSize(13)
            .fontColor('#3E2723')
          Text('按保价金额 0.3% 计费')
            .fontSize(10)
            .fontColor('#8D7B6E')
            .margin({ top: 2 })
        }
        .alignItems(HorizontalAlign.Start)
        Text('')
          .layoutWeight(1)
        Toggle({ type: ToggleType.Switch, isOn: this.insureOn })
          .onChange((on: boolean) => {
            this.insureOn = on;
          })
      }
      .width('100%')
      .margin({ top: 14 })

      Row() {
        Text('提交送鉴单')
          .fontSize(15)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
          .textAlign(TextAlign.Center)
          .width('100%')
          .padding({ top: 13, bottom: 13 })
          .borderRadius(22)
          .linearGradient({
            angle: 90,
            colors: [['#D4AF37', 0], ['#B8860B', 1]]
          })
      }
      .width('100%')
      .margin({ top: 20 })
      .onClick(() => {
        this.showSendSheet = false;
      })
    }
    .width('100%')
    .padding({ left: 20, right: 20, bottom: 24 })
    .constraintSize({ maxHeight: '85%' })
  }

  @Builder
  editSheet192() {
    Column() {
      Row() {
        Text('')
          .width(36)
          .height(4)
          .borderRadius(2)
          .backgroundColor('#D9D2C5')
      }
      .width('100%')
      .justifyContent(FlexAlign.Center)
      .margin({ top: 10 })

      Text('编辑藏品')
        .fontSize(17)
        .fontWeight(FontWeight.Bold)
        .fontColor('#3E2723')
        .margin({ top: 12 })

      Column() {
        Text('藏品名称')
          .fontSize(12)
          .fontColor('#8D7B6E')
        Row() {
          Text(this.editName)
            .fontSize(13)
            .fontColor('#3E2723')
          Text('')
            .layoutWeight(1)
        }
        .width('100%')
        .padding(12)
        .borderRadius(10)
        .backgroundColor('#F7F3EA')
        .margin({ top: 8 })
      }
      .width('100%')
      .margin({ top: 16 })
      .alignItems(HorizontalAlign.Start)

      Column() {
        Text('展示密级')
          .fontSize(12)
          .fontColor('#8D7B6E')
        Row() {
          ForEach(PRIVACY192, (p: string, i: number) => {
            Text(p)
              .fontSize(12)
              .fontColor(i === this.privacyIdx ? '#FFFFFF' : '#5D4037')
              .padding({ left: 14, right: 14, top: 7, bottom: 7 })
              .borderRadius(16)
              .backgroundColor(i === this.privacyIdx ? '#5D4037' : '#F1E8D8')
              .margin({ right: 8 })
              .onClick(() => {
                this.privacyIdx = i;
              })
          }, (p: string) => p)
        }
        .margin({ top: 8 })
      }
      .width('100%')
      .margin({ top: 16 })
      .alignItems(HorizontalAlign.Start)

      Column() {
        Text('最新估值(万元)')
          .fontSize(12)
          .fontColor('#8D7B6E')
        Row() {
          Text('−')
            .fontSize(18)
            .fontColor('#5D4037')
            .width(34)
            .height(34)
            .textAlign(TextAlign.Center)
            .borderRadius(17)
            .backgroundColor('#F1E8D8')
            .onClick(() => {
              if (this.estValue > 1) {
                this.estValue -= 1;
              }
            })
          Text(this.estValue + ' 万')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
            .layoutWeight(1)
            .textAlign(TextAlign.Center)
          Text('+')
            .fontSize(18)
            .fontColor('#5D4037')
            .width(34)
            .height(34)
            .textAlign(TextAlign.Center)
            .borderRadius(17)
            .backgroundColor('#F1E8D8')
            .onClick(() => {
              if (this.estValue < 99) {
                this.estValue += 1;
              }
            })
        }
        .width('100%')
        .margin({ top: 8 })
      }
      .width('100%')
      .margin({ top: 16 })
      .alignItems(HorizontalAlign.Start)

      Row() {
        Column() {
          Text('藏品保险')
            .fontSize(13)
            .fontColor('#3E2723')
          Text('按估值的 0.5% 年费承保')
            .fontSize(10)
            .fontColor('#8D7B6E')
            .margin({ top: 2 })
        }
        .alignItems(HorizontalAlign.Start)
        Text('')
          .layoutWeight(1)
        Toggle({ type: ToggleType.Switch, isOn: this.insureOn })
          .onChange((on: boolean) => {
            this.insureOn = on;
          })
      }
      .width('100%')
      .margin({ top: 18 })

      Row() {
        Text('取消')
          .fontSize(14)
          .fontColor('#8D7B6E')
          .textAlign(TextAlign.Center)
          .layoutWeight(1)
          .padding({ top: 13, bottom: 13 })
          .borderRadius(22)
          .backgroundColor('#F1E8D8')
        Text('保存修改')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
          .textAlign(TextAlign.Center)
          .layoutWeight(1.6)
          .padding({ top: 13, bottom: 13 })
          .borderRadius(22)
          .margin({ left: 10 })
          .linearGradient({
            angle: 90,
            colors: [['#5D4037', 0], ['#8D6E63', 1]]
          })
      }
      .width('100%')
      .margin({ top: 20 })
      .onClick(() => {
        this.myLots = this.myLots.map((l: Lot192, i: number) => {
          if (i === this.editIdx) {
            return {
              id: l.id, name: this.editName, cate: l.cate, era: l.era,
              grade: l.grade, value: this.estValue, owner: l.owner,
              verdict: l.verdict, color: l.color, hot: l.hot, state: l.state
            };
          }
          return l;
        });
        this.showEditSheet = false;
      })
    }
    .width('100%')
    .padding({ left: 20, right: 20, bottom: 24 })
    .constraintSize({ maxHeight: '85%' })
  }

  @Builder
  delDialog192() {
    Column() {
      Text('🗑️')
        .fontSize(34)
        .margin({ top: 22 })
      Text('撤销送鉴单')
        .fontSize(17)
        .fontWeight(FontWeight.Bold)
        .fontColor('#3E2723')
        .margin({ top: 8 })
      Text('撤销后本场次席位将立即释放,重新预约需重新排队')
        .fontSize(12)
        .fontColor('#8D7B6E')
        .textAlign(TextAlign.Center)
        .margin({ top: 8 })

      Row() {
        Column() {
          Text('预约场次')
            .fontSize(10)
            .fontColor('#8D7B6E')
          Text(this.delIdx < this.myOrders.length ? this.myOrders[this.delIdx].theme : '-')
            .fontSize(12)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
            .margin({ top: 4 })
        }
        .alignItems(HorizontalAlign.Start)
        .layoutWeight(1)
        Column() {
          Text('已扣留位费')
            .fontSize(10)
            .fontColor('#8D7B6E')
          Text('¥ 20.00')
            .fontSize(12)
            .fontWeight(FontWeight.Bold)
            .fontColor('#C62828')
            .margin({ top: 4 })
        }
        .alignItems(HorizontalAlign.Start)
        .layoutWeight(1)
      }
      .width('100%')
      .padding(12)
      .borderRadius(12)
      .backgroundColor('#F7F3EA')
      .margin({ top: 16 })

      Row() {
        Text('同步删除鉴定记录')
          .fontSize(12)
          .fontColor('#3E2723')
        Text('')
          .layoutWeight(1)
        Toggle({ type: ToggleType.Switch, isOn: this.keepRecordOn })
          .onChange((on: boolean) => {
            this.keepRecordOn = on;
          })
      }
      .width('100%')
      .margin({ top: 14 })

      Row() {
        Text('再想想')
          .fontSize(14)
          .fontColor('#8D7B6E')
          .textAlign(TextAlign.Center)
          .layoutWeight(1)
          .padding({ top: 12, bottom: 12 })
          .borderRadius(20)
          .backgroundColor('#F1E8D8')
          .onClick(() => {
            this.showDelDialog = false;
          })
        Text('确认撤销')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
          .textAlign(TextAlign.Center)
          .layoutWeight(1.4)
          .padding({ top: 12, bottom: 12 })
          .borderRadius(20)
          .margin({ left: 10 })
          .backgroundColor('#C62828')
          .onClick(() => {
            this.myOrders = this.myOrders.filter((o: Session192, i: number) => i !== this.delIdx);
            this.showDelDialog = false;
          })
      }
      .width('100%')
      .margin({ top: 18 })
    }
    .width('86%')
    .padding({ left: 18, right: 18, bottom: 20 })
    .borderRadius(16)
    .backgroundColor('#FFFFFF')
  }

  @Builder
  detailDialog192() {
    Column() {
      Column() {
        Text('🏺')
          .fontSize(40)
        Text(this.detailIdx < this.myLots.length ? this.myLots[this.detailIdx].name : LOTS192[0].name)
          .fontSize(17)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
          .margin({ top: 6 })
        Text(this.detailIdx < this.myLots.length ? this.myLots[this.detailIdx].era + ' · ' + this.myLots[this.detailIdx].cate : LOTS192[0].era + ' · ' + LOTS192[0].cate)
          .fontSize(11)
          .fontColor('#E8DCC8')
          .margin({ top: 4 })
      }
      .width('100%')
      .padding({ top: 22, bottom: 18 })
      .borderRadius({ topLeft: 16, topRight: 16 })
      .linearGradient({
        angle: 135,
        colors: [['#5D4037', 0], ['#8D6E63', 1]]
      })

      Column() {
        Row() {
          Column() {
            Text('估值')
              .fontSize(10)
              .fontColor('#8D7B6E')
            Text((this.detailIdx < this.myLots.length ? this.myLots[this.detailIdx].value : LOTS192[0].value) + ' 万')
              .fontSize(15)
              .fontWeight(FontWeight.Bold)
              .fontColor('#B8860B')
              .margin({ top: 4 })
          }
          .layoutWeight(1)
          Column() {
            Text('等级')
              .fontSize(10)
              .fontColor('#8D7B6E')
            Text(this.detailIdx < this.myLots.length ? this.myLots[this.detailIdx].grade : LOTS192[0].grade)
              .fontSize(15)
              .fontWeight(FontWeight.Bold)
              .fontColor(gradeColor192(this.detailIdx < this.myLots.length ? this.myLots[this.detailIdx].grade : LOTS192[0].grade))
              .margin({ top: 4 })
          }
          .layoutWeight(1)
          Column() {
            Text('热度')
              .fontSize(10)
              .fontColor('#8D7B6E')
            Text((this.detailIdx < this.myLots.length ? this.myLots[this.detailIdx].hot : LOTS192[0].hot) + '')
              .fontSize(15)
              .fontWeight(FontWeight.Bold)
              .fontColor('#5D4037')
              .margin({ top: 4 })
          }
          .layoutWeight(1)
          Column() {
            Text('结论')
              .fontSize(10)
              .fontColor('#8D7B6E')
            Text(this.detailIdx < this.myLots.length ? this.myLots[this.detailIdx].verdict : LOTS192[0].verdict)
              .fontSize(15)
              .fontWeight(FontWeight.Bold)
              .fontColor(verdictColor192(this.detailIdx < this.myLots.length ? this.myLots[this.detailIdx].verdict : LOTS192[0].verdict))
              .margin({ top: 4 })
          }
          .layoutWeight(1)
        }
        .width('100%')

        Text('来源脉络')
          .fontSize(12)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
          .margin({ top: 16 })
        Column() {
          ForEach(RECORDS192, (r: Record192) => {
            Row() {
              Text('●')
                .fontSize(8)
                .fontColor(r.color)
                .margin({ top: 4 })
              Column() {
                Text(r.result)
                  .fontSize(12)
                  .fontColor('#3E2723')
                Text(r.date)
                  .fontSize(9)
                  .fontColor('#8D7B6E')
                  .margin({ top: 2 })
              }
              .margin({ left: 8 })
              .alignItems(HorizontalAlign.Start)
              Text('')
                .layoutWeight(1)
            }
            .width('100%')
            .padding({ top: 6, bottom: 6 })
          }, (r: Record192) => r.id)
        }
        .width('100%')
        .margin({ top: 8 })
      }
      .width('100%')
      .padding({ left: 18, right: 18, top: 14, bottom: 18 })

      Row() {
        Text('关闭')
          .fontSize(14)
          .fontColor('#8D7B6E')
          .textAlign(TextAlign.Center)
          .layoutWeight(1)
          .padding({ top: 12, bottom: 12 })
          .borderRadius(20)
          .backgroundColor('#F1E8D8')
        Text('送拍登记')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
          .textAlign(TextAlign.Center)
          .layoutWeight(1.4)
          .padding({ top: 12, bottom: 12 })
          .borderRadius(20)
          .margin({ left: 10 })
          .linearGradient({
            angle: 90,
            colors: [['#D4AF37', 0], ['#B8860B', 1]]
          })
      }
      .width('100%')
      .padding({ left: 18, right: 18, bottom: 18 })
      .onClick(() => {
        this.showDetailDialog = false;
      })
    }
    .width('86%')
    .borderRadius(16)
    .backgroundColor('#FFFFFF')
    .constraintSize({ maxHeight: '85%' })
  }
}

@Component
struct LiveTab192 {
  onLianmai: () => void = () => {
  };
  @State liked: boolean[] = [false, true, false, false, true, false, false];

  build() {
    Column() {
      Column() {
        Row() {
          Text('🔴')
            .fontSize(14)
          Text('第 128 场 · 瓷器专场')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
            .margin({ left: 6 })
          Text('')
            .layoutWeight(1)
          Text('在线 412')
            .fontSize(10)
            .fontColor('#8D7B6E')
        }
        .width('100%')

        Grid() {
          GridItem() {
            Column() {
              Text('🧑‍🏫')
                .fontSize(28)
              Text('程汝言 · 主鉴')
                .fontSize(9)
                .fontColor('#FFFFFF')
                .margin({ top: 4 })
            }
            .width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Center)
            .backgroundColor('#5D4037')
          }
          GridItem() {
            Column() {
              Text('🪞')
                .fontSize(28)
              Text('藏宝人视角')
                .fontSize(9)
                .fontColor('#FFFFFF')
                .margin({ top: 4 })
            }
            .width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Center)
            .backgroundColor('#8D6E63')
          }
          GridItem() {
            Column() {
              Text('🔍')
                .fontSize(28)
              Text('微距镜头')
                .fontSize(9)
                .fontColor('#FFFFFF')
                .margin({ top: 4 })
            }
            .width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Center)
            .backgroundColor('#4E342E')
          }
          GridItem() {
            Column() {
              Text('📜')
                .fontSize(28)
              Text('款识特写')
                .fontSize(9)
                .fontColor('#FFFFFF')
                .margin({ top: 4 })
            }
            .width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Center)
            .backgroundColor('#6D4C41')
          }
        }
        .columnsTemplate('1fr 1fr')
        .rowsTemplate('1fr 1fr')
        .width('100%')
        .height(210)
        .borderRadius(12)
        .columnsGap(2)
        .rowsGap(2)
        .margin({ top: 10 })

        Row() {
          Text('🙋')
            .fontSize(18)
            .width(38)
            .height(38)
            .textAlign(TextAlign.Center)
            .borderRadius(19)
            .backgroundColor('#F9EED2')
          Text('🎤')
            .fontSize(18)
            .width(38)
            .height(38)
            .textAlign(TextAlign.Center)
            .borderRadius(19)
            .backgroundColor('#F9EED2')
            .margin({ left: 8 })
          Text('📷')
            .fontSize(18)
            .width(38)
            .height(38)
            .textAlign(TextAlign.Center)
            .borderRadius(19)
            .backgroundColor('#F9EED2')
            .margin({ left: 8 })
          Text('')
            .layoutWeight(1)
          Text('申请连麦')
            .fontSize(13)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFFFFF')
            .padding({ left: 18, right: 18, top: 10, bottom: 10 })
            .borderRadius(18)
            .linearGradient({
              angle: 90,
              colors: [['#D4AF37', 0], ['#B8860B', 1]]
            })
            .onClick(() => {
              this.onLianmai();
            })
        }
        .width('100%')
        .margin({ top: 10 })
      }
      .width('100%')
      .padding(14)
      .borderRadius(14)
      .backgroundColor('#FFFFFF')
      .margin({ left: 12, right: 12, top: 12 })

      Row() {
        Text('现场鉴定单')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Text('')
          .layoutWeight(1)
        Text('本场 8 件')
          .fontSize(10)
          .fontColor('#8D7B6E')
      }
      .width('100%')
      .padding({ left: 16, right: 16 })
      .margin({ top: 16 })

      Column() {
        ForEach(LOTS192, (l: Lot192, i: number) => {
          Row() {
            Column() {
              Text(l.cate.charAt(0))
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor('#FFFFFF')
            }
            .width(44)
            .height(44)
            .justifyContent(FlexAlign.Center)
            .borderRadius(10)
            .backgroundColor(l.color)

            Column() {
              Row() {
                Text(l.name)
                  .fontSize(13)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#3E2723')
                Text(l.grade)
                  .fontSize(8)
                  .fontColor('#FFFFFF')
                  .padding({ left: 6, right: 6, top: 2, bottom: 2 })
                  .borderRadius(8)
                  .backgroundColor(gradeColor192(l.grade))
                  .margin({ left: 6 })
                Text('')
                  .layoutWeight(1)
                Text(l.verdict)
                  .fontSize(10)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(verdictColor192(l.verdict))
              }
              .width('100%')
              Text(l.era + ' · ' + l.owner + ' · 估值 ' + l.value + ' 万')
                .fontSize(10)
                .fontColor('#8D7B6E')
                .margin({ top: 4 })
              Row() {
                Text('')
                  .width('60%')
                  .height(3)
                  .borderRadius(2)
                  .backgroundColor('#F1E8D8')
                Text('')
                  .layoutWeight(1)
              }
              .width('100%')
              .margin({ top: 6 })
              Row() {
                Text('热度')
                  .fontSize(9)
                  .fontColor('#8D7B6E')
                Text('')
                  .width(l.hot * 0.9)
                  .height(4)
                  .borderRadius(2)
                  .backgroundColor(l.color)
                  .margin({ left: 6 })
                Text('')
                  .layoutWeight(1)
              }
              .width('100%')
              .margin({ top: 4 })
            }
            .layoutWeight(1)
            .margin({ left: 10 })
            .alignItems(HorizontalAlign.Start)

            Column() {
              Text(i === 0 ? '鉴定中' : '已完成')
                .fontSize(9)
                .fontColor(i === 0 ? '#C62828' : '#00695C')
              Text(this.liked[i] ? '🔥' : '👍')
                .fontSize(16)
                .margin({ top: 6 })
                .onClick(() => {
                  this.liked[i] = !this.liked[i];
                })
            }
            .margin({ left: 8 })
          }
          .width('100%')
          .padding(12)
          .borderRadius(12)
          .backgroundColor(i === 0 ? '#FBF4E4' : '#FFFFFF')
          .margin({ top: 8 })
        }, (l: Lot192) => l.id)
      }
      .width('100%')
      .padding({ left: 12, right: 12 })
      .margin({ top: 8 })

      Row() {
        Text('直播间热议')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Text('')
          .layoutWeight(1)
        Text('弹幕 ' + DANMAKU192.length + ' 条')
          .fontSize(10)
          .fontColor('#8D7B6E')
      }
      .width('100%')
      .padding({ left: 16, right: 16 })
      .margin({ top: 16 })

      Column() {
        ForEach(DANMAKU192, (d: Danmaku192) => {
          Row() {
            Text(d.user)
              .fontSize(10)
              .fontWeight(FontWeight.Bold)
              .fontColor(d.color)
            Text(d.text)
              .fontSize(11)
              .fontColor('#3E2723')
              .margin({ left: 8 })
              .layoutWeight(1)
          }
          .width('100%')
          .padding({ left: 12, right: 12, top: 8, bottom: 8 })
          .borderRadius(10)
          .backgroundColor('#FFFFFF')
          .margin({ top: 6 })
        }, (d: Danmaku192) => d.id)
      }
      .width('100%')
      .padding({ left: 12, right: 12 })
      .margin({ top: 8 })

      Column() {
        Text('本场鉴定结论构成')
          .fontSize(13)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Row() {
          ForEach(SHARE192, (s: Share192) => {
            Column() {
            }
            .layoutWeight(s.w)
            .height(10)
            .backgroundColor(s.c)
          }, (s: Share192) => s.label)
        }
        .width('100%')
        .clip(true)
        .borderRadius(5)
        .margin({ top: 10 })
        Row() {
          ForEach(SHARE192, (s: Share192) => {
            Row() {
              Text('●')
                .fontSize(8)
                .fontColor(s.c)
              Text(s.label + ' ' + s.w + '%')
                .fontSize(9)
                .fontColor('#8D7B6E')
                .margin({ left: 4 })
            }
            .margin({ right: 10 })
          }, (s: Share192) => s.label)
        }
        .width('100%')
        .margin({ top: 8 })
      }
      .width('100%')
      .padding(14)
      .borderRadius(14)
      .backgroundColor('#FFFFFF')
      .margin({ left: 12, right: 12, top: 16 })

      Column() {
        Text('本周每日鉴宝场次')
          .fontSize(13)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Row() {
          ForEach(ATTEND192, (a: Attend192) => {
            Column() {
              Text('')
                .width(16)
                .height(a.cnt)
                .borderRadius(3)
                .backgroundColor(a.day === '六' ? '#D4AF37' : '#8D6E63')
              Text(a.day)
                .fontSize(9)
                .fontColor('#8D7B6E')
                .margin({ top: 4 })
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Center)
          }, (a: Attend192) => a.day)
        }
        .width('100%')
        .height(110)
        .alignItems(VerticalAlign.Bottom)
        .margin({ top: 10 })
      }
      .width('100%')
      .padding(14)
      .borderRadius(14)
      .backgroundColor('#FFFFFF')
      .margin({ left: 12, right: 12, top: 12, bottom: 20 })

      Column() {
        Text('热门品类热度')
          .fontSize(13)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Column() {
          ForEach(HOTCATES192, (h: HotCate192) => {
            Row() {
              Text(h.name)
                .fontSize(11)
                .fontColor('#3E2723')
                .width(52)
              Text('')
                .width(h.pct + '%')
                .height(8)
                .borderRadius(4)
                .backgroundColor(h.color)
              Text('')
                .layoutWeight(1)
                .height(8)
                .borderRadius(4)
                .backgroundColor('#F1E8D8')
            }
            .width('100%')
            .margin({ top: 8 })
          }, (h: HotCate192) => h.name)
        }
        .width('100%')
        .margin({ top: 6 })
      }
      .width('100%')
      .padding(14)
      .borderRadius(14)
      .backgroundColor('#FFFFFF')
      .margin({ left: 12, right: 12, bottom: 20 })
    }
    .width('100%')
  }
}

@Component
struct CollectionTab192 {
  lots: Lot192[] = [];
  onEdit: (idx: number) => void = () => {
  };
  onDetail: (idx: number) => void = () => {
  };

  build() {
    Column() {
      Row() {
        Column() {
          Text('藏品 ' + this.lots.length + ' 件')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
          Text('估值合计 ' + collTotal192(this.lots) + ' 万')
            .fontSize(10)
            .fontColor('#B8860B')
            .margin({ top: 4 })
        }
        .alignItems(HorizontalAlign.Start)
        Text('')
          .layoutWeight(1)
        Text('🔐 已开启隐私保护')
          .fontSize(10)
          .fontColor('#8D7B6E')
      }
      .width('100%')
      .padding({ left: 16, right: 16 })
      .margin({ top: 12 })

      Column() {
        ForEach(this.lots, (l: Lot192, i: number) => {
          Column() {
            Row() {
              Text(l.cate.charAt(0))
                .fontSize(20)
                .fontWeight(FontWeight.Bold)
                .fontColor('#FFFFFF')
                .width(56)
                .height(56)
                .textAlign(TextAlign.Center)
                .borderRadius(12)
                .backgroundColor(l.color)
              Column() {
                Row() {
                  Text(l.name)
                    .fontSize(14)
                    .fontWeight(FontWeight.Bold)
                    .fontColor('#3E2723')
                  Text(l.grade)
                    .fontSize(8)
                    .fontColor('#FFFFFF')
                    .padding({ left: 6, right: 6, top: 2, bottom: 2 })
                    .borderRadius(8)
                    .backgroundColor(gradeColor192(l.grade))
                    .margin({ left: 6 })
                }
                Text(l.era + ' · ' + l.cate + ' · 估值 ' + l.value + ' 万')
                  .fontSize(10)
                  .fontColor('#8D7B6E')
                  .margin({ top: 4 })
                Row() {
                  Text(l.verdict)
                    .fontSize(9)
                    .fontColor(verdictColor192(l.verdict))
                    .padding({ left: 8, right: 8, top: 3, bottom: 3 })
                    .borderRadius(8)
                    .backgroundColor('#F7F3EA')
                  Text('热度 ' + l.hot)
                    .fontSize(9)
                    .fontColor('#8D7B6E')
                    .margin({ left: 8 })
                }
                .margin({ top: 6 })
              }
              .layoutWeight(1)
              .margin({ left: 12 })
              .alignItems(HorizontalAlign.Start)
            }
            .width('100%')

            Row() {
              Text('编辑')
                .fontSize(11)
                .fontColor('#5D4037')
                .padding({ left: 16, right: 16, top: 7, bottom: 7 })
                .borderRadius(14)
                .backgroundColor('#F9EED2')
                .onClick(() => {
                  this.onEdit(i);
                })
              Text('详情')
                .fontSize(11)
                .fontColor('#8D7B6E')
                .padding({ left: 16, right: 16, top: 7, bottom: 7 })
                .borderRadius(14)
                .backgroundColor('#F1E8D8')
                .margin({ left: 8 })
                .onClick(() => {
                  this.onDetail(i);
                })
              Text('')
                .layoutWeight(1)
              Text('送拍')
                .fontSize(11)
                .fontColor('#FFFFFF')
                .padding({ left: 16, right: 16, top: 7, bottom: 7 })
                .borderRadius(14)
                .backgroundColor('#5D4037')
                .margin({ left: 8 })
            }
            .width('100%')
            .margin({ top: 12 })
          }
          .width('100%')
          .padding(14)
          .borderRadius(14)
          .backgroundColor('#FFFFFF')
          .margin({ top: 10 })
        }, (l: Lot192) => l.id)
      }
      .width('100%')
      .padding({ left: 12, right: 12 })
      .margin({ top: 10 })

      Column() {
        Text('鉴定结论速览')
          .fontSize(13)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Column() {
          ForEach(VERDICTS192, (v: VerdictRow192) => {
            Row() {
              Text(v.date)
                .fontSize(10)
                .fontColor('#8D7B6E')
                .width(44)
              Column() {
                Text(v.result)
                  .fontSize(12)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(v.color)
                Text(v.lot + ' · ' + v.expert)
                  .fontSize(9)
                  .fontColor('#8D7B6E')
                  .margin({ top: 2 })
              }
              .alignItems(HorizontalAlign.Start)
              .layoutWeight(1)
              Text('置信 ' + v.conf + '%')
                .fontSize(9)
                .fontColor('#B8860B')
            }
            .width('100%')
            .padding({ top: 8, bottom: 8 })
            .borderRadius(10)
            .backgroundColor(v.conf > 92 ? '#FBF4E4' : '#FFFFFF')
            .margin({ top: 6 })
          }, (v: VerdictRow192) => v.id)
        }
        .width('100%')
        .margin({ top: 6 })
      }
      .width('100%')
      .padding(14)
      .borderRadius(14)
      .backgroundColor('#FFFFFF')
      .margin({ left: 12, right: 12, top: 16, bottom: 20 })
    }
    .width('100%')
  }
}

function collTotal192(lots: Lot192[]): number {
  let sum: number = 0;
  for (let i = 0; i < lots.length; i++) {
    sum += lots[i].value;
  }
  return sum;
}

@Component
struct OrderTab192 {
  orders: Session192[] = [];
  onSend: () => void = () => {
  };
  onDel: (idx: number) => void = () => {
  };

  build() {
    Column() {
      Row() {
        Column() {
          Text('我的送鉴单')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#3E2723')
          Text('待鉴定 ' + this.orders.length + ' 单')
            .fontSize(10)
            .fontColor('#8D7B6E')
            .margin({ top: 4 })
        }
        .alignItems(HorizontalAlign.Start)
        Text('')
          .layoutWeight(1)
        Text('+ 新增送鉴')
          .fontSize(12)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
          .padding({ left: 14, right: 14, top: 9, bottom: 9 })
          .borderRadius(16)
          .backgroundColor('#5D4037')
          .onClick(() => {
            this.onSend();
          })
      }
      .width('100%')
      .padding({ left: 16, right: 16 })
      .margin({ top: 12 })

      Column() {
        ForEach(this.orders, (o: Session192, i: number) => {
          Column() {
            Row() {
              Column() {
                Text(o.date.split('-')[1])
                  .fontSize(18)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#FFFFFF')
                Text(o.week)
                  .fontSize(9)
                  .fontColor('#E8DCC8')
              }
              .width(52)
              .height(52)
              .justifyContent(FlexAlign.Center)
              .borderRadius(10)
              .backgroundColor('#5D4037')

              Column() {
                Text(o.theme)
                  .fontSize(13)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#3E2723')
                Text(o.time + ' · ' + o.expert + ' · 已约 ' + o.joined + '/' + o.seats)
                  .fontSize(10)
                  .fontColor('#8D7B6E')
                  .margin({ top: 4 })
                Row() {
                  Text(o.state)
                    .fontSize(9)
                    .fontColor(stateColor192(o.state))
                    .padding({ left: 8, right: 8, top: 3, bottom: 3 })
                    .borderRadius(8)
                    .backgroundColor('#F7F3EA')
                }
                .margin({ top: 6 })
              }
              .layoutWeight(1)
              .margin({ left: 12 })
              .alignItems(HorizontalAlign.Start)

              Column() {
                Text('撤销')
                  .fontSize(10)
                  .fontColor('#C62828')
                  .padding({ left: 12, right: 12, top: 6, bottom: 6 })
                  .borderRadius(12)
                  .backgroundColor('#FBE9E7')
                  .onClick(() => {
                    this.onDel(i);
                  })
                Text('改期')
                  .fontSize(10)
                  .fontColor('#8D7B6E')
                  .padding({ left: 12, right: 12, top: 6, bottom: 6 })
                  .borderRadius(12)
                  .backgroundColor('#F1E8D8')
                  .margin({ top: 6 })
              }
            }
            .width('100%')

            Row() {
              Text('当前排位 #' + (i + 3))
                .fontSize(9)
                .fontColor('#B8860B')
              Text('')
                .layoutWeight(1)
              Text('预计上场 ' + (20 + i * 15) + ':' + (i === 0 ? '40' : '10'))
                .fontSize(9)
                .fontColor('#8D7B6E')
            }
            .width('100%')
            .margin({ top: 10 })
          }
          .width('100%')
          .padding(14)
          .borderRadius(14)
          .backgroundColor('#FFFFFF')
          .margin({ top: 10 })
        }, (o: Session192) => o.id)
      }
      .width('100%')
      .padding({ left: 12, right: 12 })
      .margin({ top: 10 })

      Column() {
        Text('送鉴须知')
          .fontSize(13)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Column() {
          ForEach(['1. 贵重藏品建议购买运输保险并保价', '2. 连麦鉴定请提前备好微距图与款识特写', '3. 后仿品可申请复鉴一次,费用减半', '4. 直播言论仅代表行家个人学术观点'], (tip: string) => {
            Row() {
              Text('·')
                .fontSize(12)
                .fontColor('#D4AF37')
              Text(tip)
                .fontSize(11)
                .fontColor('#8D7B6E')
                .margin({ left: 6 })
            }
            .width('100%')
            .margin({ top: 6 })
          }, (tip: string) => tip)
        }
        .width('100%')
        .margin({ top: 6 })
      }
      .width('100%')
      .padding(14)
      .borderRadius(14)
      .backgroundColor('#FBF4E4')
      .margin({ left: 12, right: 12, top: 16, bottom: 20 })
    }
    .width('100%')
  }
}

@Component
struct ExpertTab192 {
  build() {
    Column() {
      Row() {
        Text('入驻行家团')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Text('')
          .layoutWeight(1)
        Text('共 ' + EXPERTS192.length + ' 位')
          .fontSize(10)
          .fontColor('#8D7B6E')
      }
      .width('100%')
      .padding({ left: 16, right: 16 })
      .margin({ top: 12 })

      Column() {
        ForEach(EXPERTS192, (e: Expert192) => {
          Row() {
            Stack() {
              Text(e.emoji)
                .fontSize(24)
                .width(52)
                .height(52)
                .textAlign(TextAlign.Center)
                .borderRadius(26)
                .backgroundColor('#F9EED2')
              Text('●')
                .fontSize(10)
                .fontColor(e.online ? '#4CAF50' : '#BDBDBD')
                .position({ x: 38, y: 36 })
            }
            .width(52)
            .height(52)

            Column() {
              Row() {
                Text(e.name)
                  .fontSize(14)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#3E2723')
                Text(e.field)
                  .fontSize(8)
                  .fontColor('#FFFFFF')
                  .padding({ left: 6, right: 6, top: 2, bottom: 2 })
                  .borderRadius(8)
                  .backgroundColor('#5D4037')
                  .margin({ left: 6 })
                Text('')
                  .layoutWeight(1)
                Text(e.online ? '在线' : '离线')
                  .fontSize(9)
                  .fontColor(e.online ? '#4CAF50' : '#BDBDBD')
              }
              .width('100%')
              Text(e.title)
                .fontSize(10)
                .fontColor('#8D7B6E')
                .margin({ top: 4 })
              Row() {
                Text('口碑 ' + e.rating)
                  .fontSize(10)
                  .fontColor('#B8860B')
                Text('主持 ' + e.sessions + ' 场')
                  .fontSize(10)
                  .fontColor('#8D7B6E')
                  .margin({ left: 12 })
                Text('')
                  .layoutWeight(1)
                Text('提问')
                  .fontSize(10)
                  .fontColor('#5D4037')
                  .padding({ left: 12, right: 12, top: 5, bottom: 5 })
                  .borderRadius(12)
                  .backgroundColor('#F9EED2')
              }
              .width('100%')
              .margin({ top: 6 })
            }
            .layoutWeight(1)
            .margin({ left: 12 })
            .alignItems(HorizontalAlign.Start)
          }
          .width('100%')
          .padding(14)
          .borderRadius(14)
          .backgroundColor('#FFFFFF')
          .margin({ top: 10 })
        }, (e: Expert192) => e.id)
      }
      .width('100%')
      .padding({ left: 12, right: 12 })
      .margin({ top: 10 })

      Column() {
        Text('行家连麦率')
          .fontSize(13)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Column() {
          ForEach(EXPERTS192, (e: Expert192) => {
            Row() {
              Text(e.name)
                .fontSize(11)
                .fontColor('#3E2723')
                .width(52)
              Text('')
                .width(e.rating * 18)
                .height(8)
                .borderRadius(4)
                .backgroundColor('#D4AF37')
              Text(e.rating + '')
                .fontSize(9)
                .fontColor('#8D7B6E')
                .margin({ left: 8 })
              Text('')
                .layoutWeight(1)
            }
            .width('100%')
            .margin({ top: 8 })
          }, (e: Expert192) => e.id)
        }
        .width('100%')
        .margin({ top: 6 })
      }
      .width('100%')
      .padding(14)
      .borderRadius(14)
      .backgroundColor('#FFFFFF')
      .margin({ left: 12, right: 12, top: 16, bottom: 20 })
    }
    .width('100%')
  }
}

@Component
struct ScheduleTab192 {
  onLianmai: () => void = () => {
  };

  build() {
    Column() {
      Row() {
        Text('直播档期')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Text('')
          .layoutWeight(1)
        Text('未来 7 天')
          .fontSize(10)
          .fontColor('#8D7B6E')
      }
      .width('100%')
      .padding({ left: 16, right: 16 })
      .margin({ top: 12 })

      Column() {
        ForEach(SESSIONS192, (s: Session192) => {
          Row() {
            Column() {
              Text(s.date.split('-')[1] + '日')
                .fontSize(15)
                .fontWeight(FontWeight.Bold)
                .fontColor(s.state === '已满员' ? '#BDBDBD' : '#5D4037')
              Text(s.week)
                .fontSize(9)
                .fontColor('#8D7B6E')
            }
            .width(48)
            .alignItems(HorizontalAlign.Center)

            Column() {
              Text(s.theme)
                .fontSize(13)
                .fontWeight(FontWeight.Bold)
                .fontColor('#3E2723')
              Text(s.time + ' · ' + s.expert)
                .fontSize(10)
                .fontColor('#8D7B6E')
                .margin({ top: 4 })
              Row() {
                Text('')
                  .width((s.joined / s.seats) * 100 + '%')
                  .height(4)
                  .borderRadius(2)
                  .backgroundColor(s.state === '已满员' ? '#C62828' : '#D4AF37')
              }
              .width('100%')
              .margin({ top: 6 })
              Text(s.joined + '/' + s.seats + ' 席位')
                .fontSize(9)
                .fontColor('#8D7B6E')
                .margin({ top: 4 })
            }
            .layoutWeight(1)
            .margin({ left: 12 })
            .alignItems(HorizontalAlign.Start)

            Text(s.state === '已满员' ? '已满' : '预约')
              .fontSize(11)
              .fontColor(s.state === '已满员' ? '#BDBDBD' : '#FFFFFF')
              .padding({ left: 14, right: 14, top: 8, bottom: 8 })
              .borderRadius(14)
              .backgroundColor(s.state === '已满员' ? '#F1E8D8' : '#5D4037')
              .onClick(() => {
                if (s.state !== '已满员') {
                  this.onLianmai();
                }
              })
          }
          .width('100%')
          .padding(14)
          .borderRadius(14)
          .backgroundColor('#FFFFFF')
          .margin({ top: 10 })
        }, (s: Session192) => s.id)
      }
      .width('100%')
      .padding({ left: 12, right: 12 })
      .margin({ top: 10 })

      Column() {
        Text('本周场次热度')
          .fontSize(13)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Row() {
          ForEach(ATTEND192, (a: Attend192) => {
            Column() {
              Text(a.cnt + '')
                .fontSize(8)
                .fontColor('#8D7B6E')
              Text('')
                .width(16)
                .height(a.cnt)
                .borderRadius(3)
                .backgroundColor('#5D4037')
              Text(a.day)
                .fontSize(9)
                .fontColor('#8D7B6E')
                .margin({ top: 4 })
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Center)
          }, (a: Attend192) => a.day)
        }
        .width('100%')
        .height(130)
        .alignItems(VerticalAlign.Bottom)
        .margin({ top: 10 })
      }
      .width('100%')
      .padding(14)
      .borderRadius(14)
      .backgroundColor('#FFFFFF')
      .margin({ left: 12, right: 12, top: 16, bottom: 20 })
    }
    .width('100%')
  }
}

@Component
struct MineTab192 {
  records: Record192[] = [];
  onDetail: () => void = () => {
  };

  build() {
    Column() {
      Column() {
        Row() {
          Text('🧑‍🎨')
            .fontSize(30)
            .width(60)
            .height(60)
            .textAlign(TextAlign.Center)
            .borderRadius(30)
            .backgroundColor('#4DFFFFFF')
          Column() {
            Text('金陵藏家·陆先生')
              .fontSize(16)
              .fontWeight(FontWeight.Bold)
              .fontColor('#FFFFFF')
            Text('藏龄 12 年 · 藏品 38 件')
              .fontSize(10)
              .fontColor('#E8DCC8')
              .margin({ top: 4 })
          }
          .margin({ left: 12 })
          .alignItems(HorizontalAlign.Start)
          Text('')
            .layoutWeight(1)
          Text('Lv.6 鉴宝行家')
            .fontSize(10)
            .fontColor('#3E2723')
            .padding({ left: 10, right: 10, top: 5, bottom: 5 })
            .borderRadius(12)
            .backgroundColor('#D4AF37')
        }
        .width('100%')

        Row() {
          Column() {
            Text('128')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor('#FFD54F')
            Text('参与场次')
              .fontSize(9)
              .fontColor('#E8DCC8')
              .margin({ top: 2 })
          }
          .layoutWeight(1)
          Column() {
            Text('31')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor('#FFD54F')
            Text('真品藏品')
              .fontSize(9)
              .fontColor('#E8DCC8')
              .margin({ top: 2 })
          }
          .layoutWeight(1)
          Column() {
            Text('7')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor('#FFD54F')
            Text('后仿捡漏')
              .fontSize(9)
              .fontColor('#E8DCC8')
              .margin({ top: 2 })
          }
          .layoutWeight(1)
          Column() {
            Text('642')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor('#FFD54F')
            Text('获赞')
              .fontSize(9)
              .fontColor('#E8DCC8')
              .margin({ top: 2 })
          }
          .layoutWeight(1)
        }
        .width('100%')
        .margin({ top: 16 })
      }
      .width('100%')
      .padding(18)
      .borderRadius(16)
      .linearGradient({
        angle: 135,
        colors: [['#5D4037', 0], ['#3E2723', 1]]
      })
      .margin({ left: 12, right: 12, top: 12 })

      Row() {
        Text('我的鉴定足迹')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Text('')
          .layoutWeight(1)
        Text('查看藏品')
          .fontSize(10)
          .fontColor('#B8860B')
          .onClick(() => {
            this.onDetail();
          })
      }
      .width('100%')
      .padding({ left: 16, right: 16 })
      .margin({ top: 16 })

      Column() {
        ForEach(this.records, (r: Record192) => {
          Row() {
            Column() {
              Text('●')
                .fontSize(10)
                .fontColor(r.color)
              Text('')
                .width(2)
                .layoutWeight(1)
                .backgroundColor('#EFE7D8')
            }
            .width(16)
            .height(56)

            Column() {
              Text(r.lot)
                .fontSize(13)
                .fontWeight(FontWeight.Bold)
                .fontColor('#3E2723')
              Text(r.result)
                .fontSize(10)
                .fontColor(r.color)
                .margin({ top: 3 })
              Text(r.date)
                .fontSize(9)
                .fontColor('#8D7B6E')
                .margin({ top: 3 })
            }
            .layoutWeight(1)
            .margin({ left: 10 })
            .alignItems(HorizontalAlign.Start)

            Text('鉴定书')
              .fontSize(10)
              .fontColor('#5D4037')
              .padding({ left: 12, right: 12, top: 6, bottom: 6 })
              .borderRadius(12)
              .backgroundColor('#F9EED2')
          }
          .width('100%')
          .padding(12)
          .borderRadius(12)
          .backgroundColor('#FFFFFF')
          .margin({ top: 8 })
        }, (r: Record192) => r.id)
      }
      .width('100%')
      .padding({ left: 12, right: 12 })
      .margin({ top: 10 })

      Column() {
        Text('我的鉴宝成就')
          .fontSize(13)
          .fontWeight(FontWeight.Bold)
          .fontColor('#3E2723')
        Row() {
          ForEach(['🏆 首件珍品', '👁️ 火眼金睛', '📜 十场连麦', '🧧 捡漏达人', '🎓 进阶学员'], (badge: string) => {
            Column() {
              Text(badge.split(' ')[0])
                .fontSize(20)
              Text(badge.split(' ')[1])
                .fontSize(8)
                .fontColor('#8D7B6E')
                .margin({ top: 4 })
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Center)
            .padding({ top: 10, bottom: 10 })
            .borderRadius(10)
            .backgroundColor('#F9EED2')
          }, (badge: string) => badge)
        }
        .width('100%')
        .margin({ top: 10 })
      }
      .width('100%')
      .padding(14)
      .borderRadius(14)
      .backgroundColor('#FFFFFF')
      .margin({ left: 12, right: 12, top: 16, bottom: 20 })
    }
    .width('100%')
  }
}


总结

在这里插入图片描述

好,今天这期节目我们拆解了一个非常完整的鉴宝直播应用。从数据模型到 UI 布局,从状态管理到弹层交互,这个应用展示了一个复杂业务场景在 ArkTS 声明式 UI 范式下的完整实现路径。最让我印象深刻的是它的"展柜"设计语言——从底部导航的金线暖光,到顶部状态栏的三段青铜渐变,到四宫格连麦窗口的同色系明度变化,整个应用在视觉上保持了高度统一的"博物馆展柜"美学。这不是随意的配色,而是业务场景(鉴宝)与视觉语言(展柜)的深度绑定。

从技术实现来看,应用采用了"状态上提 + 子组件代理"的分层架构。主组件 Index192 持有所有可变状态(myLotsmyOrders 以及各种表单临时状态),六个子 Tab 组件通过构造参数接收数据和回调。数据操作严格遵循不可变更新原则——增用 concat、删用 filter、改用 map——确保 @State 响应式系统能正确检测到变化。弹层交互分为 bindSheet(抽屉式,用于日常表单)和 bindContentCover(全屏遮罩,用于关键确认),两者各司其职。

最后说一个我觉得可以改进的地方:LiveTab192 中的点赞状态 liked: boolean[] 直接在 onClick 中用 this.liked[i] = !this.liked[i] 修改元素,这在某些版本的 ArkTS 中可能不触发响应式更新。更安全的做法是用 map 生成新数组。不过整体来说,这份代码在架构设计、视觉一致性、交互分层方面都做得相当成熟,是一个非常值得学习的 HarmonyOS 应用开发范例。好,今天的节目就到这里,我们下期再见!

Logo

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

更多推荐