鸿蒙ArkTS实战项目 - 门店陈列巡检台:巡检卡片与多列切换实现

一、本篇范围

本篇只看巡检卡片组件和页面控制区。六张卡片高度不同,正好适合拿来验证瀑布流的测量和排布结果,因此文章必须给出足够多的卡片代码,而不是只讲结果图。

二、卡片组一:入口动线与主橱窗

typescript 复制代码
@ComponentV2
struct PatrolEntranceCard {
  @Local score: number = 88;
  @Local note: string = '入口立牌和动线标识已经完成第一次核对。';

  build() {
    Column({ space: 10 }) {
      Text('入口动线').fontSize(17).fontWeight(FontWeight.Bold).fontColor('#0F172A')
      Text('核对门头、迎宾立牌、地贴和临停提示。')
        .fontSize(12).fontColor('#64748B').lineHeight(18)
      Row() {
        Text('完成度').fontSize(12).fontColor('#64748B')
        Blank()
        Text(`${Math.round(this.score)} 分`).fontSize(14).fontWeight(FontWeight.Medium).fontColor('#DC2626')
      }.width('100%')
      Slider({ value: this.score, min: 0, max: 100, step: 1 })
        .selectedColor('#EF4444')
        .trackColor('#FEE2E2')
        .blockColor('#DC2626')
        .onChange((value: number) => this.score = value)
      TextArea({ text: this.note, placeholder: '入口说明' })
        .height(94)
        .backgroundColor('#F8FAFC')
        .borderRadius(10)
        .onChange((value: string) => this.note = value)
    }
    .width('100%')
    .padding(16)
    .backgroundColor('#FFFFFF')
    .borderRadius(18)
  }
}

@ComponentV2
struct PatrolWindowCard {
  @Local mainColor: string = '秋季橙';
  @Local ready: boolean = true;
  @Local remark: string = '主橱窗需要补一条价格说明卡。';

  build() {
    Column({ space: 10 }) {
      Text('主橱窗').fontSize(17).fontWeight(FontWeight.Bold).fontColor('#0F172A')
      TextInput({ text: this.mainColor, placeholder: '主题配色' })
        .backgroundColor('#F8FAFC')
        .borderRadius(10)
        .onChange((value: string) => this.mainColor = value)
      Row() {
        Text('灯箱状态').fontSize(12).fontColor('#64748B')
        Blank()
        Toggle({ type: ToggleType.Switch, isOn: this.ready })
          .selectedColor('#2563EB')
          .onChange((value: boolean) => this.ready = value)
      }.width('100%')
      Text(this.ready ? '主橱窗灯光和背板都已正常点亮。' : '橱窗仍需补测电源与背景灯带。')
        .fontSize(12).fontColor(this.ready ? '#2563EB' : '#B45309').lineHeight(18)
      TextArea({ text: this.remark, placeholder: '补充说明' })
        .height(78)
        .backgroundColor('#F8FAFC')
        .borderRadius(10)
        .onChange((value: string) => this.remark = value)
    }
    .width('100%')
    .padding(16)
    .backgroundColor('#FFFFFF')
    .borderRadius(18)
  }
}

第一组卡片覆盖了 SliderToggleTextInputTextArea 的基本组合。因为瀑布流按卡片真实高度排布,所以卡片内容一多,算法就必须重新平衡列高度。

三、卡片组二:货架空档与收银区

typescript 复制代码
@ComponentV2
struct PatrolShelfCard {
  @Local gapCount: number = 4;
  @Local note: string = '饮品区第三层与饰品区右侧还有陈列空档。';

  build() {
    Column({ space: 10 }) {
      Text('货架空档').fontSize(17).fontWeight(FontWeight.Bold).fontColor('#0F172A')
      Text('优先处理正对主通道的空档,避免断层观感。')
        .fontSize(12).fontColor('#64748B').lineHeight(18)
      Row() {
        Text('空档数量').fontSize(12).fontColor('#64748B')
        Blank()
        Text(`${Math.round(this.gapCount)} 处`).fontSize(14).fontWeight(FontWeight.Medium).fontColor('#7C3AED')
      }.width('100%')
      Slider({ value: this.gapCount, min: 0, max: 12, step: 1 })
        .selectedColor('#8B5CF6')
        .trackColor('#F3E8FF')
        .blockColor('#7C3AED')
        .onChange((value: number) => this.gapCount = value)
      TextArea({ text: this.note, placeholder: '缺口分布' })
        .height(104)
        .backgroundColor('#F8FAFC')
        .borderRadius(10)
        .onChange((value: string) => this.note = value)
    }
    .width('100%')
    .padding(16)
    .backgroundColor('#FFFFFF')
    .borderRadius(18)
  }
}

@ComponentV2
struct PatrolCashierCard {
  @Local queueReady: boolean = false;
  @Local counterOwner: string = '陈值台';
  @Local note: string = '收银口边台需增加一盒小票纸和一块湿巾。';

  build() {
    Column({ space: 10 }) {
      Text('收银区').fontSize(17).fontWeight(FontWeight.Bold).fontColor('#0F172A')
      Row() {
        Text('排队栏').fontSize(12).fontColor('#64748B')
        Blank()
        Toggle({ type: ToggleType.Switch, isOn: this.queueReady })
          .selectedColor('#0F766E')
          .onChange((value: boolean) => this.queueReady = value)
      }.width('100%')
      TextInput({ text: this.counterOwner, placeholder: '值台负责人' })
        .backgroundColor('#F8FAFC')
        .borderRadius(10)
        .onChange((value: string) => this.counterOwner = value)
      TextArea({ text: this.note, placeholder: '口岸说明' })
        .height(86)
        .backgroundColor('#F8FAFC')
        .borderRadius(10)
        .onChange((value: string) => this.note = value)
    }
    .width('100%')
    .padding(16)
    .backgroundColor('#FFFFFF')
    .borderRadius(18)
  }
}

这两张卡片的高度分布更加明显:PatrolShelfCard 用更高的说明区制造列差,PatrolCashierCard 则偏短。技术文写到这里,重点就是让读者看到:算法为什么要找最短列,而不是机械按顺序塞固定网格。

四、卡片组三:促销台与后仓补货

typescript 复制代码
@ComponentV2
struct PatrolPromoCard {
  @Local badgeCount: number = 24;
  @Local fastFix: string = '第二活动台价签方向不一致,需要统一右上角朝向。';

  build() {
    Column({ space: 10 }) {
      Text('促销台').fontSize(17).fontWeight(FontWeight.Bold).fontColor('#0F172A')
      Text('重点检查标签、样卡和赠品说明是否同版。')
        .fontSize(12).fontColor('#64748B').lineHeight(18)
      Row() {
        Text('价签余量').fontSize(12).fontColor('#64748B')
        Blank()
        Text(`${Math.round(this.badgeCount)} 张`).fontSize(14).fontWeight(FontWeight.Medium).fontColor('#F97316')
      }.width('100%')
      Slider({ value: this.badgeCount, min: 0, max: 60, step: 1 })
        .selectedColor('#F97316')
        .trackColor('#FFEDD5')
        .blockColor('#EA580C')
        .onChange((value: number) => this.badgeCount = value)
      TextArea({ text: this.fastFix, placeholder: '当场处理项' })
        .height(98)
        .backgroundColor('#F8FAFC')
        .borderRadius(10)
        .onChange((value: string) => this.fastFix = value)
    }
    .width('100%')
    .padding(16)
    .backgroundColor('#FFFFFF')
    .borderRadius(18)
  }
}

@ComponentV2
struct PatrolStorageCard {
  @Local backroomReady: boolean = true;
  @Local count: number = 12;
  @Local note: string = '后仓临时补货位已留出两层,今晚可直接入库。';

  build() {
    Column({ space: 10 }) {
      Text('后仓补货').fontSize(17).fontWeight(FontWeight.Bold).fontColor('#0F172A')
      Row() {
        Text('临时货位').fontSize(12).fontColor('#64748B')
        Blank()
        Toggle({ type: ToggleType.Switch, isOn: this.backroomReady })
          .selectedColor('#16A34A')
          .onChange((value: boolean) => this.backroomReady = value)
      }.width('100%')
      Row() {
        Text('待补货箱数').fontSize(12).fontColor('#64748B')
        Blank()
        Text(`${Math.round(this.count)} 箱`).fontSize(14).fontWeight(FontWeight.Medium).fontColor('#16A34A')
      }.width('100%')
      Slider({ value: this.count, min: 0, max: 30, step: 1 })
        .selectedColor('#22C55E')
        .trackColor('#DCFCE7')
        .blockColor('#16A34A')
        .onChange((value: number) => this.count = value)
      TextArea({ text: this.note, placeholder: '补货说明' })
        .height(74)
        .backgroundColor('#F8FAFC')
        .borderRadius(10)
        .onChange((value: string) => this.note = value)
    }
    .width('100%')
    .padding(16)
    .backgroundColor('#FFFFFF')
    .borderRadius(18)
  }
}

最后一组卡片继续扩大了高度和控件组合的差异。对瀑布流来说,卡片是否"好看"不是第一位,能否提供足够真实的高度差才是第一位;否则就无法证明自定义算法的必要性。

五、页面控制区:列数与间距按钮

typescript 复制代码
Row({ space: 8 }) {
        Button('双列').height(36).backgroundColor(this.layoutAlgorithm.columnCount === 2 ? '#111827' : '#E5E7EB')
          .fontColor(this.layoutAlgorithm.columnCount === 2 ? '#FFFFFF' : '#334155')
          .onClick(() => this.switchColumns(2))
        Button('三列').height(36).backgroundColor(this.layoutAlgorithm.columnCount === 3 ? '#111827' : '#E5E7EB')
          .fontColor(this.layoutAlgorithm.columnCount === 3 ? '#FFFFFF' : '#334155')
          .onClick(() => this.switchColumns(3))
        Button('紧凑间距').height(36).backgroundColor('#FEE2E2').fontColor('#991B1B')
          .onClick(() => this.switchGap(8, 8))
        Button('舒展间距').height(36).backgroundColor('#DBEAFE').fontColor('#1D4ED8')
          .onClick(() => this.switchGap(14, 14))
      }
      .width('100%')
      .padding({ left: 16, right: 16, bottom: 10 })

      Scroll() {
        DynamicLayout(this.layoutAlgorithm) {
          PatrolEntranceCard()
          PatrolWindowCard()
          PatrolShelfCard()
          PatrolCashierCard()
          PatrolPromoCard()
          PatrolStorageCard()
        }
        .width('100%')
        .padding({ left: 16, right: 16, bottom: 24 })
      }

这里把列数按钮、间距按钮和 DynamicLayout 挂载区放到了一起。自定义布局算法只有暴露出可调字段,按钮区才有意义;否则 UI 再多,也只是静态展示。

六、卡片绑定对照表

卡片或区域 主要控件 状态字段 页面作用
入口动线 Slider / TextArea score / note 入口与导视巡检
主橱窗 TextInput / Toggle / TextArea mainColor / ready / remark 橱窗主题与灯箱检查
货架空档 Slider / TextArea gapCount / note 统计空档数量
收银区 Toggle / TextInput / TextArea queueReady / counterOwner / note 收银动线检查
促销台 Slider / TextArea badgeCount / fastFix 标签与样卡检查
后仓补货 Toggle / Slider / TextArea backroomReady / count / note 补货区准备

七、本篇小结

第二篇把六张巡检卡片完整摆出来,目的不是堆业务文案,而是让瀑布流算法面对真实高度差。技术文里,卡片高度本身就是算法输入。

相关推荐
lilian2332 小时前
Harmony os 技术实战|拼豆制图27:用单字符编码承载 50 张 70×70 图纸
前端·数据库·华为·harmonyos
一只小小的芙厨2 小时前
基础数论总结
笔记·学习·算法
hsjiasb4 小时前
FreeRTOS学习(三十七)——常见错误与工程规范
学习·学习笔记·freertos
小席是个热心肠4 小时前
Redis的自我学习
数据库·redis·学习
zyf1044165 小时前
暑期实践日志 Day33:完成全部字幕添加,工作基本收尾
学习·计算机网络·剪辑·暑期实践·课题任务
Chris _data5 小时前
WPF 上位机开发学习笔记 - 第四天
笔记·学习·wpf
HY小宝F6 小时前
树莓派 FFmpeg 实战笔记(二):命令行、源码编译与硬件编码的真相
学习·职场和发展·ffmpeg
2601_967264286 小时前
Jetpack Compose 实践指南:从入门到进阶
java·学习
小雪崩7 小时前
嵌入式学习 day25:哈希表及排序与查找
linux·c语言·数据结构·学习·排序算法