引言

在这里插入图片描述

办公文具作为电商市场中B端属性最强的品类之一,其移动端应用开发面临着C端零售与B端采购的双重业务需求。与普通消费品不同,办公用品的采购涉及企业批量询价、增值税开票、月结账期管理、员工领用台账等专业流程,同时还需兼顾学生和个人的零售购买场景。本应用以京东办公文具馆为业务原型,基于HarmonyOS声明式UI开发框架,构建了一个涵盖文具集市、书写工具、办公纸品、桌面收纳、企业采购、消息中心和用户中心七大功能模块的完整电商系统。

从技术架构层面来看,该应用在前两个应用的单页面多标签架构基础上,进一步丰富了弹窗交互的维度。应用引入了批量采购询价弹窗(showBulk)和开票申请弹窗(showInvoice)两个B端专属弹窗,使得弹窗总数达到五个。批量采购弹窗的采购数量采用了步进10的递增递减设计(范围10-5000),体现了企业采购的批量特征。开票弹窗集成了发票类型选择(电子普票、纸质普票、增值税专票)和发票抬头选择(个人、企业、单位),覆盖了企业采购中最核心的开票场景。所有商品数据同样通过interface接口进行类型约束,GoodsItemnum字段在文具场景中表示笔尖规格或纸张克重,展现了数据模型设计的持续复用能力。

从业务设计层面来看,该应用深度还原了办公文具行业的核心业务流程。头部区域展示开学季促销信息和企业会员专属折扣,通过双层级优惠吸引用户;书写工具页面引入笔尖规格柱状图,通过可视化手段帮助用户理解不同笔尖的粗细差异;办公纸品页面使用纸张克重分布柱状图和复购进度条列表,为办公用品的批量采购提供了数据支撑。企业采购模块是应用的核心特色,展示了综合好评率、服务企业数量和最快账期等关键指标,并提供从定制文具到考勤机的完整集采服务目录。

从交互设计层面来看,应用在文本处理上大量使用了slice方法进行字符串截取。例如"次日达 · 破损包退".slice(0, 10)、"好评率 ".slice(0, 1)等,这种技术在移动端UI中用于控制文本长度和适配不同屏幕宽度。此外,桌面收纳页面的好评分段柱状图采用了(g.score - 85) / 10 * 90的计算公式,将评分从85分基线开始分段显示,使得90分和95分的差异在柱状图中更加明显,体现了数据可视化中的细节优化思维。

数据模型与接口定义

在这里插入图片描述

应用通过三个接口定义了核心数据模型,与前面两个应用保持了一致的结构设计。GoodsItem接口描述商品实体,TabInfo接口定义导航标签,MsgItem接口规范消息通知结构。这种统一的接口设计使得三个应用共享同一套数据驱动逻辑,体现了良好的架构复用性。

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

interface TabInfo {
  name: string
  icon: string

interface MsgItem {
  title: string
  content: string
  time: string
  read: boolean
}

在文具应用中,GoodsItemnum字段承担了新的业务语义:在书写工具列表中表示笔尖规格数值(如5表示0.5mm、4表示0.4mm),在办公纸品列表中表示纸张克重(如70表示70g、80表示80g),在桌面收纳列表中复用为评分基数。tag字段在书写工具中存储了笔尖规格的完整描述(如"笔尖0.5mm"),通过g.tag.slice(2, 7)可以提取出"0.5mm"部分用于柱状图标注。

状态管理与组件初始化

在这里插入图片描述

根组件Index通过@State装饰器声明了响应式状态变量。与前两个应用相比,文具应用引入了采购数量(bulkNum)、发票类型选择(selInvType)和发票抬头选择(selTitle)三个B端业务专属状态。

@Entry
@Component
struct Index {
  @State currentTab: number = 0
  @State showPublish: boolean = false
  @State showBulk: boolean = false
  @State showInvoice: boolean = false
  @State showDelete: boolean = false
  @State showDetail: boolean = false
  @State selectedItem: GoodsItem | null = null
  @State selCat: number = 0
  @State selUse: number = 0
  @State selSpec: number = 0
  @State bulkNum: number = 100
  @State selInvType: number = 0
  @State selTitle: number = 0
  @State deleteIndex: number = -1
  @State msgs: MsgItem[] = [
    { title: '采购审批通过', content: '您提交的企业采购单已审批通过,预计 2 日内发货', time: '10:36', read: false },
    { title: '开票通知', content: '订单尾号 8821 的电子发票已开具,请查收邮箱', time: '09:48', read: false },
    { title: '补货提醒', content: '您常购的 A4 纸库存不足一箱,建议及时补货', time: '昨天', read: false },
    { title: '物流更新', content: '您的文具整箱已从广州仓发出,预计后天送达', time: '昨天', read: true },
    { title: '企业专属价', content: '本月企业会员价已更新,打印机耗材直降 15%', time: '前天', read: true },
    { title: '对账单生成', content: '8 月采购对账单已生成,可在企业中心下载', time: '前天', read: true },
    { title: '新品上架', content: '进口中性笔新款到货,0.5mm 针管头书写顺滑', time: '周三', read: true },
    { title: '直播预告', content: '明晚 19:00 办公焕新专场直播,整点抽打印机', time: '周三', read: true },
    { title: '优惠券到账', content: '企业采购满 1000 减 120 券已发放,30 天有效', time: '周四', read: true },
    { title: '评价有礼', content: '评价采购的办公椅可领 30 元无门槛券', time: '上周', read: true },
    { title: '以旧换新', content: '旧打印机折价回收,最高抵 800 元', time: '上周', read: true },
    { title: '安全提醒', content: '谨防冒充供应商的变更收款账号诈骗', time: '上周', read: true }
  ]

消息列表的内容深度体现了B端业务特征——包含采购审批通过、开票通知、补货提醒、对账单生成等企业采购专属通知。bulkNum的初始值为100,对应企业采购的典型起订量。selInvTypeselTitle分别用于开票弹窗中的发票类型和抬头选择,初始值均为0表示默认选择第一项。

私有数据与商品列表

在这里插入图片描述

组件内部通过private声明了配置选项和五组商品数据。配置选项包括书写工具品类、适用场景、规格选择、发票类型和发票抬头五组数组。商品数据按品类分为综合精选items、书写工具pens、办公纸品papers、桌面收纳desks和企业采购corps五组。

  private tabs: TabInfo[] = [
    { name: '文具集市', icon: '✏️' },
    { name: '书写工具', icon: '🖊️' },
    { name: '办公纸品', icon: '📄' },
    { name: '桌面收纳', icon: '🗄️' },
    { name: '企业采购', icon: '🏢' },
    { name: '消息', icon: '✉️' },
    { name: '我的', icon: '👤' }
  ]
  private cats: string[] = ['中性笔', '钢笔', '荧光笔', '铅笔', '马克笔', '白板笔']
  private uses: string[] = ['学生刷题', '办公签字', '教师批改', '绘画手账']
  private specs: string[] = ['0.38mm 黑', '0.5mm 黑', '0.5mm 蓝', '12 色混装']
  private invTypes: string[] = ['电子普票', '纸质普票', '增值税专票']
  private titles: string[] = ['个人抬头', '企业抬头', '单位抬头']

  private items: GoodsItem[] = [
    { name: '百乐P500中性笔0.5', price: 9, desc: '直液式 大容量顺滑', score: 96, num: 5, tag: '刷题神器', hot: true },
    { name: '三菱UM-151中性笔', price: 8, desc: '低粘度油墨 快干', score: 95, num: 5, tag: '经典款', hot: false },
    { name: '晨光优品按动笔', price: 3, desc: '国货性价比 顺滑不断墨', score: 93, num: 5, tag: '性价比', hot: true },
    { name: '百乐78G+钢笔', price: 68, desc: 'F尖 磨砂手感 练字', score: 95, num: 3, tag: '入门钢笔', hot: false },
    { name: '写乐四季织钢笔', price: 328, desc: '透明杆 15度尖', score: 96, num: 3, tag: '进阶款', hot: false },
    { name: '凌美狩猎者钢笔', price: 79, desc: '德系经典 ABS笔杆', score: 94, num: 3, tag: '德系', hot: true },
    { name: '斑马JJ15复古色', price: 7, desc: '复古五色 手账配色', score: 94, num: 5, tag: '手账', hot: false },
    { name: '无印双头荧光笔', price: 12, desc: '不透纸 温和色系', score: 93, num: 5, tag: '划重点', hot: false },
    { name: '施德楼马克笔套装', price: 128, desc: '专业设计 24色', score: 95, num: 5, tag: '设计师', hot: false },
    { name: '辉柏嘉水彩铅笔', price: 89, desc: '36色 水溶晕染', score: 94, num: 5, tag: '绘画', hot: false },
    { name: '得力A4复印纸整箱', price: 118, desc: '70g 500张×8包', score: 95, num: 70, tag: '办公刚需', hot: true },
    { name: '晨光笔记本B5本', price: 6, desc: '康奈尔内页 加厚', score: 93, num: 5, tag: '学生党', hot: false },
    { name: '国誉Campus笔记本', price: 15, desc: '东大脑训练格', score: 95, num: 5, tag: '进口', hot: false },
    { name: '得力订书机大号', price: 25, desc: '可订50页 省力', score: 92, num: 5, tag: '办公', hot: false },
    { name: '得力文件夹加厚', price: 4, desc: 'A4双强夹 透明', score: 91, num: 5, tag: '归档', hot: false },
    { name: '桌面文件收纳架', price: 45, desc: '三层分格 竖立收纳', score: 93, num: 5, tag: '收纳', hot: true },
    { name: '透明抽屉收纳盒', price: 35, desc: '五层小抽屉', score: 92, num: 5, tag: '桌面', hot: false },
    { name: '便签纸组合装', price: 16, desc: '76×76 四色各一本', score: 91, num: 5, tag: '便签', hot: false },
    { name: '白板磁吸大号', price: 129, desc: '90×120cm 免打孔', score: 94, num: 5, tag: '会议室', hot: false },
    { name: '人体工学办公椅', price: 899, desc: '腰托可调 网布透气', score: 95, num: 5, tag: '久坐党', hot: true },
    { name: '升降桌电动款', price: 1599, desc: '记忆高度 双电机', score: 96, num: 5, tag: '站坐交替', hot: false },
    { name: '桌面理线器磁吸', price: 19, desc: '告别线材凌乱', score: 90, num: 5, tag: '神器', hot: false },
    { name: '洞洞板桌面置物', price: 59, desc: '自由组合 收纳上墙', score: 92, num: 5, tag: '改造', hot: false },
    { name: '墨囊补给装100枚', price: 22, desc: '通用型 黑蓝红', score: 93, num: 5, tag: '耗材', hot: false }
  ]

综合商品列表覆盖了从3元的晨光按动笔到1599元的电动升降桌,价格梯度极大,兼顾了学生个人用户和企业采购用户。书写工具列表penstag字段存储了完整的笔尖规格信息(如"笔尖0.5mm"),这使得柱状图标注可以通过slice方法提取规格数值。企业采购列表corpsnum字段用于"已合作 X 家"的计算(g.num * 36),通过乘以固定系数模拟了服务企业数量的累加效果。

头部构建与双层级促销

在这里插入图片描述

buildHeader方法构建了应用顶部导航区域,采用了深蓝色#283593作为主背景色,传达了办公用品的专业商务感。头部整合了品牌标识、企业采购服务标签、开票入口和消息中心入口,底部还展示了开学季促销信息条。

  @Builder buildHeader() {
    Column() {
      Row() {
        Column() {
          Text('京东办公文具馆').fontSize(18).fontColor(Color.White).fontWeight(FontWeight.Bold)
          Row() {
            Text('🏢').fontSize(11)
            Text('企业采购 · 对公转账 · 增值税开票').fontSize(10).fontColor('rgba(255,255,255,0.8)').margin({ left: 4 })
          }
          .margin({ top: 4 })
          .alignItems(VerticalAlign.Center)
        }
        .alignItems(HorizontalAlign.Start)
        .layoutWeight(1)

        Column() {
          Text('📄').fontSize(18)
          Text('开票').fontSize(9).fontColor('rgba(255,255,255,0.8)').margin({ top: 2 })
        }
        .width(44)
        .onClick(() => {
          this.selInvType = 0
          this.selTitle = 0
          this.showInvoice = true
        })

        Column() {
          Text('✉️').fontSize(18)
          if (this.unreadCount() > 0) {
            Text(this.unreadCount().toString())
              .fontSize(8)
              .fontColor(Color.White)
              .backgroundColor('#FF3B30')
              .borderRadius(7)
              .padding({ left: 4, right: 4 })
              .position({ x: 12, y: -4 })
              .scale({ x: 1.12, y: 1.12 })
              .animation({ duration: 650, iterations: -1, curve: Curve.EaseInOut })
          }
        }
        .width(44)
        .onClick(() => {
          this.currentTab = 5
        })
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 12, bottom: 12 })
      .alignItems(VerticalAlign.Center)

      Row() {
        Column() {
          Text('开学季 · 文具 5 折起').fontSize(11).fontColor('#E3F2FD')
          Text('企业会员再享折上 95 折').fontSize(10).fontColor('rgba(255,255,255,0.75)').margin({ top: 2 })
        }
        .alignItems(HorizontalAlign.Start)

        Column() {
          Text('领券').fontSize(11).fontColor('#283593').backgroundColor('#FFFFFF').borderRadius(12).padding({ left: 12, right: 12, top: 4, bottom: 4 })
            .scale({ x: 1.05, y: 1.05 })
            .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
        }
        .margin({ left: 12 })
      }
      .width('100%')
      .padding({ left: 16, right: 16, bottom: 12 })
      .justifyContent(FlexAlign.Start)
      .alignItems(VerticalAlign.Center)
    }
    .width('100%')
    .backgroundColor('#283593')
  }

与前两个应用的显著区别在于:头部右侧的第一个快捷入口是"开票"而非品类图标,直接突出B端开票业务的重要性。点击开票入口时会重置selInvTypeselTitle为0并打开开票弹窗。促销信息条采用了双层级设计——上层"开学季 · 文具 5 折起"面向C端学生用户,下层"企业会员再享折上 95 折"面向B端企业用户,通过信息分层同时服务两类用户群体。"领券"按钮使用白底蓝字设计,与深蓝头部形成对比,配有1.05倍脉冲动画引导点击。

文具集市与开学季活动

在这里插入图片描述

buildTab1方法构建了文具集市的商品列表页。页面顶部是开学焕新季活动横幅,配有"368万+师生已选购"的社交证明数据。下方商品列表使用了铅笔、钢笔、纸张和收纳柜四种表情符号循环交替,热销标签显示为"回购率高",配合脉冲动画吸引用户关注。

  @Builder buildTab1() {
    Column() {
      Row() {
        Column() {
          Text('📚').fontSize(28)
          Column() {
            Text('开学焕新季').fontSize(15).fontColor(Color.White).fontWeight(FontWeight.Bold)
            Text('文具 5 折 · 笔记本买二送一').fontSize(10).fontColor('rgba(255,255,255,0.8)').margin({ top: 3 })
          }
          .alignItems(HorizontalAlign.Start)
          .margin({ left: 10 })
        }
        .alignItems(HorizontalAlign.Center)
        .layoutWeight(1)

        Column() {
          Text('368万+').fontSize(15).fontColor('#FF7043').fontWeight(FontWeight.Bold)
          Text('师生已选购').fontSize(9).fontColor('rgba(255,255,255,0.7)').margin({ top: 3 })
            .scale({ x: 1.05, y: 1.05 })
            .animation({ duration: 800, iterations: -1, curve: Curve.EaseInOut })
        }
        .alignItems(HorizontalAlign.End)
      }
      .width('100%')
      .backgroundColor('#3949AB')
      .borderRadius(14)
      .padding(16)
      .margin({ left: 12, right: 12, top: 12 })
      .alignItems(VerticalAlign.Center)

      Row() {
        Text('文具精选').fontSize(15).fontColor('#212121').fontWeight(FontWeight.Bold)
        Text('次日达 · 破损包退'.slice(0, 10)).fontSize(10).fontColor('#9AA0B5').margin({ left: 8 })
      }
      .width('100%')
      .padding({ left: 16, top: 14, bottom: 8 })
      .justifyContent(FlexAlign.Start)
      .alignItems(VerticalAlign.Center)

      ForEach(this.items, (g: GoodsItem, i: number) => {
        Row() {
          Column() {
            Text(i % 4 === 0 ? '✏️' : (i % 4 === 1 ? '🖊️' : (i % 4 === 2 ? '📄' : '🗄️'))).fontSize(26)
          }
          .width(72)
          .height(72)
          .borderRadius(12)
          .backgroundColor(i % 2 === 0 ? '#E8EAF6' : '#E3F2FD')
          .justifyContent(FlexAlign.Center)

          Column() {
            Text(g.name).fontSize(13).fontColor('#212121').fontWeight(FontWeight.Medium).maxLines(1)
            Text(g.desc).fontSize(10).fontColor('#8A8FA3').margin({ top: 3 }).maxLines(1)

            Row() {
              Text(g.tag).fontSize(9).fontColor('#283593').backgroundColor('#E8EAF6').borderRadius(4).padding({ left: 5, right: 5, top: 2, bottom: 2 })
              if (g.hot) {
                Text('回购率高'.slice(0, 4)).fontSize(9).fontColor(Color.White).backgroundColor('#FF3B30').borderRadius(4).padding({ left: 5, right: 5, top: 2, bottom: 2 }).margin({ left: 6 })
                  .scale({ x: 1.06, y: 1.06 })
                  .animation({ duration: 650, iterations: -1, curve: Curve.EaseInOut })
              }
            }
            .margin({ top: 5 })
            .alignItems(VerticalAlign.Center)

            Stack({ alignContent: Alignment.Start }) {
              Column().width('100%').height(6).backgroundColor('#F0F0F5').borderRadius(3)
              Column().width(g.score + '%').height(6).backgroundColor('#FF7043').borderRadius(3)
            }
            .width('100%')
            .margin({ top: 6 })
          }
          .layoutWeight(1)
          .alignItems(HorizontalAlign.Start)
          .margin({ left: 10 })

          Column() {
            Text('¥' + g.price).fontSize(16).fontColor('#D84315').fontWeight(FontWeight.Bold)
            Column() {
              Text('批量采购').fontSize(10).fontColor(Color.White)
            }
            .backgroundColor('#283593')
            .borderRadius(13)
            .padding({ left: 10, right: 10, top: 5, bottom: 5 })
            .margin({ top: 8 })
            .onClick(() => {
              this.selectedItem = g
              this.bulkNum = 100
              this.showBulk = true
            })
          }
          .alignItems(HorizontalAlign.End)
        }
        .width('100%')
        .backgroundColor(Color.White)
        .borderRadius(12)
        .padding(12)
        .margin({ left: 12, right: 12, bottom: 10 })
        .alignItems(VerticalAlign.Center)
        .onClick(() => {
          this.selectedItem = g
          this.showDetail = true
        })
      }, (g: GoodsItem) => g.name)
    }
    .width('100%')
    .padding({ bottom: 12 })
  }

活动横幅使用了比头部更浅的蓝色#3949AB,形成同色系的层次感。"368万+师生已选购"的社交证明数据配有1.05倍脉冲动画,通过数据背书增强用户信任。商品标签使用蓝色#283593背景而非前两个应用的品类色,评分进度条使用橙色#FF7043填充。价格文字使用深橙色#D84315而非前两个应用的红色#B71C1C,整体色彩偏向冷色调商务风格。"批量采购"按钮点击后会重置采购数量为100(企业采购典型起订量)并打开批量询价弹窗。

书写工具与笔尖规格可视化

在这里插入图片描述

buildTab2方法构建了书写工具页面,核心特色是笔尖规格柱状图和双列精选列表。柱状图使用深蓝色#283593柱体展示各笔尖的规格数值,标签通过g.tag.slice(2, 7)从"笔尖0.5mm"格式中提取出"0.5mm"部分。

  @Builder buildTab2() {
    Column() {
      Text('笔尖规格柱状图(mm×10)').fontSize(15).fontColor('#212121').fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ left: 16, top: 14, bottom: 8 })

      Column() {
        Row({ space: 10 }) {
          ForEach(this.pens, (g: GoodsItem) => {
            Column() {
              Column()
                .width(16)
                .height(g.num / this.maxOfNum(this.pens) * 100)
                .backgroundColor('#283593')
                .borderRadius(4)
              Text(g.tag.slice(2, 7)).fontSize(7).fontColor('#8A8FA3').margin({ top: 3 }).maxLines(1)
            }
            .alignItems(HorizontalAlign.Start)
          }, (g: GoodsItem) => g.name)
        }
        .width('100%')
        .alignItems(VerticalAlign.End)
      }
      .width('100%')
      .backgroundColor(Color.White)
      .borderRadius(12)
      .padding(14)
      .margin({ left: 12, right: 12 })
      .alignItems(HorizontalAlign.Start)

      Text('书写工具双列精选').fontSize(15).fontColor('#212121').fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ left: 16, top: 16, bottom: 8 })

      Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
        ForEach(this.pens, (g: GoodsItem, i: number) => {
          Column() {
            Row() {
              Column() {
                Text('🖊️').fontSize(22)
              }
              .width(44)
              .height(44)
              .borderRadius(22)
              .backgroundColor(i % 2 === 0 ? '#E8EAF6' : '#FFF3E0')
              .justifyContent(FlexAlign.Center)

              Column() {
                Text(g.tag).fontSize(9).fontColor('#283593').fontWeight(FontWeight.Bold)
                Text('好评 ' + g.score + '%').fontSize(9).fontColor('#8A8FA3').margin({ top: 3 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.End)
            }
            .width('100%')
            .alignItems(VerticalAlign.Center)

            Text(g.name).fontSize(12).fontColor('#212121').fontWeight(FontWeight.Medium).margin({ top: 8 }).maxLines(1)
            Text(g.desc).fontSize(9).fontColor('#8A8FA3').margin({ top: 3 }).maxLines(1)

            Row() {
              Text('¥' + g.price).fontSize(14).fontColor('#D84315').fontWeight(FontWeight.Bold)
              if (g.hot) {
                Text('编辑推荐').fontSize(9).fontColor(Color.White).backgroundColor('#FF3B30').borderRadius(4).padding({ left: 5, right: 5, top: 2, bottom: 2 }).margin({ left: 6 })
                  .scale({ x: 1.08, y: 1.08 })
                  .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
              }
            }
            .width('100%')
            .margin({ top: 6 })
            .justifyContent(FlexAlign.SpaceBetween)
            .alignItems(VerticalAlign.Center)
          }
          .width('48%')
          .backgroundColor(Color.White)
          .borderRadius(12)
          .padding(12)
          .margin({ bottom: 10 })
          .onClick(() => {
            this.selectedItem = g
            this.showDetail = true
          })
        }, (g: GoodsItem) => g.name)
      }
      .width('100%')
      .padding({ left: 12, right: 12 })
    }
    .width('100%')
    .padding({ bottom: 12 })
  }

笔尖规格柱状图的标题中"(mm×10)“表示数值已乘以10进行放大显示——num字段存储的是5(代表0.5mm),柱状图直接使用该数值进行高度计算。g.tag.slice(2, 7)从"笔尖0.5mm"中提取"0.5mm”,这种字符串截取技术在标签提取中非常实用。双列精选列表的卡片设计采用圆形图标加右侧标签的并排布局,热销标签显示为"编辑推荐"而非通用的"热销",体现了文具品类的专业推荐属性。圆形图标背景色交替使用淡蓝#E8EAF6和淡橙#FFF3E0,冷色调和暖色调的交替增加了视觉趣味。

办公纸品与克重分布

在这里插入图片描述

buildTab3方法构建了办公纸品页面,融合了纸张克重分布柱状图、口碑排行复购进度条列表和横向滑动的本册速览三个板块。克重柱状图使用浅橙色#FFAB91柱体,在深蓝色#1A237E背景上形成鲜明对比。

  @Builder buildTab3() {
    Column() {
      Column() {
        Text('纸张克重分布柱状图(g)').fontSize(13).fontColor(Color.White).fontWeight(FontWeight.Bold)
        Row({ space: 10 }) {
          ForEach(this.papers, (g: GoodsItem) => {
            Column() {
              Column()
                .width(16)
                .height(g.num / this.maxOfNum(this.papers) * 100)
                .backgroundColor('#FFAB91')
                .borderRadius(4)
              Text(g.num.toString()).fontSize(8).fontColor('rgba(255,255,255,0.85)').margin({ top: 3 }).maxLines(1)
            }
            .alignItems(HorizontalAlign.Start)
          }, (g: GoodsItem) => g.name)
        }
        .width('100%')
        .margin({ top: 12 })
        .alignItems(VerticalAlign.End)
      }
      .width('100%')
      .backgroundColor('#1A237E')
      .borderRadius(14)
      .padding(14)
      .margin({ left: 12, right: 12, top: 12 })
      .alignItems(HorizontalAlign.Start)

      Text('口碑排行 · 复购进度').fontSize(15).fontColor('#212121').fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ left: 16, top: 16, bottom: 8 })

      Column() {
        ForEach(this.papers, (g: GoodsItem, i: number) => {
          Row() {
            Text((i + 1).toString()).fontSize(12).fontColor(Color.White).backgroundColor(i < 3 ? '#FF7043' : '#9AA0B5').borderRadius(10).width(20).height(20).textAlign(TextAlign.Center)

            Column() {
              Text(g.name).fontSize(12).fontColor('#212121').fontWeight(FontWeight.Medium).maxLines(1)
              Stack({ alignContent: Alignment.Start }) {
                Column().width('100%').height(7).backgroundColor('#F0F0F5').borderRadius(4)
                Column().width(g.score + '%').height(7).backgroundColor('#283593').borderRadius(4)
              }
              .width('100%')
              .margin({ top: 5 })
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Start)
            .margin({ left: 10 })

            Column() {
              Text('¥' + g.price).fontSize(13).fontColor('#D84315').fontWeight(FontWeight.Bold)
              if (g.hot) {
                Text('🔥 囤货王').fontSize(8).fontColor('#D84315').margin({ top: 3 })
                  .scale({ x: 1.1, y: 1.1 })
                  .animation({ duration: 600, iterations: -1, curve: Curve.EaseInOut })
              }
            }
            .alignItems(HorizontalAlign.End)
          }
          .width('100%')
          .alignItems(VerticalAlign.Center)
          .margin({ bottom: 12 })
          .onClick(() => {
            this.selectedItem = g
            this.showDetail = true
          })
        }, (g: GoodsItem) => g.name)
      }
      .width('100%')
      .backgroundColor(Color.White)
      .borderRadius(12)
      .padding(14)
      .margin({ left: 12, right: 12 })

      Text('本册速览 · 横滑'.slice(0, 8)).fontSize(15).fontColor('#212121').fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ left: 16, top: 16, bottom: 8 })

      Scroll() {
        Row({ space: 10 }) {
          ForEach(this.papers, (g: GoodsItem, i: number) => {
            Column() {
              Column() {
                Text('📄').fontSize(28)
              }
              .width(76)
              .height(76)
              .borderRadius(12)
              .backgroundColor(i % 2 === 0 ? '#E3F2FD' : '#E8EAF6')
              .justifyContent(FlexAlign.Center)

              Text(g.name).fontSize(11).fontColor('#212121').margin({ top: 8 }).maxLines(1)
              Text('¥' + g.price).fontSize(13).fontColor('#D84315').fontWeight(FontWeight.Bold).margin({ top: 4 })
            }
            .width(112)
            .backgroundColor(Color.White)
            .borderRadius(12)
            .padding(10)
            .onClick(() => {
              this.selectedItem = g
              this.showDetail = true
            })
          }, (g: GoodsItem) => g.name)
        }
        .padding({ left: 12, right: 12 })
      }
      .scrollable(ScrollDirection.Horizontal)
      .scrollBar(BarState.Off)
      .width('100%')
    }
    .width('100%')
    .padding({ bottom: 12 })
  }

口碑排行列表的前三名序号使用橙色#FF7043背景,与克重柱状图的柱体颜色形成呼应。复购进度条使用蓝色#283593填充,保持了品牌色的一致性。热销标签显示为"囤货王"并配有1.1倍脉冲动画和火焰图标,这种趣味化的标签文案增强了用户的购买冲动。横向滑动区的本册卡片宽度为112像素,比酒水应用的120像素更窄,适应了纸品商品信息更简洁的展示需求。

应用核心流程

文具集市

书写工具

办公纸品

桌面收纳

企业采购

消息

我的

点击卡片

点击批量采购

点击查看详情

申请开票

批量采购

确认删除

应用启动

渲染头部企业采购标识

加载底部导航栏

用户选择标签页

展示开学季活动与商品列表

展示笔尖规格柱状图与双列精选

展示克重柱状图与复购排行

展示收纳列表与好评分段图

展示采购数据与服务目录

展示消息中心列表

展示用户中心

用户点击商品

打开商品详情弹窗

打开采购询价弹窗

选择操作

打开开票弹窗

选择规格/调整数量/查看总价

选择发票类型/抬头/金额区间

用户点击删除

打开删除确认弹窗

从消息数组移除

关闭弹窗返回列表

企业采购中心与集采服务

buildTab5方法是应用最具B端特色的功能页面,构建了一个完整的企业采购中心界面。页面顶部展示了采购数据面板——综合好评率、服务企业数量和最快账期,配以办公楼图标动画。下方是集采服务项目列表,涵盖从定制文具到考勤机的完整产品线。

  @Builder buildTab5() {
    Column() {
      Column() {
        Row() {
          Column() {
            Text('企业采购中心').fontSize(17).fontColor(Color.White).fontWeight(FontWeight.Bold)
            Text('对公转账 · 月结账期 · 专属客户经理'.slice(0, 15)).fontSize(10).fontColor('rgba(255,255,255,0.75)').margin({ top: 4 })
          }
          .alignItems(HorizontalAlign.Start)
          .layoutWeight(1)

          Text('🏢').fontSize(32)
            .scale({ x: 1.08, y: 1.08 })
            .animation({ duration: 800, iterations: -1, curve: Curve.EaseInOut })
        }
        .width('100%')
        .alignItems(VerticalAlign.Center)

        Row() {
          Column() {
            Text('86%').fontSize(22).fontColor('#FFAB91').fontWeight(FontWeight.Bold)
            Text('综合好评').fontSize(10).fontColor('rgba(255,255,255,0.7)').margin({ top: 2 })
          }
          .layoutWeight(1)
          .alignItems(HorizontalAlign.Start)

          Column() {
            Text('3200+').fontSize(22).fontColor('#FFF59D').fontWeight(FontWeight.Bold).margin({ top: 2 })
              .scale({ x: 1.04, y: 1.04 })
              .animation({ duration: 800, iterations: -1, curve: Curve.EaseInOut })
            Text('服务企业').fontSize(10).fontColor('rgba(255,255,255,0.7)').margin({ top: 2 })
          }
          .layoutWeight(1)
          .alignItems(HorizontalAlign.Start)

          Column() {
            Text('T+7').fontSize(22).fontColor('#80CBC4').fontWeight(FontWeight.Bold).margin({ top: 2 })
            Text('最快账期').fontSize(10).fontColor('rgba(255,255,255,0.7)').margin({ top: 2 })
          }
          .layoutWeight(1)
          .alignItems(HorizontalAlign.Start)
        }
        .width('100%')
        .margin({ top: 16 })
        .alignItems(VerticalAlign.Top)
      }
      .width('100%')
      .backgroundColor('#283593')
      .borderRadius(14)
      .padding(18)
      .margin({ left: 12, right: 12, top: 12 })
      .alignItems(HorizontalAlign.Start)

      Text('集采服务项目'.slice(0, 6)).fontSize(15).fontColor('#212121').fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ left: 16, top: 16, bottom: 8 })

      Column() {
        ForEach(this.corps, (g: GoodsItem, i: number) => {
          Row() {
            Column() {
              Text(i % 5 === 0 ? '🏢' : (i % 5 === 1 ? '🧾' : (i % 5 === 2 ? '🖨️' : (i % 5 === 3 ? '🪴' : '🪑')))).fontSize(24)
            }
            .width(52)
            .height(52)
            .borderRadius(12)
            .backgroundColor(i % 2 === 0 ? '#E8EAF6' : '#E0F2F1')
            .justifyContent(FlexAlign.Center)

            Column() {
              Row() {
                Text(g.name).fontSize(13).fontColor('#212121').fontWeight(FontWeight.Medium).maxLines(1)
                Text(g.tag).fontSize(9).fontColor('#283593').backgroundColor('#E8EAF6').borderRadius(4).padding({ left: 5, right: 5, top: 2, bottom: 2 }).margin({ left: 6 })
                if (g.hot) {
                  Text('客户点赞'.slice(0, 4)).fontSize(9).fontColor(Color.White).backgroundColor('#FF3B30').borderRadius(4).padding({ left: 5, right: 5, top: 2, bottom: 2 }).margin({ left: 6 })
                    .scale({ x: 1.08, y: 1.08 })
                    .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
                }
              }
              .alignItems(VerticalAlign.Center)

              Text(g.desc).fontSize(10).fontColor('#8A8FA3').margin({ top: 3 }).maxLines(1)
              Text('好评率 ' + g.score + '% · 已合作 ' + g.num * 36 + ' 家').fontSize(9).fontColor('#9AA0B5').margin({ top: 3 })
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Start)
            .margin({ left: 10 })

            Column() {
              Text('¥' + g.price + ' 起').fontSize(13).fontColor('#D84315').fontWeight(FontWeight.Bold)
              Column() {
                Text('批量采购'.slice(0, 4)).fontSize(10).fontColor(Color.White)
              }
              .backgroundColor('#283593')
              .borderRadius(13)
              .padding({ left: 10, right: 10, top: 5, bottom: 5 })
              .margin({ top: 6 })
              .onClick(() => {
                this.selectedItem = g
                this.bulkNum = 100
                this.showBulk = true
              })
            }
            .alignItems(HorizontalAlign.End)
          }
          .width('100%')
          .backgroundColor(Color.White)
          .borderRadius(12)
          .padding(12)
          .margin({ bottom: 10 })
          .alignItems(VerticalAlign.Center)
        }, (g: GoodsItem) => g.name)
      }
      .width('100%')
      .padding({ left: 12, right: 12 })
    }
    .width('100%')
    .padding({ bottom: 12 })
  }

企业采购数据面板使用了深蓝色#283593背景与头部品牌色一致。三个数据指标采用了不同的强调色:综合好评率使用浅橙#FFAB91,服务企业数量使用浅黄#FFF59D并配有1.04倍脉冲动画,最快账期使用浅青#80CBC4。通过不同颜色的数据展示,用户可以快速区分不同维度的指标。“已合作 " + g.num * 36 + " 家"的计算公式将num字段乘以固定系数36,模拟了企业服务数量的累加效果。集采服务列表的图标使用了五种表情符号循环(办公楼、发票、打印机、绿植、椅子),覆盖了企业办公场景的主要品类。热销标签显示为"客户点赞”,强调了B端客户口碑的重要性。

批量采购询价弹窗

bulkModal方法是B端业务的核心弹窗,集成了规格选择器、采购数量步进器和实时总价计算器。采购数量采用步进10的递增递减设计(范围10-5000),总价按selPrice() * bulkNum实时计算,并显示批量92折优惠信息。

  @Builder bulkModal() {
    Column() {
      Column() {
        Row() {
          Column() {
            Text('批量采购询价'.slice(0, 6)).fontSize(16).fontColor('#212121').fontWeight(FontWeight.Bold)
            Text('量大从优 · 免费开票 · 月结可选'.slice(0, 14)).fontSize(10).fontColor('#283593').margin({ top: 3 })
          }
          .alignItems(HorizontalAlign.Start)

          Text('✕').fontSize(16).fontColor('#9AA0B5')
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        .alignItems(VerticalAlign.Center)

        Column() {
          Text(this.selName()).fontSize(13).fontColor('#212121').fontWeight(FontWeight.Medium).maxLines(1)
          Text(this.selDesc()).fontSize(10).fontColor('#8A8FA3').margin({ top: 3 })
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 14 })

        Column() {
          Text('规格选择'.slice(0, 4)).fontSize(12).fontColor('#5C6280')
          Flex({ wrap: FlexWrap.Wrap }) {
            ForEach(this.specs, (s: string, i: number) => {
              Text(s).fontSize(11).fontColor(this.selSpec === i ? Color.White : '#5C6280')
                .backgroundColor(this.selSpec === i ? '#283593' : '#F5F6FA')
                .borderRadius(13)
                .padding({ left: 12, right: 12, top: 6, bottom: 6 })
                .margin({ right: 8, top: 8 })
                .onClick(() => {
                  this.selSpec = i
                })
            }, (s: string) => s)
          }
          .width('100%')
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 14 })

        Row() {
          Column() {
            Text('采购数量(件)'.slice(0, 7)).fontSize(11).fontColor('#5C6280')
            Row() {
              Text('-').fontSize(15).fontColor('#283593').width(28).height(28).textAlign(TextAlign.Center).backgroundColor('#F5F6FA').borderRadius(8)
                .onClick(() => {
                  if (this.bulkNum > 10) {
                    this.bulkNum = this.bulkNum - 10
                  }
                })
              Text(this.bulkNum.toString()).fontSize(13).fontColor('#212121').fontWeight(FontWeight.Medium).width(60).textAlign(TextAlign.Center)
              Text('+').fontSize(15).fontColor('#283593').width(28).height(28).textAlign(TextAlign.Center).backgroundColor('#F5F6FA').borderRadius(8)
                .onClick(() => {
                  if (this.bulkNum < 5000) {
                    this.bulkNum = this.bulkNum + 10
                  }
                })
            }
            .margin({ top: 6 })
            .alignItems(VerticalAlign.Center)
          }
          .alignItems(HorizontalAlign.Start)

          Column() {
            Text('预估总价'.slice(0, 4)).fontSize(11).fontColor('#5C6280')
            Text('¥' + (this.selPrice() * this.bulkNum)).fontSize(22).fontColor('#D84315').fontWeight(FontWeight.Bold).margin({ top: 6 })
              .scale({ x: 1.05, y: 1.05 })
              .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            Text('已享批量 92 折'.slice(0, 9)).fontSize(9).fontColor('#2E7D32').margin({ top: 3 })
          }
          .alignItems(HorizontalAlign.End)
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        .margin({ top: 16 })
        .alignItems(VerticalAlign.Top)

        Row() {
          Column() {
            Text('仅询价'.slice(0, 3)).fontSize(11).fontColor('#8A8FA3')
          }
          .padding({ left: 10, right: 10, top: 5, bottom: 5 })
          .border({ width: 1, color: '#E5E5EC' })
          .borderRadius(12)
          .onClick(() => {
            this.showBulk = false
          })

          Column() {
            Text('提交采购单'.slice(0, 5)).fontSize(13).fontColor(Color.White).fontWeight(FontWeight.Medium)
          }
          .layoutWeight(1)
          .height(40)
          .backgroundColor('#283593')
          .borderRadius(20)
          .justifyContent(FlexAlign.Center)
          .margin({ left: 10 })
          .onClick(() => {
            this.showBulk = false
          })
        }
        .width('100%')
        .margin({ top: 20 })
        .alignItems(VerticalAlign.Center)
      }
      .width('100%')
      .backgroundColor(Color.White)
      .borderRadius(16)
      .padding(16)
      .constraintSize({ maxHeight: '80%' })
      .onClick((event: ClickEvent) => {
        event.stopPropagation()
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('rgba(0,0,0,0.5)')
    .justifyContent(FlexAlign.End)
    .onClick(() => {
      this.showBulk = false
    })
  }

批量采购弹窗从底部弹出(justifyContent: FlexAlign.End),符合B端快速操作的习惯。采购数量的步进设计是最大亮点——每次增减10件而非1件,范围限制为10-5000,这直接反映了企业采购的批量特征。递减时检查bulkNum > 10,递增时检查bulkNum < 5000,确保了数量在合理范围内。预估总价配有1.05倍脉冲动画,"已享批量 92 折"以绿色#2E7D32显示,通过优惠信息激励用户增加采购量。底部双按钮设计中,"仅询价"使用描边样式作为轻量操作,"提交采购单"使用蓝色实心样式作为正式操作。

开票申请弹窗

invoiceModal方法是应用中B端属性最强的弹窗,集成了发票类型选择、发票抬头选择和开票金额区间展示三大功能。发票类型覆盖了电子普票、纸质普票和增值税专票三种,抬头覆盖了个人、企业和单位三种。

  @Builder invoiceModal() {
    Column() {
      Column() {
        Row() {
          Column() {
            Text('开票申请').fontSize(16).fontColor('#212121').fontWeight(FontWeight.Bold)
            Text('电子发票 30 秒极速开具'.slice(0, 12)).fontSize(10).fontColor('#00897B').margin({ top: 3 })
          }
          .alignItems(HorizontalAlign.Start)

          Text('✕').fontSize(16).fontColor('#9AA0B5')
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        .alignItems(VerticalAlign.Center)

        Column() {
          Text('发票类型'.slice(0, 4)).fontSize(12).fontColor('#5C6280')
          Flex({ wrap: FlexWrap.Wrap }) {
            ForEach(this.invTypes, (t: string, i: number) => {
              Text(t).fontSize(11).fontColor(this.selInvType === i ? Color.White : '#5C6280')
                .backgroundColor(this.selInvType === i ? '#283593' : '#F5F6FA')
                .borderRadius(13)
                .padding({ left: 12, right: 12, top: 6, bottom: 6 })
                .margin({ right: 8, top: 8 })
                .onClick(() => {
                  this.selInvType = i
                })
            }, (t: string) => t)
          }
          .width('100%')
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 16 })

        Column() {
          Text('发票抬头'.slice(0, 4)).fontSize(12).fontColor('#5C6280')
          Flex({ wrap: FlexWrap.Wrap }) {
            ForEach(this.titles, (t: string, i: number) => {
              Text(t).fontSize(11).fontColor(this.selTitle === i ? Color.White : '#5C6280')
                .backgroundColor(this.selTitle === i ? '#00897B' : '#F5F6FA')
                .borderRadius(13)
                .padding({ left: 12, right: 12, top: 6, bottom: 6 })
                .margin({ right: 8, top: 8 })
                .onClick(() => {
                  this.selTitle = i
                })
            }, (t: string) => t)
          }
          .width('100%')
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 14 })

        Column() {
          Text('开票金额区间'.slice(0, 6)).fontSize(12).fontColor('#5C6280')
          Row({ space: 8 }) {
            Text('¥100-500').fontSize(11).fontColor('#5C6280').backgroundColor('#F5F6FA').borderRadius(10).padding({ left: 10, right: 10, top: 6, bottom: 6 })
            Text('¥500-2000').fontSize(11).fontColor('#5C6280').backgroundColor('#F5F6FA').borderRadius(10).padding({ left: 10, right: 10, top: 6, bottom: 6 })
            Text('¥2000+').fontSize(11).fontColor(Color.White).backgroundColor('#D84315').borderRadius(10).padding({ left: 10, right: 10, top: 6, bottom: 6 }).margin({ left: 8 })
              .scale({ x: 1.08, y: 1.08 })
              .animation({ duration: 650, iterations: -1, curve: Curve.EaseInOut })
          }
          .margin({ top: 8 })
          .alignItems(VerticalAlign.Center)
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 14 })

        Row() {
          Column() {
            Text('取消'.slice(0, 2)).fontSize(13).fontColor('#5C6280')
          }
          .layoutWeight(1)
          .height(40)
          .backgroundColor('#F5F6FA')
          .borderRadius(20)
          .justifyContent(FlexAlign.Center)
          .onClick(() => {
            this.showInvoice = false
          })

          Column() {
            Text('提交开票'.slice(0, 4)).fontSize(13).fontColor(Color.White).fontWeight(FontWeight.Medium)
          }
          .layoutWeight(1.6)
          .height(40)
          .backgroundColor('#00897B')
          .borderRadius(20)
          .justifyContent(FlexAlign.Center)
          .margin({ left: 10 })
          .onClick(() => {
            this.showInvoice = false
          })
        }
        .width('100%')
        .margin({ top: 20 })
        .alignItems(VerticalAlign.Center)
      }
      .width('92%')
      .backgroundColor(Color.White)
      .borderRadius(16)
      .padding(16)
      .constraintSize({ maxHeight: '80%' })
      .onClick((event: ClickEvent) => {
        event.stopPropagation()
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('rgba(0,0,0,0.5)')
    .justifyContent(FlexAlign.Center)
    .onClick(() => {
      this.showInvoice = false
    })
  }

开票弹窗从中央弹出(justifyContent: FlexAlign.Center),与其他B端弹窗从底部弹出不同,体现了开票操作的正式性和审慎性。两个选择器采用了不同的选中颜色:发票类型选中为蓝色#283593(品牌色),发票抬头选中为绿色#00897B(财税色),通过颜色差异帮助用户区分不同维度的选择。开票金额区间使用了三个标签展示,其中"¥2000+"使用橙色#D84315背景配1.08倍脉冲动画,强调了高金额开票的引导意图。提交按钮使用了绿色#00897B背景而非品牌蓝色,因为绿色在财税场景中代表"确认通过"的含义。

主构建函数与弹窗编排

build方法作为应用入口构建函数,通过Stack容器将主界面与五个弹窗叠加。整体背景色使用了#F4F6FB——一种偏冷的浅蓝灰色,比前两个应用的暖色调背景更符合办公商务的视觉调性。

  build() {
    Stack() {
      Column() {
        this.buildHeader()
        Scroll() {
          Column() {
            if (this.currentTab === 0) {
              this.buildTab1()
            } else if (this.currentTab === 1) {
              this.buildTab2()
            } else if (this.currentTab === 2) {
              this.buildTab3()
            } else if (this.currentTab === 3) {
              this.buildTab4()
            } else if (this.currentTab === 4) {
              this.buildTab5()
            } else if (this.currentTab === 5) {
              this.buildTab6()
            } else {
              this.buildTab7()
            }
          }
          .width('100%')
        }
        .layoutWeight(1)
        .scrollBar(BarState.Off)
        .width('100%')
        .align(Alignment.Top)

        this.buildBottom()
      }
      .width('100%')
      .height('100%')

      if (this.showPublish) {
        this.publishModal()
      }
      if (this.showBulk) {
        this.bulkModal()
      }
      if (this.showInvoice) {
        this.invoiceModal()
      }
      if (this.showDelete) {
        this.deleteModal()
      }
      if (this.showDetail) {
        this.detailModal()
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F4F6FB')
  }

底部导航栏的选中状态颜色为蓝色#283593,与头部品牌色一致。弹窗的渲染顺序为:发布闲置、批量采购、开票申请、删除确认、商品详情。前三个弹窗是B端专属功能,后两个是通用功能,这种排列顺序确保了B端弹窗在渲染层级上的优先性。五个弹窗通过独立的@State布尔变量控制显示,互斥的设计避免了多个弹窗同时出现的混乱情况。

桌面收纳好评分段柱状图

buildTab4方法构建了桌面收纳页面,其中好评分段柱状图采用了独特的计算公式(g.score - 85) / 10 * 90。这个公式将评分从85分基线开始计算,除以10得到分数段比例,再乘以90像素的最大高度,使得90-100分范围内的评分差异在柱状图中被放大显示。

  @Builder buildTab4() {
    Column() {
      Text('桌面收纳专区').fontSize(15).fontColor('#212121').fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ left: 16, top: 14, bottom: 8 })

      Column() {
        ForEach(this.desks, (g: GoodsItem, i: number) => {
          Row() {
            Column()
              .width(4)
              .height(48)
              .backgroundColor(i % 4 === 0 ? '#283593' : (i % 4 === 1 ? '#FF7043' : (i % 4 === 2 ? '#00897B' : '#5C6BC0')))
              .borderRadius(2)

            Column() {
              Row() {
                Text(g.name).fontSize(13).fontColor('#212121').fontWeight(FontWeight.Medium).maxLines(1)
                Text(g.tag).fontSize(9).fontColor('#00897B').backgroundColor('#E0F2F1').borderRadius(4).padding({ left: 5, right: 5, top: 2, bottom: 2 }).margin({ left: 6 })
                if (g.hot) {
                  Text('热销').fontSize(9).fontColor(Color.White).backgroundColor('#FF3B30').borderRadius(4).padding({ left: 5, right: 5, top: 2, bottom: 2 }).margin({ left: 6 })
                    .scale({ x: 1.08, y: 1.08 })
                    .animation({ duration: 650, iterations: -1, curve: Curve.EaseInOut })
                }
              }
              .alignItems(VerticalAlign.Center)

              Text(g.desc).fontSize(10).fontColor('#8A8FA3').margin({ top: 3 }).maxLines(1)
              Text('好评率 ' + g.score + '%'.slice(0, 1) + ')').fontSize(9).fontColor('#9AA0B5').margin({ top: 3 })
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Start)
            .margin({ left: 10 })

            Column() {
              Text('¥' + g.price).fontSize(15).fontColor('#D84315').fontWeight(FontWeight.Bold)
              Column() {
                Text('查看详情'.slice(0, 4)).fontSize(9).fontColor('#283593')
              }
              .borderRadius(11)
              .border({ width: 1, color: '#283593' })
              .padding({ left: 8, right: 8, top: 3, bottom: 3 })
              .margin({ top: 6 })
              .onClick(() => {
                this.selectedItem = g
                this.showDetail = true
              })
            }
            .alignItems(HorizontalAlign.End)
          }
          .width('100%')
          .backgroundColor(Color.White)
          .borderRadius(12)
          .padding(12)
          .margin({ bottom: 10 })
          .alignItems(VerticalAlign.Center)
          .onClick(() => {
            this.selectedItem = g
            this.showDetail = true
          })
        }, (g: GoodsItem) => g.name)
      }
      .width('100%')
      .padding({ left: 12, right: 12 })

      Text('好评分段柱状图'.slice(0, 7)).fontSize(15).fontColor('#212121').fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ left: 16, top: 10, bottom: 8 })

      Column() {
        Row({ space: 12 }) {
          ForEach(this.desks, (g: GoodsItem) => {
            Column() {
              Column()
                .width(16)
                .height((g.score - 85) / 10 * 90)
                .backgroundColor('#00897B')
                .borderRadius(4)
              Text(g.score.toString()).fontSize(8).fontColor('#8A8FA3').margin({ top: 3 }).maxLines(1)
            }
            .alignItems(HorizontalAlign.Start)
          }, (g: GoodsItem) => g.name)
        }
        .width('100%')
        .alignItems(VerticalAlign.End)
      }
      .width('100%')
      .backgroundColor(Color.White)
      .borderRadius(12)
      .padding(14)
      .margin({ left: 12, right: 12, top: 4 })
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .padding({ bottom: 12 })
  }

桌面收纳列表的左侧色条使用了四色循环(蓝色、橙色、绿色、蓝灰色),比前两个应用的三色循环更丰富。标签使用绿色#00897B背景配浅绿#E0F2F1底色,区别于其他页面的蓝色标签。"查看详情"按钮使用了描边样式而非实心背景,作为轻量级操作入口。好评分段柱状图的(g.score - 85) / 10 * 90公式是数据可视化中的分段放大技术——当所有评分都在90-98范围内时,直接使用评分值作为柱高会导致差异不明显,通过减去85分基线再除以10进行比例放大,使得93分和96分的柱高差异从3%变成了30%,大幅提升了可视化效果。

技术点对比分析

技术维度 实现方式 设计特点 适用场景
状态管理 @State+B端业务状态 采购数量、发票类型、抬头选择 批量采购、开票申请
步进调节 bulkNum±10步进 范围10-5000,步进10件 企业批量采购数量选择
字符串截取 slice方法广泛使用 控制文本长度、提取标签数值 标签展示、标题截断
分段柱状图 (score-85)/10*90 减去基线放大差异 桌面收纳好评分段图
双色选择器 类型蓝+抬头绿 颜色区分不同选择维度 开票弹窗双维度选择
弹窗弹出 居中vs底部差异化 开票居中、采购底部 B端弹窗交互定位
数据面板 多色数据指标 好评橙、企业黄、账期青 企业采购中心
估值计算 num*36固定系数 模拟企业服务数量累加 集采服务合作数展示
动画效果 scale+animation脉冲 iterations:-1无限循环 领券按钮、未读角标、总价
事件处理 onClick+stopPropagation 遮罩关闭+内容阻止冒泡 所有弹窗交互

安装DevEco Studio程序

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

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

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

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

在这里插入图片描述


完整代码:

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

interface TabInfo {
  name: string
  icon: string
}

interface MsgItem {
  title: string
  content: string
  time: string
  read: boolean
}

@Entry
@Component
struct Index {
  @State currentTab: number = 0
  @State showPublish: boolean = false
  @State showBulk: boolean = false
  @State showInvoice: boolean = false
  @State showDelete: boolean = false
  @State showDetail: boolean = false
  @State selectedItem: GoodsItem | null = null
  @State selCat: number = 0
  @State selUse: number = 0
  @State selSpec: number = 0
  @State bulkNum: number = 100
  @State selInvType: number = 0
  @State selTitle: number = 0
  @State deleteIndex: number = -1
  @State msgs: MsgItem[] = [
    { title: '采购审批通过', content: '您提交的企业采购单已审批通过,预计 2 日内发货', time: '10:36', read: false },
    { title: '开票通知', content: '订单尾号 8821 的电子发票已开具,请查收邮箱', time: '09:48', read: false },
    { title: '补货提醒', content: '您常购的 A4 纸库存不足一箱,建议及时补货', time: '昨天', read: false },
    { title: '物流更新', content: '您的文具整箱已从广州仓发出,预计后天送达', time: '昨天', read: true },
    { title: '企业专属价', content: '本月企业会员价已更新,打印机耗材直降 15%', time: '前天', read: true },
    { title: '对账单生成', content: '8 月采购对账单已生成,可在企业中心下载', time: '前天', read: true },
    { title: '新品上架', content: '进口中性笔新款到货,0.5mm 针管头书写顺滑', time: '周三', read: true },
    { title: '直播预告', content: '明晚 19:00 办公焕新专场直播,整点抽打印机', time: '周三', read: true },
    { title: '优惠券到账', content: '企业采购满 1000 减 120 券已发放,30 天有效', time: '周四', read: true },
    { title: '评价有礼', content: '评价采购的办公椅可领 30 元无门槛券', time: '上周', read: true },
    { title: '以旧换新', content: '旧打印机折价回收,最高抵 800 元', time: '上周', read: true },
    { title: '安全提醒', content: '谨防冒充供应商的变更收款账号诈骗', time: '上周', read: true }
  ]

  private tabs: TabInfo[] = [
    { name: '文具集市', icon: '✏️' },
    { name: '书写工具', icon: '🖊️' },
    { name: '办公纸品', icon: '📄' },
    { name: '桌面收纳', icon: '🗄️' },
    { name: '企业采购', icon: '🏢' },
    { name: '消息', icon: '✉️' },
    { name: '我的', icon: '👤' }
  ]
  private cats: string[] = ['中性笔', '钢笔', '荧光笔', '铅笔', '马克笔', '白板笔']
  private uses: string[] = ['学生刷题', '办公签字', '教师批改', '绘画手账']
  private specs: string[] = ['0.38mm 黑', '0.5mm 黑', '0.5mm 蓝', '12 色混装']
  private invTypes: string[] = ['电子普票', '纸质普票', '增值税专票']
  private titles: string[] = ['个人抬头', '企业抬头', '单位抬头']

  private items: GoodsItem[] = [
    { name: '百乐P500中性笔0.5', price: 9, desc: '直液式 大容量顺滑', score: 96, num: 5, tag: '刷题神器', hot: true },
    { name: '三菱UM-151中性笔', price: 8, desc: '低粘度油墨 快干', score: 95, num: 5, tag: '经典款', hot: false },
    { name: '晨光优品按动笔', price: 3, desc: '国货性价比 顺滑不断墨', score: 93, num: 5, tag: '性价比', hot: true },
    { name: '百乐78G+钢笔', price: 68, desc: 'F尖 磨砂手感 练字', score: 95, num: 3, tag: '入门钢笔', hot: false },
    { name: '写乐四季织钢笔', price: 328, desc: '透明杆 15度尖', score: 96, num: 3, tag: '进阶款', hot: false },
    { name: '凌美狩猎者钢笔', price: 79, desc: '德系经典 ABS笔杆', score: 94, num: 3, tag: '德系', hot: true },
    { name: '斑马JJ15复古色', price: 7, desc: '复古五色 手账配色', score: 94, num: 5, tag: '手账', hot: false },
    { name: '无印双头荧光笔', price: 12, desc: '不透纸 温和色系', score: 93, num: 5, tag: '划重点', hot: false },
    { name: '施德楼马克笔套装', price: 128, desc: '专业设计 24色', score: 95, num: 5, tag: '设计师', hot: false },
    { name: '辉柏嘉水彩铅笔', price: 89, desc: '36色 水溶晕染', score: 94, num: 5, tag: '绘画', hot: false },
    { name: '得力A4复印纸整箱', price: 118, desc: '70g 500张×8包', score: 95, num: 70, tag: '办公刚需', hot: true },
    { name: '晨光笔记本B5本', price: 6, desc: '康奈尔内页 加厚', score: 93, num: 5, tag: '学生党', hot: false },
    { name: '国誉Campus笔记本', price: 15, desc: '东大脑训练格', score: 95, num: 5, tag: '进口', hot: false },
    { name: '得力订书机大号', price: 25, desc: '可订50页 省力', score: 92, num: 5, tag: '办公', hot: false },
    { name: '得力文件夹加厚', price: 4, desc: 'A4双强夹 透明', score: 91, num: 5, tag: '归档', hot: false },
    { name: '桌面文件收纳架', price: 45, desc: '三层分格 竖立收纳', score: 93, num: 5, tag: '收纳', hot: true },
    { name: '透明抽屉收纳盒', price: 35, desc: '五层小抽屉', score: 92, num: 5, tag: '桌面', hot: false },
    { name: '便签纸组合装', price: 16, desc: '76×76 四色各一本', score: 91, num: 5, tag: '便签', hot: false },
    { name: '白板磁吸大号', price: 129, desc: '90×120cm 免打孔', score: 94, num: 5, tag: '会议室', hot: false },
    { name: '人体工学办公椅', price: 899, desc: '腰托可调 网布透气', score: 95, num: 5, tag: '久坐党', hot: true },
    { name: '升降桌电动款', price: 1599, desc: '记忆高度 双电机', score: 96, num: 5, tag: '站坐交替', hot: false },
    { name: '桌面理线器磁吸', price: 19, desc: '告别线材凌乱', score: 90, num: 5, tag: '神器', hot: false },
    { name: '洞洞板桌面置物', price: 59, desc: '自由组合 收纳上墙', score: 92, num: 5, tag: '改造', hot: false },
    { name: '墨囊补给装100枚', price: 22, desc: '通用型 黑蓝红', score: 93, num: 5, tag: '耗材', hot: false }
  ]
  private pens: GoodsItem[] = [
    { name: '百乐P500直液笔', price: 9, desc: '针管头 视觉笔尖', score: 96, num: 5, tag: '笔尖0.5mm', hot: true },
    { name: '三菱UM-100', price: 5, desc: '入门经典 圆润书写', score: 94, num: 10, tag: '笔尖0.5mm', hot: false },
    { name: '三菱SN-220速干', price: 8, desc: '0.4秒速干 左手党', score: 95, num: 10, tag: '笔尖0.4mm', hot: true },
    { name: '斑马JJ77防逆流', price: 9, desc: '高密度颜料 速干', score: 94, num: 10, tag: '笔尖0.5mm', hot: false },
    { name: '百乐Juice果汁笔', price: 7, desc: '16色可选 果汁感', score: 95, num: 4, tag: '笔尖0.4mm', hot: false },
    { name: '百乐Juice up', price: 15, desc: '金属笔尖 精细', score: 96, num: 4, tag: '笔尖0.3mm', hot: false },
    { name: '晨光K35按动', price: 2, desc: '国民按动 随手囤', score: 92, num: 5, tag: '笔尖0.5mm', hot: false },
    { name: '得力臻顺滑', price: 2, desc: '大容量 书写千米', score: 91, num: 5, tag: '笔尖0.5mm', hot: false },
    { name: '派通BLN105', price: 12, desc: '低重心 稳定不累', score: 95, num: 5, tag: '笔尖0.5mm', hot: false },
    { name: '无印经典凝胶笔', price: 10, desc: '简约设计 顺滑', score: 93, num: 5, tag: '笔尖0.5mm', hot: false },
    { name: '凌美2000钢笔', price: 1180, desc: '包豪斯设计 拉丝杆', score: 97, num: 2, tag: '笔尖EF尖', hot: false },
    { name: '百乐CUSTOM74', price: 788, desc: '14K金尖 透明杆', score: 97, num: 2, tag: '笔尖FM尖', hot: true }
  ]
  private papers: GoodsItem[] = [
    { name: '得力A4 70g整箱', price: 118, desc: '8包×500张 复印印', score: 95, num: 70, tag: '70g', hot: true },
    { name: 'Double A 80g', price: 158, desc: '泰国进口 高白度', score: 96, num: 80, tag: '80g', hot: false },
    { name: '百旺A4复印纸', price: 138, desc: '中性施胶 不卡纸', score: 95, num: 70, tag: '70g', hot: false },
    { name: '晨光A3加宽纸', price: 168, desc: '工程图纸打印', score: 93, num: 70, tag: '70g', hot: false },
    { name: '国誉Campus本B5', price: 15, desc: '横线30行 东大脑', score: 95, num: 26, tag: '26孔', hot: true },
    { name: '国誉淡彩曲奇本', price: 18, desc: '少女心配色 淡彩', score: 94, num: 26, tag: '26孔', hot: false },
    { name: '满乐文高级拍纸', price: 22, desc: '厚实书写纸', score: 93, num: 30, tag: '30孔', hot: false },
    { name: '康奈尔笔记本', price: 12, desc: '笔记法分区 记忆', score: 92, num: 26, tag: '26孔', hot: false },
    { name: '牛皮纸档案袋', price: 1, desc: '加厚5系 档案', score: 90, num: 5, tag: '档案', hot: false },
    { name: '复写联单三联', price: 8, desc: '无碳复写 一式三份', score: 91, num: 5, tag: '联单', hot: false }
  ]
  private desks: GoodsItem[] = [
    { name: '三层桌面收纳架', price: 45, desc: 'A4分格 侧挂笔筒', score: 93, num: 5, tag: '收纳', hot: true },
    { name: '亚克力抽屉柜', price: 69, desc: '透明可视 五层', score: 94, num: 5, tag: '透明', hot: false },
    { name: '洞洞板置物套装', price: 59, desc: '桌面小森林 铁艺', score: 92, num: 5, tag: '上墙', hot: false },
    { name: '旋转笔筒大容量', price: 29, desc: '360度转动 分格', score: 93, num: 5, tag: '笔筒', hot: false },
    { name: '文件框立式三层', price: 39, desc: '竖放文件 一目了然', score: 92, num: 5, tag: '文件', hot: true },
    { name: '桌面增高架竹木', price: 79, desc: '抬高视线下收纳', score: 94, num: 5, tag: '增高架', hot: false },
    { name: '理线夹磁吸贴', price: 19, desc: '告别线材缠绕', score: 90, num: 5, tag: '理线', hot: false },
    { name: '白板笔磁性套装', price: 25, desc: '可擦无味 四色', score: 92, num: 5, tag: '白板', hot: false },
    { name: '桌面小日历翻页', price: 15, desc: '台历 可记事', score: 91, num: 5, tag: '台历', hot: false },
    { name: '护眼台灯国AA级', price: 159, desc: '无频闪 自动调光', score: 95, num: 5, tag: '护眼', hot: false }
  ]
  private corps: GoodsItem[] = [
    { name: '企业中性笔定制', price: 2, desc: 'LOGO定制 千支起印', score: 95, num: 5, tag: '定制', hot: true },
    { name: 'A4纸企业直采价', price: 108, desc: '月结账期 免运费', score: 96, num: 70, tag: '直采', hot: false },
    { name: '办公椅批量采购', price: 799, desc: '10把起批发价', score: 95, num: 5, tag: '批发', hot: false },
    { name: '打印机租赁套餐', price: 199, desc: '月付含耗材 上门', score: 94, num: 5, tag: '租赁', hot: true },
    { name: '硒鼓墨盒集采', price: 129, desc: '原装耗材 直降15%', score: 94, num: 5, tag: '耗材', hot: false },
    { name: '绿植租摆服务', price: 299, desc: '月租含养护', score: 92, num: 5, tag: '绿植', hot: false },
    { name: '员工福利礼盒', price: 99, desc: '三档可选 开票', score: 93, num: 5, tag: '福利', hot: false },
    { name: '会议室白板定制', price: 399, desc: '尺寸定制 安装', score: 93, num: 5, tag: '定制', hot: false },
    { name: '碎纸机办公级', price: 599, desc: '4级保密 批量', score: 94, num: 5, tag: '保密', hot: false },
    { name: '考勤机人脸款', price: 899, desc: '企业版 云后台', score: 95, num: 5, tag: '考勤', hot: false }
  ]

  selName(): string {
    if (this.selectedItem === null) {
      return ''
    }
    return this.selectedItem.name
  }
  selDesc(): string {
    if (this.selectedItem === null) {
      return ''
    }
    return this.selectedItem.desc
  }
  selTag(): string {
    if (this.selectedItem === null) {
      return ''
    }
    return this.selectedItem.tag
  }
  selPrice(): number {
    if (this.selectedItem === null) {
      return 0
    }
    return this.selectedItem.price
  }
  selScore(): number {
    if (this.selectedItem === null) {
      return 0
    }
    return this.selectedItem.score
  }
  maxOfNum(list: GoodsItem[]): number {
    let m: number = 0
    for (let i = 0; i < list.length; i++) {
      if (list[i].num > m) {
        m = list[i].num
      }
    }
    return m
  }
  unreadCount(): number {
    let c: number = 0
    for (let i = 0; i < this.msgs.length; i++) {
      if (!this.msgs[i].read) {
        c++
      }
    }
    return c
  }

  @Builder buildHeader() {
    Column() {
      Row() {
        Column() {
          Text('京东办公文具馆').fontSize(18).fontColor(Color.White).fontWeight(FontWeight.Bold)
          Row() {
            Text('🏢').fontSize(11)
            Text('企业采购 · 对公转账 · 增值税开票').fontSize(10).fontColor('rgba(255,255,255,0.8)').margin({ left: 4 })
          }
          .margin({ top: 4 })
          .alignItems(VerticalAlign.Center)
        }
        .alignItems(HorizontalAlign.Start)
        .layoutWeight(1)

        Column() {
          Text('📄').fontSize(18)
          Text('开票').fontSize(9).fontColor('rgba(255,255,255,0.8)').margin({ top: 2 })
        }
        .width(44)
        .onClick(() => {
          this.selInvType = 0
          this.selTitle = 0
          this.showInvoice = true
        })

        Column() {
          Text('✉️').fontSize(18)
          if (this.unreadCount() > 0) {
            Text(this.unreadCount().toString())
              .fontSize(8)
              .fontColor(Color.White)
              .backgroundColor('#FF3B30')
              .borderRadius(7)
              .padding({ left: 4, right: 4 })
              .position({ x: 12, y: -4 })
              .scale({ x: 1.12, y: 1.12 })
              .animation({ duration: 650, iterations: -1, curve: Curve.EaseInOut })
          }
        }
        .width(44)
        .onClick(() => {
          this.currentTab = 5
        })
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 12, bottom: 12 })
      .alignItems(VerticalAlign.Center)

      Row() {
        Column() {
          Text('开学季 · 文具 5 折起').fontSize(11).fontColor('#E3F2FD')
          Text('企业会员再享折上 95 折').fontSize(10).fontColor('rgba(255,255,255,0.75)').margin({ top: 2 })
        }
        .alignItems(HorizontalAlign.Start)

        Column() {
          Text('领券').fontSize(11).fontColor('#283593').backgroundColor('#FFFFFF').borderRadius(12).padding({ left: 12, right: 12, top: 4, bottom: 4 })
            .scale({ x: 1.05, y: 1.05 })
            .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
        }
        .margin({ left: 12 })
      }
      .width('100%')
      .padding({ left: 16, right: 16, bottom: 12 })
      .justifyContent(FlexAlign.Start)
      .alignItems(VerticalAlign.Center)
    }
    .width('100%')
    .backgroundColor('#283593')
  }

  @Builder buildTab1() {
    Column() {
      Row() {
        Column() {
          Text('📚').fontSize(28)
          Column() {
            Text('开学焕新季').fontSize(15).fontColor(Color.White).fontWeight(FontWeight.Bold)
            Text('文具 5 折 · 笔记本买二送一').fontSize(10).fontColor('rgba(255,255,255,0.8)').margin({ top: 3 })
          }
          .alignItems(HorizontalAlign.Start)
          .margin({ left: 10 })
        }
        .layoutWeight(1)

        Column() {
          Text('368万+').fontSize(15).fontColor('#FF7043').fontWeight(FontWeight.Bold)
          Text('师生已选购').fontSize(9).fontColor('rgba(255,255,255,0.7)').margin({ top: 3 })
            .scale({ x: 1.05, y: 1.05 })
            .animation({ duration: 800, iterations: -1, curve: Curve.EaseInOut })
        }
        .alignItems(HorizontalAlign.End)
      }
      .width('100%')
      .backgroundColor('#3949AB')
      .borderRadius(14)
      .padding(16)
      .margin({ left: 12, right: 12, top: 12 })
      .alignItems(VerticalAlign.Center)

      Row() {
        Text('文具精选').fontSize(15).fontColor('#212121').fontWeight(FontWeight.Bold)
        Text('次日达 · 破损包退'.slice(0, 10)).fontSize(10).fontColor('#9AA0B5').margin({ left: 8 })
      }
      .width('100%')
      .padding({ left: 16, top: 14, bottom: 8 })
      .justifyContent(FlexAlign.Start)
      .alignItems(VerticalAlign.Center)

      ForEach(this.items, (g: GoodsItem, i: number) => {
        Row() {
          Column() {
            Text(i % 4 === 0 ? '✏️' : (i % 4 === 1 ? '🖊️' : (i % 4 === 2 ? '📄' : '🗄️'))).fontSize(26)
          }
          .width(72)
          .height(72)
          .borderRadius(12)
          .backgroundColor(i % 2 === 0 ? '#E8EAF6' : '#E3F2FD')
          .justifyContent(FlexAlign.Center)

          Column() {
            Text(g.name).fontSize(13).fontColor('#212121').fontWeight(FontWeight.Medium).maxLines(1)
            Text(g.desc).fontSize(10).fontColor('#8A8FA3').margin({ top: 3 }).maxLines(1)

            Row() {
              Text(g.tag).fontSize(9).fontColor('#283593').backgroundColor('#E8EAF6').borderRadius(4).padding({ left: 5, right: 5, top: 2, bottom: 2 })
              if (g.hot) {
                Text('回购率高'.slice(0, 4)).fontSize(9).fontColor(Color.White).backgroundColor('#FF3B30').borderRadius(4).padding({ left: 5, right: 5, top: 2, bottom: 2 }).margin({ left: 6 })
                  .scale({ x: 1.06, y: 1.06 })
                  .animation({ duration: 650, iterations: -1, curve: Curve.EaseInOut })
              }
            }
            .margin({ top: 5 })
            .alignItems(VerticalAlign.Center)

            Stack({ alignContent: Alignment.Start }) {
              Column().width('100%').height(6).backgroundColor('#F0F0F5').borderRadius(3)
              Column().width(g.score + '%').height(6).backgroundColor('#FF7043').borderRadius(3)
            }
            .width('100%')
            .margin({ top: 6 })
          }
          .layoutWeight(1)
          .alignItems(HorizontalAlign.Start)
          .margin({ left: 10 })

          Column() {
            Text('¥' + g.price).fontSize(16).fontColor('#D84315').fontWeight(FontWeight.Bold)
            Column() {
              Text('批量采购').fontSize(10).fontColor(Color.White)
            }
            .backgroundColor('#283593')
            .borderRadius(13)
            .padding({ left: 10, right: 10, top: 5, bottom: 5 })
            .margin({ top: 8 })
            .onClick(() => {
              this.selectedItem = g
              this.bulkNum = 100
              this.showBulk = true
            })
          }
          .alignItems(HorizontalAlign.End)
        }
        .width('100%')
        .backgroundColor(Color.White)
        .borderRadius(12)
        .padding(12)
        .margin({ left: 12, right: 12, bottom: 10 })
        .alignItems(VerticalAlign.Center)
        .onClick(() => {
          this.selectedItem = g
          this.showDetail = true
        })
      }, (g: GoodsItem) => g.name)
    }
    .width('100%')
    .padding({ bottom: 12 })
  }

  @Builder buildTab2() {
    Column() {
      Text('笔尖规格柱状图(mm×10)').fontSize(15).fontColor('#212121').fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ left: 16, top: 14, bottom: 8 })

      Column() {
        Row({ space: 10 }) {
          ForEach(this.pens, (g: GoodsItem) => {
            Column() {
              Column()
                .width(16)
                .height(g.num / this.maxOfNum(this.pens) * 100)
                .backgroundColor('#283593')
                .borderRadius(4)
              Text(g.tag.slice(2, 7)).fontSize(7).fontColor('#8A8FA3').margin({ top: 3 }).maxLines(1)
            }
            .alignItems(HorizontalAlign.Start)
          }, (g: GoodsItem) => g.name)
        }
        .width('100%')
      }
      .width('100%')
      .backgroundColor(Color.White)
      .borderRadius(12)
      .padding(14)
      .margin({ left: 12, right: 12 })
      .alignItems(HorizontalAlign.Start)

      Text('书写工具双列精选').fontSize(15).fontColor('#212121').fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ left: 16, top: 16, bottom: 8 })

      Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
        ForEach(this.pens, (g: GoodsItem, i: number) => {
          Column() {
            Row() {
              Column() {
                Text('🖊️').fontSize(22)
              }
              .width(44)
              .height(44)
              .borderRadius(22)
              .backgroundColor(i % 2 === 0 ? '#E8EAF6' : '#FFF3E0')
              .justifyContent(FlexAlign.Center)

              Column() {
                Text(g.tag).fontSize(9).fontColor('#283593').fontWeight(FontWeight.Bold)
                Text('好评 ' + g.score + '%').fontSize(9).fontColor('#8A8FA3').margin({ top: 3 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.End)
            }
            .width('100%')
            .alignItems(VerticalAlign.Center)

            Text(g.name).fontSize(12).fontColor('#212121').fontWeight(FontWeight.Medium).margin({ top: 8 }).maxLines(1)
            Text(g.desc).fontSize(9).fontColor('#8A8FA3').margin({ top: 3 }).maxLines(1)

            Row() {
              Text('¥' + g.price).fontSize(14).fontColor('#D84315').fontWeight(FontWeight.Bold)
              if (g.hot) {
                Text('编辑推荐').fontSize(9).fontColor(Color.White).backgroundColor('#FF3B30').borderRadius(4).padding({ left: 5, right: 5, top: 2, bottom: 2 }).margin({ left: 6 })
                  .scale({ x: 1.08, y: 1.08 })
                  .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
              }
            }
            .width('100%')
            .margin({ top: 6 })
            .justifyContent(FlexAlign.SpaceBetween)
            .alignItems(VerticalAlign.Center)
          }
          .width('48%')
          .backgroundColor(Color.White)
          .borderRadius(12)
          .padding(12)
          .margin({ bottom: 10 })
          .onClick(() => {
            this.selectedItem = g
            this.showDetail = true
          })
        }, (g: GoodsItem) => g.name)
      }
      .width('100%')
      .padding({ left: 12, right: 12 })
    }
    .width('100%')
    .padding({ bottom: 12 })
  }

  @Builder buildTab3() {
    Column() {
      Column() {
        Text('纸张克重分布柱状图(g)').fontSize(13).fontColor(Color.White).fontWeight(FontWeight.Bold)
        Row({ space: 10 }) {
          ForEach(this.papers, (g: GoodsItem) => {
            Column() {
              Column()
                .width(16)
                .height(g.num / this.maxOfNum(this.papers) * 100)
                .backgroundColor('#FFAB91')
                .borderRadius(4)
              Text(g.num.toString()).fontSize(8).fontColor('rgba(255,255,255,0.85)').margin({ top: 3 }).maxLines(1)
            }
            .alignItems(HorizontalAlign.Start)
          }, (g: GoodsItem) => g.name)
        }
        .width('100%')
        .margin({ top: 12 })
      }
      .width('100%')
      .backgroundColor('#1A237E')
      .borderRadius(14)
      .padding(14)
      .margin({ left: 12, right: 12, top: 12 })
      .alignItems(HorizontalAlign.Start)

      Text('口碑排行 · 复购进度').fontSize(15).fontColor('#212121').fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ left: 16, top: 16, bottom: 8 })

      Column() {
        ForEach(this.papers, (g: GoodsItem, i: number) => {
          Row() {
            Text((i + 1).toString()).fontSize(12).fontColor(Color.White).backgroundColor(i < 3 ? '#FF7043' : '#9AA0B5').borderRadius(10).width(20).height(20).textAlign(TextAlign.Center)

            Column() {
              Text(g.name).fontSize(12).fontColor('#212121').fontWeight(FontWeight.Medium).maxLines(1)
              Stack({ alignContent: Alignment.Start }) {
                Column().width('100%').height(7).backgroundColor('#F0F0F5').borderRadius(4)
                Column().width(g.score + '%').height(7).backgroundColor('#283593').borderRadius(4)
              }
              .width('100%')
              .margin({ top: 5 })
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Start)
            .margin({ left: 10 })

            Column() {
              Text('¥' + g.price).fontSize(13).fontColor('#D84315').fontWeight(FontWeight.Bold)
              if (g.hot) {
                Text('🔥 囤货王').fontSize(8).fontColor('#D84315').margin({ top: 3 })
                  .scale({ x: 1.1, y: 1.1 })
                  .animation({ duration: 600, iterations: -1, curve: Curve.EaseInOut })
              }
            }
            .alignItems(HorizontalAlign.End)
          }
          .width('100%')
          .alignItems(VerticalAlign.Center)
          .margin({ bottom: 12 })
          .onClick(() => {
            this.selectedItem = g
            this.showDetail = true
          })
        }, (g: GoodsItem) => g.name)
      }
      .width('100%')
      .backgroundColor(Color.White)
      .borderRadius(12)
      .padding(14)
      .margin({ left: 12, right: 12 })

      Text('本册速览 · 横滑'.slice(0, 8)).fontSize(15).fontColor('#212121').fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ left: 16, top: 16, bottom: 8 })

      Scroll() {
        Row({ space: 10 }) {
          ForEach(this.papers, (g: GoodsItem, i: number) => {
            Column() {
              Column() {
                Text('📄').fontSize(28)
              }
              .width(76)
              .height(76)
              .borderRadius(12)
              .backgroundColor(i % 2 === 0 ? '#E3F2FD' : '#E8EAF6')
              .justifyContent(FlexAlign.Center)

              Text(g.name).fontSize(11).fontColor('#212121').margin({ top: 8 }).maxLines(1)
              Text('¥' + g.price).fontSize(13).fontColor('#D84315').fontWeight(FontWeight.Bold).margin({ top: 4 })
            }
            .width(112)
            .backgroundColor(Color.White)
            .borderRadius(12)
            .padding(10)
            .onClick(() => {
              this.selectedItem = g
              this.showDetail = true
            })
          }, (g: GoodsItem) => g.name)
        }
        .padding({ left: 12, right: 12 })
      }
      .scrollable(ScrollDirection.Horizontal)
      .scrollBar(BarState.Off)
      .width('100%')
    }
    .width('100%')
    .padding({ bottom: 12 })
  }

  @Builder buildTab4() {
    Column() {
      Text('桌面收纳专区').fontSize(15).fontColor('#212121').fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ left: 16, top: 14, bottom: 8 })

      Column() {
        ForEach(this.desks, (g: GoodsItem, i: number) => {
          Row() {
            Column()
              .width(4)
              .height(48)
              .backgroundColor(i % 4 === 0 ? '#283593' : (i % 4 === 1 ? '#FF7043' : (i % 4 === 2 ? '#00897B' : '#5C6BC0')))
              .borderRadius(2)

            Column() {
              Row() {
                Text(g.name).fontSize(13).fontColor('#212121').fontWeight(FontWeight.Medium).maxLines(1)
                Text(g.tag).fontSize(9).fontColor('#00897B').backgroundColor('#E0F2F1').borderRadius(4).padding({ left: 5, right: 5, top: 2, bottom: 2 }).margin({ left: 6 })
                if (g.hot) {
                  Text('热销').fontSize(9).fontColor(Color.White).backgroundColor('#FF3B30').borderRadius(4).padding({ left: 5, right: 5, top: 2, bottom: 2 }).margin({ left: 6 })
                    .scale({ x: 1.08, y: 1.08 })
                    .animation({ duration: 650, iterations: -1, curve: Curve.EaseInOut })
                }
              }
              .alignItems(VerticalAlign.Center)

              Text(g.desc).fontSize(10).fontColor('#8A8FA3').margin({ top: 3 }).maxLines(1)
              Text('好评率 ' + g.score + '%'.slice(0, 1) + ')').fontSize(9).fontColor('#9AA0B5').margin({ top: 3 })
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Start)
            .margin({ left: 10 })

            Column() {
              Text('¥' + g.price).fontSize(15).fontColor('#D84315').fontWeight(FontWeight.Bold)
              Column() {
                Text('查看详情'.slice(0, 4)).fontSize(9).fontColor('#283593')
              }
              .borderRadius(11)
              .border({ width: 1, color: '#283593' })
              .padding({ left: 8, right: 8, top: 3, bottom: 3 })
              .margin({ top: 6 })
              .onClick(() => {
                this.selectedItem = g
                this.showDetail = true
              })
            }
            .alignItems(HorizontalAlign.End)
          }
          .width('100%')
          .backgroundColor(Color.White)
          .borderRadius(12)
          .padding(12)
          .margin({ bottom: 10 })
          .alignItems(VerticalAlign.Center)
          .onClick(() => {
            this.selectedItem = g
            this.showDetail = true
          })
        }, (g: GoodsItem) => g.name)
      }
      .width('100%')
      .padding({ left: 12, right: 12 })

      Text('好评分段柱状图'.slice(0, 7)).fontSize(15).fontColor('#212121').fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ left: 16, top: 10, bottom: 8 })

      Column() {
        Row({ space: 12 }) {
          ForEach(this.desks, (g: GoodsItem) => {
            Column() {
              Column()
                .width(16)
                .height((g.score - 85) / 10 * 90)
                .backgroundColor('#00897B')
                .borderRadius(4)
              Text(g.score.toString()).fontSize(8).fontColor('#8A8FA3').margin({ top: 3 }).maxLines(1)
            }
            .alignItems(HorizontalAlign.Start)
          }, (g: GoodsItem) => g.name)
        }
        .width('100%')
      }
      .width('100%')
      .backgroundColor(Color.White)
      .borderRadius(12)
      .padding(14)
      .margin({ left: 12, right: 12, top: 4 })
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .padding({ bottom: 12 })
  }

  @Builder buildTab5() {
    Column() {
      Column() {
        Row() {
          Column() {
            Text('企业采购中心').fontSize(17).fontColor(Color.White).fontWeight(FontWeight.Bold)
            Text('对公转账 · 月结账期 · 专属客户经理'.slice(0, 15)).fontSize(10).fontColor('rgba(255,255,255,0.75)').margin({ top: 4 })
          }
          .alignItems(HorizontalAlign.Start)
          .layoutWeight(1)

          Text('🏢').fontSize(32)
            .scale({ x: 1.08, y: 1.08 })
            .animation({ duration: 800, iterations: -1, curve: Curve.EaseInOut })
        }
        .width('100%')
        .alignItems(VerticalAlign.Center)

        Row() {
          Column() {
            Text('86%').fontSize(22).fontColor('#FFAB91').fontWeight(FontWeight.Bold)
            Text('综合好评').fontSize(10).fontColor('rgba(255,255,255,0.7)').margin({ top: 2 })
          }
          .layoutWeight(1)
          .alignItems(HorizontalAlign.Start)

          Column() {
            Text('3200+').fontSize(22).fontColor('#FFF59D').fontWeight(FontWeight.Bold).margin({ top: 2 })
              .scale({ x: 1.04, y: 1.04 })
              .animation({ duration: 800, iterations: -1, curve: Curve.EaseInOut })
            Text('服务企业').fontSize(10).fontColor('rgba(255,255,255,0.7)').margin({ top: 2 })
          }
          .layoutWeight(1)
          .alignItems(HorizontalAlign.Start)

          Column() {
            Text('T+7').fontSize(22).fontColor('#80CBC4').fontWeight(FontWeight.Bold).margin({ top: 2 })
            Text('最快账期').fontSize(10).fontColor('rgba(255,255,255,0.7)').margin({ top: 2 })
          }
          .layoutWeight(1)
          .alignItems(HorizontalAlign.Start)
        }
        .width('100%')
        .margin({ top: 16 })
        .alignItems(VerticalAlign.Top)
      }
      .width('100%')
      .backgroundColor('#283593')
      .borderRadius(14)
      .padding(18)
      .margin({ left: 12, right: 12, top: 12 })
      .alignItems(HorizontalAlign.Start)

      Text('集采服务项目'.slice(0, 6)).fontSize(15).fontColor('#212121').fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ left: 16, top: 16, bottom: 8 })

      Column() {
        ForEach(this.corps, (g: GoodsItem, i: number) => {
          Row() {
            Column() {
              Text(i % 5 === 0 ? '🏢' : (i % 5 === 1 ? '🧾' : (i % 5 === 2 ? '🖨️' : (i % 5 === 3 ? '🪴' : '🪑')))).fontSize(24)
            }
            .width(52)
            .height(52)
            .borderRadius(12)
            .backgroundColor(i % 2 === 0 ? '#E8EAF6' : '#E0F2F1')
            .justifyContent(FlexAlign.Center)

            Column() {
              Row() {
                Text(g.name).fontSize(13).fontColor('#212121').fontWeight(FontWeight.Medium).maxLines(1)
                Text(g.tag).fontSize(9).fontColor('#283593').backgroundColor('#E8EAF6').borderRadius(4).padding({ left: 5, right: 5, top: 2, bottom: 2 }).margin({ left: 6 })
                if (g.hot) {
                  Text('客户点赞'.slice(0, 4)).fontSize(9).fontColor(Color.White).backgroundColor('#FF3B30').borderRadius(4).padding({ left: 5, right: 5, top: 2, bottom: 2 }).margin({ left: 6 })
                    .scale({ x: 1.08, y: 1.08 })
                    .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
                }
              }
              .alignItems(VerticalAlign.Center)

              Text(g.desc).fontSize(10).fontColor('#8A8FA3').margin({ top: 3 }).maxLines(1)
              Text('好评率 ' + g.score + '% · 已合作 ' + g.num * 36 + ' 家').fontSize(9).fontColor('#9AA0B5').margin({ top: 3 })
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Start)
            .margin({ left: 10 })

            Column() {
              Text('¥' + g.price + ' 起').fontSize(13).fontColor('#D84315').fontWeight(FontWeight.Bold)
              Column() {
                Text('批量采购'.slice(0, 4)).fontSize(10).fontColor(Color.White)
              }
              .backgroundColor('#283593')
              .borderRadius(13)
              .padding({ left: 10, right: 10, top: 5, bottom: 5 })
              .margin({ top: 6 })
              .onClick(() => {
                this.selectedItem = g
                this.bulkNum = 100
                this.showBulk = true
              })
            }
            .alignItems(HorizontalAlign.End)
          }
          .width('100%')
          .backgroundColor(Color.White)
          .borderRadius(12)
          .padding(12)
          .margin({ bottom: 10 })
          .alignItems(VerticalAlign.Center)
        }, (g: GoodsItem) => g.name)
      }
      .width('100%')
      .padding({ left: 12, right: 12 })
    }
    .width('100%')
    .padding({ bottom: 12 })
  }

  @Builder buildTab6() {
    Column() {
      Row() {
        Text('消息中心').fontSize(16).fontColor('#212121').fontWeight(FontWeight.Bold)
        Text('未读 ' + this.unreadCount() + ' 条').fontSize(11).fontColor('#FF3B30').margin({ left: 8 })
          .scale({ x: 1.05, y: 1.05 })
          .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 14, bottom: 10 })
      .justifyContent(FlexAlign.Start)
      .alignItems(VerticalAlign.Center)

      ForEach(this.msgs, (m: MsgItem, i: number) => {
        Row() {
          Column() {
            Text(m.read ? '📭' : '📎').fontSize(20)
            if (!m.read) {
              Column()
                .width(8)
                .height(8)
                .borderRadius(4)
                .backgroundColor('#FF3B30')
                .position({ x: 30, y: -2 })
                .scale({ x: 1.2, y: 1.2 })
                .animation({ duration: 600, iterations: -1, curve: Curve.EaseInOut })
            }
          }
          .width(44)

          Column() {
            Row() {
              Text(m.title).fontSize(13).fontColor(m.read ? '#5C6280' : '#212121').fontWeight(m.read ? FontWeight.Normal : FontWeight.Bold).maxLines(1)
              Text(m.time).fontSize(9).fontColor('#9AA0B5').margin({ left: 6 })
            }
            .width('100%')
            .justifyContent(FlexAlign.SpaceBetween)
            .alignItems(VerticalAlign.Center)

            Text(m.content).fontSize(11).fontColor('#8A8FA3').margin({ top: 4 }).maxLines(2)
          }
          .layoutWeight(1)
          .alignItems(HorizontalAlign.Start)
          .margin({ left: 8 })

          Column() {
            Text('删除').fontSize(10).fontColor('#9AA0B5')
          }
          .padding({ left: 8, right: 8, top: 4, bottom: 4 })
          .border({ width: 1, color: '#E5E5EC' })
          .borderRadius(10)
          .onClick(() => {
            this.selectedItem = null
            this.deleteIndex = i
            this.showDelete = true
          })
        }
        .width('100%')
        .backgroundColor(Color.White)
        .borderRadius(12)
        .padding(12)
        .margin({ left: 12, right: 12, bottom: 10 })
        .alignItems(VerticalAlign.Center)
      }, (m: MsgItem) => m.title + m.time)
    }
    .width('100%')
    .padding({ bottom: 12 })
  }

  @Builder buildTab7() {
    Column() {
      Row() {
        Column() {
          Text('🏢').fontSize(34)
        }
        .width(64)
        .height(64)
        .borderRadius(32)
        .backgroundColor('#E8EAF6')
        .justifyContent(FlexAlign.Center)

        Column() {
          Text('行政小管家').fontSize(16).fontColor('#212121').fontWeight(FontWeight.Bold)
          Row() {
            Text('企业会员').fontSize(9).fontColor('#283593').backgroundColor('#E8EAF6').borderRadius(4).padding({ left: 5, right: 5, top: 2, bottom: 2 })
            Text('累计采购 ¥28.6万'.slice(0, 12)).fontSize(10).fontColor('#9AA0B5').margin({ left: 8 })
          }
          .margin({ top: 6 })
          .alignItems(VerticalAlign.Center)
        }
        .layoutWeight(1)
        .alignItems(HorizontalAlign.Start)
        .margin({ left: 12 })

        Text('›').fontSize(20).fontColor('#C5C9D9')
      }
      .width('100%')
      .backgroundColor(Color.White)
      .borderRadius(14)
      .padding(16)
      .margin({ left: 12, right: 12, top: 12 })
      .alignItems(VerticalAlign.Center)

      Row() {
        Column() {
          Text('4').fontSize(18).fontColor('#283593').fontWeight(FontWeight.Bold)
          Text('待审批').fontSize(10).fontColor('#8A8FA3').margin({ top: 3 })
        }
        .layoutWeight(1)

        Column() {
          Text('26').fontSize(18).fontColor('#FF7043').fontWeight(FontWeight.Bold)
          Text('采购单').fontSize(10).fontColor('#8A8FA3').margin({ top: 3 })
        }
        .layoutWeight(1)

        Column() {
          Text('8').fontSize(18).fontColor('#00897B').fontWeight(FontWeight.Bold)
          Text('待开票').fontSize(10).fontColor('#8A8FA3').margin({ top: 3 })
        }
        .layoutWeight(1)

        Column() {
          Text('2').fontSize(18).fontColor('#D84315').fontWeight(FontWeight.Bold)
          Text('优惠券').fontSize(10).fontColor('#8A8FA3').margin({ top: 3 })
            .scale({ x: 1.08, y: 1.08 })
            .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
        }
        .layoutWeight(1)
      }
      .width('100%')
      .backgroundColor(Color.White)
      .borderRadius(14)
      .padding({ top: 14, bottom: 14 })
      .margin({ left: 12, right: 12, top: 12 })

      Column() {
        Row() {
          Text('🧾').fontSize(18)
          Text('采购订单').fontSize(13).fontColor('#212121').margin({ left: 10 })
          Text('全部 26 ›'.slice(0, 9)).fontSize(11).fontColor('#9AA0B5')
        }
        .width('100%')
        .padding({ top: 12, bottom: 12 })
        .justifyContent(FlexAlign.SpaceBetween)
        .alignItems(VerticalAlign.Center)

        Row() {
          Text('📄').fontSize(18)
          Text('发票管理').fontSize(13).fontColor('#212121').margin({ left: 10 })
          Text('8 张待开票 ›'.slice(0, 9)).fontSize(11).fontColor('#D84315')
        }
        .width('100%')
        .padding({ top: 12, bottom: 12 })
        .justifyContent(FlexAlign.SpaceBetween)
        .alignItems(VerticalAlign.Center)

        Row() {
          Text('👥').fontSize(18)
          Text('员工领用台账').fontSize(13).fontColor('#212121').margin({ left: 10 })
          Text('本月 86 件 ›'.slice(0, 9)).fontSize(11).fontColor('#9AA0B5')
        }
        .width('100%')
        .padding({ top: 12, bottom: 12 })
        .justifyContent(FlexAlign.SpaceBetween)
        .alignItems(VerticalAlign.Center)

        Row() {
          Text('📊').fontSize(18)
          Text('采购月度报表'.slice(0, 6)).fontSize(13).fontColor('#212121').margin({ left: 10 })
          Text('8 月已生成 ›'.slice(0, 8)).fontSize(11).fontColor('#9AA0B5')
        }
        .width('100%')
        .padding({ top: 12, bottom: 12 })
        .justifyContent(FlexAlign.SpaceBetween)
        .alignItems(VerticalAlign.Center)

        Row() {
          Text('⚙️').fontSize(18)
          Text('设置').fontSize(13).fontColor('#212121').margin({ left: 10 })
          Text('›').fontSize(14).fontColor('#C5C9D9')
        }
        .width('100%')
        .padding({ top: 12, bottom: 12 })
        .justifyContent(FlexAlign.SpaceBetween)
        .alignItems(VerticalAlign.Center)
      }
      .width('100%')
      .backgroundColor(Color.White)
      .borderRadius(14)
      .padding({ left: 16, right: 16 })
      .margin({ left: 12, right: 12, top: 12 })

      Column() {
        Text('闲置办公用品转让给需要的团队'.slice(0, 14)).fontSize(12).fontColor('#8A8FA3')
        Column() {
          Text('发布闲置文具').fontSize(13).fontColor(Color.White).fontWeight(FontWeight.Medium)
        }
        .width(180)
        .height(40)
        .backgroundColor('#283593')
        .borderRadius(20)
        .justifyContent(FlexAlign.Center)
        .margin({ top: 12 })
        .scale({ x: 1.04, y: 1.04 })
        .animation({ duration: 800, iterations: -1, curve: Curve.EaseInOut })
        .onClick(() => {
          this.selCat = 0
          this.selUse = 0
          this.showPublish = true
        })
      }
      .width('100%')
      .padding({ top: 20, bottom: 20 })
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .padding({ bottom: 12 })
  }

  @Builder buildBottom() {
    Row() {
      ForEach(this.tabs, (t: TabInfo, i: number) => {
        Column() {
          Text(t.icon).fontSize(18)
          Text(t.name).fontSize(9).fontColor(this.currentTab === i ? '#283593' : '#9AA0B5').margin({ top: 2 })
        }
        .layoutWeight(1)
        .padding({ top: 6, bottom: 6 })
        .justifyContent(FlexAlign.Center)
        .onClick(() => {
          this.currentTab = i
        })
      }, (t: TabInfo) => t.name)
    }
    .width('100%')
    .backgroundColor(Color.White)
    .border({ width: 1, color: '#EDEDF2' })
  }

  @Builder publishModal() {
    Column() {
      Column() {
        Row() {
          Text('发布闲置文具').fontSize(16).fontColor('#212121').fontWeight(FontWeight.Bold)
          Text('✕').fontSize(16).fontColor('#9AA0B5')
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        .alignItems(VerticalAlign.Center)

        Column() {
          Text('物品描述').fontSize(12).fontColor('#5C6280')
          TextInput({ placeholder: '如:凌美狩猎者钢笔 EF尖 九成新' })
            .height(40)
            .backgroundColor('#F5F6FA')
            .borderRadius(8)
            .margin({ top: 6 })
            .fontSize(12)
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 16 })

        Column() {
          Text('物品品类').fontSize(12).fontColor('#5C6280')
          Flex({ wrap: FlexWrap.Wrap }) {
            ForEach(this.cats, (c: string, i: number) => {
              Text(c).fontSize(11).fontColor(this.selCat === i ? Color.White : '#5C6280')
                .backgroundColor(this.selCat === i ? '#283593' : '#F5F6FA')
                .borderRadius(13)
                .padding({ left: 12, right: 12, top: 6, bottom: 6 })
                .margin({ right: 8, top: 8 })
                .onClick(() => {
                  this.selCat = i
                })
            }, (c: string) => c)
          }
          .width('100%')
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 14 })

        Column() {
          Text('适用场景'.slice(0, 4)).fontSize(12).fontColor('#5C6280')
          Flex({ wrap: FlexWrap.Wrap }) {
            ForEach(this.uses, (u: string, i: number) => {
              Text(u).fontSize(11).fontColor(this.selUse === i ? Color.White : '#5C6280')
                .backgroundColor(this.selUse === i ? '#00897B' : '#F5F6FA')
                .borderRadius(13)
                .padding({ left: 12, right: 12, top: 6, bottom: 6 })
                .margin({ right: 8, top: 8 })
                .onClick(() => {
                  this.selUse = i
                })
            }, (u: string) => u)
          }
          .width('100%')
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 14 })

        Row() {
          Column() {
            Text('暂不发布'.slice(0, 4)).fontSize(13).fontColor('#5C6280')
          }
          .layoutWeight(1)
          .height(40)
          .backgroundColor('#F5F6FA')
          .borderRadius(20)
          .justifyContent(FlexAlign.Center)
          .onClick(() => {
            this.showPublish = false
          })

          Column() {
            Text('确认发布'.slice(0, 4)).fontSize(13).fontColor(Color.White).fontWeight(FontWeight.Medium)
          }
          .layoutWeight(1.6)
          .height(40)
          .backgroundColor('#283593')
          .borderRadius(20)
          .justifyContent(FlexAlign.Center)
          .margin({ left: 10 })
          .scale({ x: 1.02, y: 1.02 })
          .animation({ duration: 800, iterations: -1, curve: Curve.EaseInOut })
          .onClick(() => {
            this.showPublish = false
          })
        }
        .width('100%')
        .margin({ top: 20 })
        .alignItems(VerticalAlign.Center)
      }
      .width('92%')
      .backgroundColor(Color.White)
      .borderRadius(16)
      .padding(16)
      .constraintSize({ maxHeight: '80%' })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('rgba(0,0,0,0.5)')
    .justifyContent(FlexAlign.Center)
    .onClick(() => {
      this.showPublish = false
    })
  }

  @Builder bulkModal() {
    Column() {
      Column() {
        Row() {
          Column() {
            Text('批量采购询价'.slice(0, 6)).fontSize(16).fontColor('#212121').fontWeight(FontWeight.Bold)
            Text('量大从优 · 免费开票 · 月结可选'.slice(0, 14)).fontSize(10).fontColor('#283593').margin({ top: 3 })
          }
          .alignItems(HorizontalAlign.Start)

          Text('✕').fontSize(16).fontColor('#9AA0B5')
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        .alignItems(VerticalAlign.Center)

        Column() {
          Text(this.selName()).fontSize(13).fontColor('#212121').fontWeight(FontWeight.Medium).maxLines(1)
          Text(this.selDesc()).fontSize(10).fontColor('#8A8FA3').margin({ top: 3 })
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 14 })

        Column() {
          Text('规格选择'.slice(0, 4)).fontSize(12).fontColor('#5C6280')
          Flex({ wrap: FlexWrap.Wrap }) {
            ForEach(this.specs, (s: string, i: number) => {
              Text(s).fontSize(11).fontColor(this.selSpec === i ? Color.White : '#5C6280')
                .backgroundColor(this.selSpec === i ? '#283593' : '#F5F6FA')
                .borderRadius(13)
                .padding({ left: 12, right: 12, top: 6, bottom: 6 })
                .margin({ right: 8, top: 8 })
                .onClick(() => {
                  this.selSpec = i
                })
            }, (s: string) => s)
          }
          .width('100%')
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 14 })

        Row() {
          Column() {
            Text('采购数量(件)'.slice(0, 7)).fontSize(11).fontColor('#5C6280')
            Row() {
              Text('-').fontSize(15).fontColor('#283593').width(28).height(28).textAlign(TextAlign.Center).backgroundColor('#F5F6FA').borderRadius(8)
                .onClick(() => {
                  if (this.bulkNum > 10) {
                    this.bulkNum = this.bulkNum - 10
                  }
                })
              Text(this.bulkNum.toString()).fontSize(13).fontColor('#212121').fontWeight(FontWeight.Medium).width(60).textAlign(TextAlign.Center)
              Text('+').fontSize(15).fontColor('#283593').width(28).height(28).textAlign(TextAlign.Center).backgroundColor('#F5F6FA').borderRadius(8)
                .onClick(() => {
                  if (this.bulkNum < 5000) {
                    this.bulkNum = this.bulkNum + 10
                  }
                })
            }
            .margin({ top: 6 })
            .alignItems(VerticalAlign.Center)
          }
          .alignItems(HorizontalAlign.Start)

          Column() {
            Text('预估总价'.slice(0, 4)).fontSize(11).fontColor('#5C6280')
            Text('¥' + (this.selPrice() * this.bulkNum)).fontSize(22).fontColor('#D84315').fontWeight(FontWeight.Bold).margin({ top: 6 })
              .scale({ x: 1.05, y: 1.05 })
              .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
            Text('已享批量 92 折'.slice(0, 9)).fontSize(9).fontColor('#2E7D32').margin({ top: 3 })
          }
          .alignItems(HorizontalAlign.End)
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        .margin({ top: 16 })
        .alignItems(VerticalAlign.Top)

        Row() {
          Column() {
            Text('仅询价'.slice(0, 3)).fontSize(11).fontColor('#8A8FA3')
          }
          .padding({ left: 10, right: 10, top: 5, bottom: 5 })
          .border({ width: 1, color: '#E5E5EC' })
          .borderRadius(12)
          .onClick(() => {
            this.showBulk = false
          })

          Column() {
            Text('提交采购单'.slice(0, 5)).fontSize(13).fontColor(Color.White).fontWeight(FontWeight.Medium)
          }
          .layoutWeight(1)
          .height(40)
          .backgroundColor('#283593')
          .borderRadius(20)
          .justifyContent(FlexAlign.Center)
          .margin({ left: 10 })
          .onClick(() => {
            this.showBulk = false
          })
        }
        .width('100%')
        .margin({ top: 20 })
        .alignItems(VerticalAlign.Center)
      }
      .width('100%')
      .backgroundColor(Color.White)
      .borderRadius(16)
      .padding(16)
      .constraintSize({ maxHeight: '80%' })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('rgba(0,0,0,0.5)')
    .justifyContent(FlexAlign.End)
    .onClick(() => {
      this.showBulk = false
    })
  }

  @Builder invoiceModal() {
    Column() {
      Column() {
        Row() {
          Column() {
            Text('开票申请').fontSize(16).fontColor('#212121').fontWeight(FontWeight.Bold)
            Text('电子发票 30 秒极速开具'.slice(0, 12)).fontSize(10).fontColor('#00897B').margin({ top: 3 })
          }
          .alignItems(HorizontalAlign.Start)

          Text('✕').fontSize(16).fontColor('#9AA0B5')
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        .alignItems(VerticalAlign.Center)

        Column() {
          Text('发票类型'.slice(0, 4)).fontSize(12).fontColor('#5C6280')
          Flex({ wrap: FlexWrap.Wrap }) {
            ForEach(this.invTypes, (t: string, i: number) => {
              Text(t).fontSize(11).fontColor(this.selInvType === i ? Color.White : '#5C6280')
                .backgroundColor(this.selInvType === i ? '#283593' : '#F5F6FA')
                .borderRadius(13)
                .padding({ left: 12, right: 12, top: 6, bottom: 6 })
                .margin({ right: 8, top: 8 })
                .onClick(() => {
                  this.selInvType = i
                })
            }, (t: string) => t)
          }
          .width('100%')
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 16 })

        Column() {
          Text('发票抬头'.slice(0, 4)).fontSize(12).fontColor('#5C6280')
          Flex({ wrap: FlexWrap.Wrap }) {
            ForEach(this.titles, (t: string, i: number) => {
              Text(t).fontSize(11).fontColor(this.selTitle === i ? Color.White : '#5C6280')
                .backgroundColor(this.selTitle === i ? '#00897B' : '#F5F6FA')
                .borderRadius(13)
                .padding({ left: 12, right: 12, top: 6, bottom: 6 })
                .margin({ right: 8, top: 8 })
                .onClick(() => {
                  this.selTitle = i
                })
            }, (t: string) => t)
          }
          .width('100%')
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 14 })

        Column() {
          Text('开票金额区间'.slice(0, 6)).fontSize(12).fontColor('#5C6280')
          Row({ space: 8 }) {
            Text('¥100-500').fontSize(11).fontColor('#5C6280').backgroundColor('#F5F6FA').borderRadius(10).padding({ left: 10, right: 10, top: 6, bottom: 6 })
            Text('¥500-2000').fontSize(11).fontColor('#5C6280').backgroundColor('#F5F6FA').borderRadius(10).padding({ left: 10, right: 10, top: 6, bottom: 6 })
            Text('¥2000+').fontSize(11).fontColor(Color.White).backgroundColor('#D84315').borderRadius(10).padding({ left: 10, right: 10, top: 6, bottom: 6 }).margin({ left: 8 })
              .scale({ x: 1.08, y: 1.08 })
              .animation({ duration: 650, iterations: -1, curve: Curve.EaseInOut })
          }
          .margin({ top: 8 })
          .alignItems(VerticalAlign.Center)
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 14 })

        Row() {
          Column() {
            Text('取消'.slice(0, 2)).fontSize(13).fontColor('#5C6280')
          }
          .layoutWeight(1)
          .height(40)
          .backgroundColor('#F5F6FA')
          .borderRadius(20)
          .justifyContent(FlexAlign.Center)
          .onClick(() => {
            this.showInvoice = false
          })

          Column() {
            Text('提交开票'.slice(0, 4)).fontSize(13).fontColor(Color.White).fontWeight(FontWeight.Medium)
          }
          .layoutWeight(1.6)
          .height(40)
          .backgroundColor('#00897B')
          .borderRadius(20)
          .justifyContent(FlexAlign.Center)
          .margin({ left: 10 })
          .onClick(() => {
            this.showInvoice = false
          })
        }
        .width('100%')
        .margin({ top: 20 })
        .alignItems(VerticalAlign.Center)
      }
      .width('92%')
      .backgroundColor(Color.White)
      .borderRadius(16)
      .padding(16)
      .constraintSize({ maxHeight: '80%' })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('rgba(0,0,0,0.5)')
    .justifyContent(FlexAlign.Center)
    .onClick(() => {
      this.showInvoice = false
    })
  }

  @Builder deleteModal() {
    Column() {
      Column() {
        Text('🗑️').fontSize(38)
          .scale({ x: 1.12, y: 1.12 })
          .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
        Text('确认删除这条消息?'.slice(0, 11)).fontSize(15).fontColor('#212121').fontWeight(FontWeight.Bold).margin({ top: 12 })
        Text('删除后该通知将无法恢复'.slice(0, 12)).fontSize(11).fontColor('#9AA0B5').margin({ top: 6 })

        Row() {
          Column() {
            Text('再想想'.slice(0, 3)).fontSize(13).fontColor('#5C6280')
          }
          .layoutWeight(1)
          .height(40)
          .backgroundColor('#F5F6FA')
          .borderRadius(20)
          .justifyContent(FlexAlign.Center)
          .onClick(() => {
            this.showDelete = false
          })

          Column() {
            Text('确认删除'.slice(0, 4)).fontSize(13).fontColor(Color.White)
          }
          .layoutWeight(1)
          .height(40)
          .backgroundColor('#D84315')
          .borderRadius(20)
          .justifyContent(FlexAlign.Center)
          .margin({ left: 10 })
          .onClick(() => {
            if (this.deleteIndex >= 0 && this.deleteIndex < this.msgs.length) {
              this.msgs.splice(this.deleteIndex, 1)
            }
            this.deleteIndex = -1
            this.showDelete = false
          })
        }
        .width('100%')
        .margin({ top: 20 })
        .alignItems(VerticalAlign.Center)
      }
      .width('76%')
      .backgroundColor(Color.White)
      .borderRadius(16)
      .padding(20)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('rgba(0,0,0,0.5)')
    .justifyContent(FlexAlign.Center)
    .onClick(() => {
      this.showDelete = false
    })
  }

  @Builder detailModal() {
    Column() {
      Column() {
        Column() {
          Column() {
            Text('✏️').fontSize(40)
          }
          .width(96)
          .height(96)
          .borderRadius(16)
          .backgroundColor('#E8EAF6')
          .justifyContent(FlexAlign.Center)

          Text(this.selName()).fontSize(16).fontColor('#212121').fontWeight(FontWeight.Bold).margin({ top: 12 }).maxLines(2)
          Text(this.selDesc()).fontSize(11).fontColor('#8A8FA3').margin({ top: 6 }).maxLines(2)
          Row() {
            Text(this.selTag()).fontSize(10).fontColor('#283593').backgroundColor('#E8EAF6').borderRadius(4).padding({ left: 6, right: 6, top: 3, bottom: 3 })
            Text('好评 ' + this.selScore() + '%').fontSize(10).fontColor('#2E7D32').margin({ left: 8 })
          }
          .margin({ top: 8 })
          .alignItems(VerticalAlign.Center)
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)

        Column() {
          Text('商品参数'.slice(0, 4)).fontSize(13).fontColor('#212121').fontWeight(FontWeight.Bold)
          Row() {
            Text('发货时效').fontSize(11).fontColor('#8A8FA3')
            Text('次日达 · 假一赔十'.slice(0, 9)).fontSize(11).fontColor('#212121').margin({ left: 8 })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .margin({ top: 10 })
          Row() {
            Text('售后政策'.slice(0, 4)).fontSize(11).fontColor('#8A8FA3')
            Text('7天无理由 · 破损包退'.slice(0, 11)).fontSize(11).fontColor('#212121').margin({ left: 8 })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .margin({ top: 8 })
          Row() {
            Text('开票支持'.slice(0, 4)).fontSize(11).fontColor('#8A8FA3')
            Text('普票 / 专票均可'.slice(0, 9)).fontSize(11).fontColor('#212121').margin({ left: 8 })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .margin({ top: 8 })
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 18 })

        Row() {
          Text('¥' + this.selPrice()).fontSize(22).fontColor('#D84315').fontWeight(FontWeight.Bold)
            .scale({ x: 1.05, y: 1.05 })
            .animation({ duration: 700, iterations: -1, curve: Curve.EaseInOut })
          Text('满99减15'.slice(0, 6)).fontSize(10).fontColor(Color.White).backgroundColor('#FF3B30').borderRadius(4).padding({ left: 5, right: 5, top: 2, bottom: 2 }).margin({ left: 8 })
        }
        .width('100%')
        .margin({ top: 16 })
        .alignItems(VerticalAlign.Center)

        Row() {
          Column() {
            Text('申请开票'.slice(0, 4)).fontSize(12).fontColor('#00897B')
          }
          .layoutWeight(1)
          .height(38)
          .borderRadius(19)
          .border({ width: 1, color: '#00897B' })
          .justifyContent(FlexAlign.Center)
          .onClick(() => {
            this.showDetail = false
            this.showInvoice = true
          })

          Column() {
            Text('批量采购'.slice(0, 4)).fontSize(12).fontColor(Color.White)
          }
          .layoutWeight(1)
          .height(38)
          .backgroundColor('#283593')
          .borderRadius(19)
          .justifyContent(FlexAlign.Center)
          .margin({ left: 10 })
          .onClick(() => {
            this.showDetail = false
            this.showBulk = true
          })
        }
        .width('100%')
        .margin({ top: 18 })
        .alignItems(VerticalAlign.Center)
      }
      .width('80%')
      .height('100%')
      .backgroundColor(Color.White)
      .padding(16)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('rgba(0,0,0,0.5)')
    .justifyContent(FlexAlign.End)
    .alignItems(HorizontalAlign.End)
    .onClick(() => {
      this.showDetail = false
    })
  }

  build() {
    Stack() {
      Column() {
        this.buildHeader()
        Scroll() {
          Column() {
            if (this.currentTab === 0) {
              this.buildTab1()
            } else if (this.currentTab === 1) {
              this.buildTab2()
            } else if (this.currentTab === 2) {
              this.buildTab3()
            } else if (this.currentTab === 3) {
              this.buildTab4()
            } else if (this.currentTab === 4) {
              this.buildTab5()
            } else if (this.currentTab === 5) {
              this.buildTab6()
            } else {
              this.buildTab7()
            }
          }
          .width('100%')
        }
        .layoutWeight(1)
        .scrollBar(BarState.Off)
        .width('100%')
        .align(Alignment.Top)

        this.buildBottom()
      }
      .width('100%')
      .height('100%')

      if (this.showPublish) {
        this.publishModal()
      }
      if (this.showBulk) {
        this.bulkModal()
      }
      if (this.showInvoice) {
        this.invoiceModal()
      }
      if (this.showDelete) {
        this.deleteModal()
      }
      if (this.showDetail) {
        this.detailModal()
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F4F6FB')
  }
}


总结

在这里插入图片描述

本文深入解析了一个基于HarmonyOS声明式UI的办公文具电商应用的完整源码实现。从数据模型定义到多标签页构建,从笔尖规格可视化到企业采购中心,从批量询价弹窗到智能开票系统,该应用涵盖了办公文具B端电商开发中的全部核心技术点。应用通过@State状态管理实现了UI的响应式更新,通过@Builder方法拆分实现了视图逻辑的模块化组织,通过interface接口保证了数据的类型安全,在架构层面与前两个应用保持了一致的设计哲学。

在B端业务功能层面,应用展现了极高的专业完成度。批量采购询价弹窗的步进10设计直接反映了企业采购的批量特征,92折优惠信息的绿色展示有效促进了转化。开票申请弹窗覆盖了电子普票、纸质普票和增值税专票三种发票类型,以及个人、企业、单位三种抬头选择,双色选择器设计通过颜色差异帮助用户区分不同维度的选择。企业采购中心的数据面板使用三种不同颜色的数据指标(好评橙、企业黄、账期青),使得关键业务数据一目了然。这些B端专属功能的设计充分考虑了企业用户的实际采购流程和开票需求。

在技术实现层面,桌面收纳页面的好评分段柱状图采用了(g.score - 85) / 10 * 90的分段放大公式,这是数据可视化中的经典技术——当数据集中在狭窄范围内时,通过减去基线值再进行比例放大,使得微小差异在视觉上更加明显。应用中大量使用的slice方法进行字符串截取,既用于控制文本长度适配不同屏幕宽度,也用于从复合标签中提取数值部分(如从"笔尖0.5mm"中提取"0.5mm"),展示了ArkTS在字符串处理方面的灵活性。整体而言,该应用的源码为HarmonyOS平台上的办公文具B端电商开发提供了一个功能完整、交互丰富、专业度高的实践参考,与前两个应用共同构成了垂直电商场景的完整技术体系。

Logo

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

更多推荐