麦秆画,这门以农作物废弃的麦秆为原材料的民间艺术,将田间地头最平凡之物化作了最不平凡的画作。经过选秆、浸泡、剖片、染色、拼贴、熨烫、装框等十余道工序,金黄色的麦秆片在匠人手中拼贴成山水花鸟、人物走兽,在光线下泛着天然的金属光泽——这种"以麦为墨、以秆为笔"的艺术形式,体现了中国劳动人民化平凡为神奇的创造力。

本文将深入解析一个基于 ArkTS 声明式 UI 框架构建的麦秆画工艺数字化管理系统。该系统以金黄暖色为视觉主调,通过麦穗摇曳动画模拟风吹麦田的动态场景,以时间轴+瀑布流的混合布局呈现六大维度的工艺数据——从麦秆原料的产地溯源到染色染料的色卡展示,从拼贴工序的步骤分解到烫画工具的温度参数,从匠人信息到最终画作成品。系统通过左侧时间轴竖线节点配合右侧交替高度卡片的布局策略,让每一项数据都如同挂在时间轴上的一个节点,形成了一种兼具纵向序列感和视觉节奏感的独特信息架构。

从技术创新的角度来看,该系统引入了四个全新的辅助函数:grainXgrainY 控制麦穗动画的位置坐标、timelineLeft 定义时间轴的固定水平位置、cardHeight 根据索引返回交替的卡片高度。这些函数共同驱动了系统最具特色的"时间轴+瀑布流"混合布局——每个列表项的左侧都有一个圆形时间节点和连接竖线,右侧卡片的高度通过 cardHeight 函数在70像素和55像素之间交替变化,形成如瀑布般高低错落的视觉韵律。这种布局在六个 Tab 中保持统一,成为麦秆画系统最显著的视觉标识。

系统入口 Page770

头部区域 headerType

滚动内容区 Scroll

底部标签栏 tabBar

模态弹窗组

金黄渐变Banner

麦穗摇曳动画

标签筛选条

新增按钮 绿色

巨字水印 麦

麦秆Tab - 时间轴节点+产地

染色Tab - 色卡圆点+瀑布流

拼贴Tab - 步骤序号+操作按钮

烫画Tab - 温度红色显示

匠人Tab - 年限+编删

画作Tab - 尺寸+高价

月度产量图表

新增弹窗

编辑弹窗

删除弹窗


在这里插入图片描述

一、色彩体系:金麦暖色的视觉基调

1.1 色彩接口定义

系统通过 interface ColorPalette 定义了一套以金黄色为核心的色彩类型体系,包含二十四个语义化颜色字段。

interface ColorPalette {
  bg: string;
  card: string;
  paper: string;
  title: string;
  sub: string;
  text3: string;
  ink: string;
  wheat: string;
  wheatL: string;
  wheatD: string;
  straw: string;
  grainA: string;
  grainB: string;
  grainC: string;
  amber: string;
  gold: string;
  cream: string;
  red: string;
  green: string;
  line: string;
  tabOn: string;
  mask: string;
  transparent: string;
  axisBg: string;
}

与前面系统的色彩接口相比,麦秆画系统引入了几个独特的语义化字段:wheat(麦黄色)、wheatL(浅麦色)、wheatD(深麦色)构成麦色三阶系列,分别用于不同的视觉层级;straw(稻草色)和 grainAgrainBgrainC(谷物色系)提供了更多的暖色调选项;axisBg(时间轴背景色)以极低透明度的麦黄色 rgba(212,160,32,0.1) 专门用于时间轴相关元素的背景——这是麦秆画系统独有的字段,体现了时间轴布局在系统中的重要地位。

1.2 色彩常量实例化

const COLORS: ColorPalette = {
  bg: '#FAF5E8',
  card: '#FFFFFF',
  paper: '#F0E8D0',
  title: '#3E2A0A',
  sub: '#6B5A28',
  text3: '#9B8B58',
  ink: '#2A1A00',
  wheat: '#D4A020',
  wheatL: '#E8C04A',
  wheatD: '#A88010',
  straw: '#C4A040',
  grainA: '#D4A020',
  grainB: '#E8C04A',
  grainC: '#B89010',
  amber: '#CA8A10',
  gold: '#B8860B',
  cream: '#FFF8E1',
  red: '#D32F2F',
  green: '#388E3C',
  line: '#E0D8B8',
  tabOn: '#3E2A0A',
  mask: 'rgba(0,0,0,0.5)',
  transparent: 'rgba(0,0,0,0)',
  axisBg: 'rgba(212,160,32,0.1)'
};

这套色彩方案营造了一种"金秋麦田"的视觉氛围。背景色 #FAF5E8 是极淡的暖黄色,模拟麦田收割后阳光照射的田野底色;标题色 #3E2A0A 是深棕黑色,如同麦秆烤制后的深色质感;麦色系列 wheat#D4A020)、wheatL#E8C04A)、wheatD#A88010)覆盖了从亮金到暗金的全色域,模拟了麦秆从新鲜金黄到陈化暗金的自然色彩变化。绿色 #388E3C 在系统中被巧妙地用于新增按钮的背景色和"优"字标签,代表着麦秆原料的天然植物属性。axisBg 以极低透明度的麦黄色用于标签选中态和时间轴背景,在保持整体统一感的同时提供了微妙的视觉区分。


在这里插入图片描述

二、标签元数据与索引体系

2.1 六大工艺标签

const TAB_LIST: TabMeta[] = [
  { icon: '麦', label: '麦秆' },
  { icon: '染', label: '染色' },
  { icon: '拼', label: '拼贴' },
  { icon: '烫', label: '烫画' },
  { icon: '匠', label: '匠人' },
  { icon: '画', label: '画作' }
];

六个标签——麦秆、染色、拼贴、烫画、匠人、画作——完整覆盖了麦秆画工艺的全流程。从原料(麦秆)到前处理(染色),从核心工艺(拼贴)到后期定型(烫画),再到人力(匠人)和成品(画作),每个标签都对应着一个明确的工艺环节。"麦、染、拼、烫、匠、画"六个单字图标精准概括了各自模块的核心动作,与麦秆画的手工特性高度契合。

2.2 索引数组体系

const ROW1_IDX: number[] = [0, 1, 2];
const ROW2_IDX: number[] = [3, 4, 5];
const TAG_IDX: number[] = [0, 1, 2, 3];
const TAG_NAME_IDX: string[] = ['麦秆', '染色', '拼贴', '画作'];
const GRAIN_IDX: number[] = [0, 1, 2, 3, 4, 5, 6];
const MONTH_IDX: number[] = [0, 1, 2, 3, 4, 5];
const MONTH_NAME_IDX: string[] = ['正月', '二月', '三月', '四月', '五月', '六月'];
const SALES_IDX: number[] = [25, 40, 52, 65, 78, 55];

在这里插入图片描述

GRAIN_IDX 是一个七元素数组,用于头部麦穗摇曳动画——七根麦穗模拟一小束麦秆的形态。SALES_IDX 的数值 [25, 40, 52, 65, 78, 55] 呈现稳步上升后回落的趋势,五月份产量最高(78幅),反映了麦秆画在麦收季节前后产量最丰的季节性特征。


三、数据模型层:六大可观察实体

3.1 麦秆数据模型

@Observed
export class StrawItem {
  name: string;
  kind: string;
  origin: string;
  price: number;
  constructor(name: string, kind: string, origin: string, price: number) {
    this.name = name; this.kind = kind; this.origin = origin; this.price = price;
  }
}

在这里插入图片描述

StrawItem 描述麦秆原料信息,包含 origin(产地)字段——大麦秆以河南信阳产为佳,青稞秆来自西藏拉萨,不同产地的麦秆在色泽、韧性上各有差异。产地信息在列表中以" · 产地"的形式展示,为原料溯源提供了数据支撑。

3.2 染色数据模型

@Observed
export class DyeItem {
  name: string;
  kind: string;
  color: string;
  price: number;
  constructor(name: string, kind: string, color: string, price: number) {
    this.name = name; this.kind = kind; this.color = color; this.price = price;
  }
}

DyeItem 是麦秆画系统最独特的数据模型——它包含 color 字段,存储染料的十六进制颜色值(如 #D4A020 代表金黄、#2050D4 代表石青)。这个字段在 UI 中被直接用作圆形色块的 fill 值,使每种染料都能以其实际颜色在列表中呈现,形成了一个可视化的色卡系统。这种"数据即颜色"的设计是麦秆画系统在数据建模层面最具创意的决策。

3.3 拼贴与烫画数据模型

@Observed
export class PasteItem {
  name: string;
  kind: string;
  steps: number;
  price: number;
  desc: string;
  constructor(name: string, kind: string, steps: number, price: number, desc: string) {
    this.name = name; this.kind = kind; this.steps = steps; this.price = price; this.desc = desc;
  }
}

@Observed
export class IronItem {
  name: string;
  kind: string;
  temp: number;
  price: number;
  constructor(name: string, kind: string, temp: number, price: number) {
    this.name = name; this.kind = kind; this.temp = temp; this.price = price;
  }
}

PasteItem 包含 steps(步骤数)和 desc(描述)两个字段,用于展示拼贴工序的复杂度和说明文字。IronItem 包含 temp(温度)字段,记录烫画工具的工作温度——从80度的低温烫到500度的喷火枪,温度参数是烫画工艺的核心技术指标,在列表中以红色文字突出显示,提醒用户注意操作安全。

3.4 匠人与画作数据模型

@Observed
export class ArtisanItem {
  name: string;
  kind: string;
  years: number;
  price: number;
  constructor(name: string, kind: string, years: number, price: number) {
    this.name = name; this.kind = kind; this.years = years; this.price = price;
  }
}

@Observed
export class PaintingArtItem {
  name: string;
  kind: string;
  size: string;
  price: number;
  constructor(name: string, kind: string, size: string, price: number) {
    this.name = name; this.kind = kind; this.size = string; this.price = price;
  }
}

在这里插入图片描述

ArtisanItem 使用 years 字段记录匠人从业年限。PaintingArtItem 使用 size 字段记录画作的物理尺寸(如"40x50cm"),size 字段在列表中以" · 尺寸"的形式展示。画作数据的价格区间从550元到2800元("清明上河图"仿古画),体现了麦秆画作为艺术品的价值跨度。


四、模拟数据集

4.1 麦秆与染料模拟数据

const mockStrawList: StrawItem[] = [
  new StrawItem('大麦秆', '常用麦秆', '河南信阳', 15),
  new StrawItem('小麦秆', '常用麦秆', '山东莱阳', 18),
  new StrawItem('燕麦秆', '特种麦秆', '河北张家口', 20),
  new StrawItem('黑麦秆', '特种麦秆', '内蒙古赤峰', 22),
  new StrawItem('荞麦秆', '杂粮秆', '陕西延安', 12),
  new StrawItem('莜麦秆', '杂粮秆', '山西大同', 14),
  new StrawItem('青稞秆', '高原麦秆', '西藏拉萨', 25),
  new StrawItem('水稻秆', '稻草', '江苏苏州', 10),
  new StrawItem('芦苇秆', '草本秆', '白洋淀', 8),
  new StrawItem('荻草秆', '草本秆', '湖北洪湖', 8)
];

在这里插入图片描述

十条麦秆数据覆盖了大麦、小麦、燕麦、黑麦、荞麦、莜麦、青稞、水稻、芦苇、荻草等多种草本秆类原料,产地遍布河南、山东、河北、内蒙古、陕西、山西、西藏、江苏、湖北等地。数据从"常用麦秆"逐步过渡到"特种麦秆"“杂粮秆”“高原麦秆”“草本秆”,价格从8元到25元递增,体现了不同原料的稀缺程度。

染料数据 mockDyeList 包含十种染料,分为矿物染料(金黄、赭石、朱砂、石青)和植物染料(藤黄、靛蓝、槐黄、栀子黄、红花、五倍子)两大类,每种染料都存储了其实际颜色值。拼贴数据 mockPasteList 列出了选秆、浸泡、剖秆、刮平、裁切、拼贴、熨烫、压平、装框、封漆十道工序。烫画数据 mockIronList 包含十种烫画工具及其温度参数。匠人数据和画作数据各包含十到十二条记录。


五、辅助函数:麦穗位置与时间轴布局

5.1 麦穗位置函数

function grainX(i: number): number {
  return 15 + i * 42;
}

function grainY(i: number): number {
  return 128 + (i % 2) * 12;
}

grainX 计算麦穗的水平位置,基准15像素,每根麦穗向右偏移42像素,七根麦穗从x=15到x=267分布在头部区域。grainY 计算垂直位置,使用 (i % 2) * 12 实现偶数索引在128像素、奇数索引在140像素的交替分布——12像素的垂直偏移比核雕系统的10像素更大,使得麦穗的高低错落更加明显,更好地模拟了风吹麦田时麦穗摇摆的动态效果。

5.2 时间轴位置与卡片高度函数

function timelineLeft(idx: number): number {
  return 30;
}

function cardHeight(idx: number): number {
  return idx % 2 === 0 ? 70 : 55;
}

timelineLeft 函数返回固定值30,定义了时间轴在列表项中的水平位置——所有时间节点和连接线都位于距离左侧30像素的位置,形成一条贯穿整个列表的竖直时间轴。

cardHeight 是麦秆画系统最具特色的布局函数。它根据索引返回交替的高度值:偶数索引返回70像素,奇数索引返回55像素。15像素的高度差使得列表项的右侧内容区域呈现出"高-矮-高-矮"的交替节奏,如同瀑布般高低错落。这种交替高度配合左侧统一位置的时间轴节点,形成了"时间轴+瀑布流"的混合布局效果——左侧是稳定的竖直序列,右侧是动态的高低波动,两者在视觉上形成了一种稳定与变化并存的美感。

辅助函数

grainX: 15 + i*42

grainY: 128 + i%2*12

timelineLeft: 固定30px

cardHeight: 偶70/奇55

麦穗水平定位

麦穗垂直交替

时间轴固定位置

卡片高度交替

头部麦穗动画

左侧时间轴竖线

右侧瀑布流高低

时间轴+瀑布流混合


六、页面组件主体

6.1 状态变量

@Entry
@Component
struct Page770 {
  @State selTab: number = 0;
  @State selTag: number = 0;
  @State breath: boolean = false;
  @State showAdd: boolean = false;
  @State showEdit: boolean = false;
  @State showDel: boolean = false;
  @State editIdx: number = 0;
  @State delIdx: number = 0;
  @State strawList: StrawItem[] = mockStrawList;
  @State dyeList: DyeItem[] = mockDyeList;
  @State pasteList: PasteItem[] = mockPasteList;
  @State ironList: IronItem[] = mockIronList;
  @State artisanList: ArtisanItem[] = mockArtisanList;
  @State artList: PaintingArtItem[] = mockArtList;

状态变量的组织方式与前序系统一致。六个数据列表分别绑定麦秆、染料、拼贴、烫画、匠人、画作六个模拟数据集。

6.2 生命周期函数

aboutToAppear() {
  setInterval(() => { this.breath = !this.breath; }, 600);
}

在这里插入图片描述

定时器间隔为600毫秒,介于折扇系统的700毫秒和核雕系统的500毫秒之间。这个间隔模拟了风吹麦田时麦穗摇摆的自然节奏——不急不缓,恰到好处。breath 状态的翻转驱动着头部七根麦穗的高度变化(12↔30像素)和旋转角度(-8↔+8度),营造出麦穗在微风中轻轻摇摆的动态效果。


七、构建函数 build() 与页面结构

build() {
  Column() {
    this.headerType()
    Divider().strokeWidth(1).color(COLORS.line)
    Scroll() {
      Column() {
        if (this.selTab === 0) {
          this.strawTab()
        } else if (this.selTab === 1) {
          this.dyeTab()
        } else if (this.selTab === 2) {
          this.pasteTab()
        } else if (this.selTab === 3) {
          this.ironTab()
        } else if (this.selTab === 4) {
          this.artisanTab()
        } else {
          this.artTab()
        }
        this.salesChart()
      }.width('100%').padding({ left: 10, right: 10, top: 10, bottom: 100 })
    }.constraintSize({ maxHeight: '70%' }).scrollBar(BarState.Off)
    this.tabBar()
    if (this.showAdd) { this.addModal() }
    if (this.showEdit) { this.editModal() }
    if (this.showDel) { this.delModal() }
  }.width('100%').height('100%').backgroundColor(COLORS.bg)
}

在这里插入图片描述

页面结构与前序系统保持一致。背景色为 COLORS.bg#FAF5E8,极淡暖黄),比前序系统更温暖明亮,契合麦秆画"金麦流光"的视觉主题。


八、头部构建器 headerType()

8.1 金黄渐变与标题

@Builder
headerType() {
  Stack({ alignContent: Alignment.TopStart }) {
    Column()
      .width('100%')
      .height(170)
      .linearGradient({
        angle: 135,
        colors: [[COLORS.wheatD, 0.0], [COLORS.wheat, 0.5], [COLORS.bg, 1.0]]
      })
    Row() {
      Text('麦秆画')
        .fontSize(22)
        .fontColor(COLORS.cream)
        .fontWeight(FontWeight.Bold)
      Text('·金麦流光')
        .fontSize(14)
        .fontColor(COLORS.wheatL)
        .margin({ left: 6 })
    }
    .position({ x: 14, y: 12 })

头部渐变从深麦色 #A88010 到中麦色 #D4A020 再到背景色 #FAF5E8,模拟了麦秆从根部深色到穗部金色的自然渐变。主标题"麦秆画"以22号粗体奶油色字体显示——字号比前序系统的24号略小,是因为"麦秆画"三字比"折扇"“核雕"两字多一个字,适当减小字号保证了标题在视觉上的均衡。副标题”·金麦流光"以14号浅麦色字体跟随。

8.2 搜索框、标签与新增按钮

    TextInput({ placeholder: '搜索麦秆/染料/画作' })
      .fontSize(13).fontColor(COLORS.title)
      .placeholderColor(COLORS.text3)
      .backgroundColor(COLORS.card).borderRadius(8)
      .margin({ left: 12, right: 12, top: 44 })
    Row({ space: 6 }) {
      ForEach(TAG_IDX, (t: number) => {
        Column() {
          Text(TAG_NAME_IDX[t]).fontSize(11)
            .fontColor(this.selTag === t ? COLORS.cream : COLORS.wheat)
        }
        .padding({ left: 10, right: 10, top: 4, bottom: 4 })
        .backgroundColor(this.selTag === t ? COLORS.wheatD : COLORS.axisBg)
        .borderRadius(12)
        .onClick(() => { this.selTag = t; this.selTab = t; })
      }, (t: number) => 'tg' + t)
    }
    .width('100%').padding({ left: 12, right: 12 })
    .position({ x: 0, y: 88 })
    Row() {
      Text('+').fontSize(14).fontColor(COLORS.cream)
      Text('新增').fontSize(12).fontColor(COLORS.cream).margin({ left: 4 })
    }
    .padding({ left: 12, right: 12, top: 6, bottom: 6 })
    .backgroundColor(COLORS.green).borderRadius(8)
    .position({ x: 270, y: 88 })
    .onClick(() => { this.showAdd = true; })

标签筛选条的未选中态背景色使用了 COLORS.axisBgrgba(212,160,32,0.1)),这种极低透明度的麦黄色与整体的暖金基调完美融合。新增按钮的背景色为绿色 COLORS.green#388E3C),这是麦秆画系统的独特设计——绿色代表麦秆的植物属性和自然起源,与金黄色的整体色调形成了一种"绿叶配金穗"的色彩呼应,在视觉上传达了"从绿色植物到金色艺术品"的工艺转化理念。

8.3 麦穗摇曳动画

    ForEach(GRAIN_IDX, (g: number) => {
      Column()
        .width(2)
        .height(this.breath ? 30 : 12)
        .backgroundColor(g % 2 === 0 ? COLORS.wheat : COLORS.wheatL)
        .borderRadius(1)
        .rotate({ angle: this.breath ? 8 : -8 })
        .position({ x: grainX(g), y: grainY(g) })
        .animation({ duration: 600, iterations: -1, playMode: PlayMode.Alternate })
    }, (g: number) => 'gr' + g)
    Text('麦')
      .fontSize(48)
      .fontColor('rgba(255,255,255,0.1)')
      .fontWeight(FontWeight.Bold)
      .position({ x: 260, y: 100 })
  }
  .width('100%').height(170)
}

麦穗动画通过 ForEach 遍历 GRAIN_IDX(0到6共七根)实现。每根麦穗是一个宽度2像素、高度随 breath 在12和30像素间切换的 Column,颜色在深麦色和浅麦色间交替。与折扇系统的扇骨动画相比,麦穗动画增加了 rotate 旋转属性——角度在-8度和+8度之间切换,模拟麦穗在风中左右摇摆的动态。这种"高度变化+角度旋转"的双属性动画,比单纯的"高度变化"更加生动,让麦穗看起来真的在微风中摇摆。

"麦"字水印以48号字、极低透明度白色定位于头部右侧,为金色渐变背景增添了一层文化标识。


九、六大 Tab 内容构建器:时间轴+瀑布流的统一架构

9.1 麦秆列表 strawTab()

麦秆列表是"时间轴+瀑布流"布局的典型代表。

@Builder
strawTab() {
  Column({ space: 6 }) {
    Text('麦秆 · 时间轴瀑布流').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
      .width('100%').padding({ left: 4, top: 4, bottom: 4 })
    ForEach(this.strawList, (item: StrawItem, idx: number) => {
      Row() {
        Column() {
          Circle().width(12).height(12)
            .fill(idx % 3 === 0 ? COLORS.wheat : idx % 3 === 1 ? COLORS.grainB : COLORS.amber)
            .stroke(COLORS.wheatL)
            .strokeWidth(1)
          Column()
            .width(2).height(cardHeight(idx))
            .backgroundColor(COLORS.wheatL)
            .margin({ top: 2 })
        }.width(30)
        Column({ space: 4 }) {
          Text(item.name).fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
          Row() {
            Text(item.kind).fontSize(10).fontColor(COLORS.sub)
            Text(' · ' + item.origin).fontSize(10).fontColor(COLORS.text3)
          }.width('100%')
          Row() {
            Text('Y' + item.price).fontSize(12).fontColor(COLORS.wheatD).fontWeight(FontWeight.Bold)
            Column().layoutWeight(1)
            if (idx % 3 === 0) {
              Text('优').fontSize(9).fontColor(COLORS.cream)
                .backgroundColor(COLORS.green).padding({ left: 4, right: 4, top: 1, bottom: 1 }).borderRadius(3)
            }
          }.width('100%')
        }
        .layoutWeight(1).alignItems(HorizontalAlign.Start).padding({ left: 6 })
      }
      .width('100%').padding({ left: 8, right: 8, top: 4, bottom: 4 })
      .backgroundColor(COLORS.card).borderRadius(8)
      .margin({ top: 2 })
      .onClick(() => { this.editIdx = idx; this.showEdit = true; })
    }, (item: StrawItem) => item.name)
  }.width('100%')
}

每个列表项由两部分组成。左侧是一个宽度30像素的 Column,包含一个12像素直径的 Circle 时间节点(填充色三色循环,带浅麦色描边)和一条宽度2像素、高度由 cardHeight(idx) 计算的竖线——圆形节点代表时间点,竖线代表时间流向,两者共同构成了时间轴的视觉形态。竖线的高度随 cardHeight 在70和55像素间交替,使得时间轴不是一条等长的直线,而是一条长短交替的"断续线",与右侧瀑布流卡片的高低变化形成呼应。

右侧内容区域包含名称(粗体)、种类与产地(一行展示)、价格和可选的"优"字标签。每三个列表项中有一项会显示绿色背景的"优"字标签(idx % 3 === 0),标记该原料为优质品级。这种"三选一"的标签策略为列表增添了信息层级。

9.2 染色列表 dyeTab()

@Builder
dyeTab() {
  Column({ space: 6 }) {
    Row() {
      Text('+').fontSize(16).fontColor(COLORS.tabOn)
      Text('新增染料').fontSize(13).fontColor(COLORS.tabOn).margin({ left: 4 })
    }
    .width('100%').padding({ left: 12, top: 8, bottom: 8 })
    .backgroundColor(COLORS.card).borderRadius(8)
    .onClick(() => { this.showAdd = true; })
    ForEach(this.dyeList, (item: DyeItem, idx: number) => {
      Row() {
        Column() {
          Circle().width(12).height(12)
            .fill(idx % 3 === 0 ? COLORS.wheat : idx % 3 === 1 ? COLORS.grainB : COLORS.amber)
            .stroke(COLORS.wheatL)
            .strokeWidth(1)
          Column()
            .width(2).height(cardHeight(idx))
            .backgroundColor(COLORS.wheatL)
            .margin({ top: 2 })
        }.width(30)
        Column({ space: 4 }) {
          Row() {
            Circle().width(16).height(16)
              .fill(item.color)
              .stroke(COLORS.line)
              .strokeWidth(1)
            Text(item.name).fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
              .margin({ left: 6 })
          }
          Text(item.kind).fontSize(10).fontColor(COLORS.sub)
          Text('Y' + item.price).fontSize(12).fontColor(COLORS.wheatD).fontWeight(FontWeight.Bold)
        }
        .layoutWeight(1).alignItems(HorizontalAlign.Start).padding({ left: 6 })
      }
      .width('100%').padding({ left: 8, right: 8, top: 4, bottom: 4 })
      .backgroundColor(idx % 2 === 0 ? COLORS.card : COLORS.paper)
      .borderRadius(8).margin({ top: 2 })
      .onClick(() => { this.editIdx = idx; this.showEdit = true; })
    }, (item: DyeItem) => item.name)
  }.width('100%')
}

染色列表最独特的元素是右侧内容区顶部的16像素直径圆形色块——它的 fill 值直接取自数据模型的 item.color 字段。这意味着金黄染料显示为金色圆点、朱砂染料显示为红色圆点、石青染料显示为蓝色圆点——每种染料以其真实颜色在列表中呈现,形成了一个直观的可视化色卡。这种"数据驱动颜色"的设计让用户无需阅读文字就能快速辨识不同染料的色彩属性,是麦秆画系统在信息可视化方面最出色的设计决策。

9.3 拼贴列表 pasteTab()

@Builder
pasteTab() {
  Column({ space: 6 }) {
    ForEach(this.pasteList, (item: PasteItem, idx: number) => {
      Row() {
        Column() {
          Text((idx + 1).toString())
            .fontSize(11).fontColor(COLORS.cream).fontWeight(FontWeight.Bold)
          Circle().width(10).height(10)
            .fill(idx % 3 === 0 ? COLORS.wheat : idx % 3 === 1 ? COLORS.grainB : COLORS.amber)
            .stroke(COLORS.wheatL)
            .strokeWidth(1)
            .margin({ top: 2 })
          Column()
            .width(2).height(cardHeight(idx) - 20)
            .backgroundColor(COLORS.wheatL)
            .margin({ top: 2 })
        }.width(30)
        Column({ space: 4 }) {
          Row() {
            Text(item.name).fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
            Column().layoutWeight(1)
            Text(item.steps + '步').fontSize(9).fontColor(COLORS.cream)
              .backgroundColor(COLORS.green).padding({ left: 4, right: 4, top: 1, bottom: 1 }).borderRadius(3)
            Text(' Y' + item.price).fontSize(12).fontColor(COLORS.wheatD).fontWeight(FontWeight.Bold)
              .margin({ left: 6 })
          }.width('100%')
          Text(item.kind).fontSize(10).fontColor(COLORS.sub)
          Text(item.desc).fontSize(10).fontColor(COLORS.text3)
          Row({ space: 8 }) {
            Text('编辑').fontSize(10).fontColor(COLORS.tabOn)
              .onClick(() => { this.editIdx = idx; this.showEdit = true; })
            Text('删除').fontSize(10).fontColor(COLORS.red)
              .onClick(() => { this.delIdx = idx; this.showDel = true; })
          }.width('100%')
        }
        .layoutWeight(1).alignItems(HorizontalAlign.Start).padding({ left: 6 })
      }
      .width('100%').padding({ left: 8, right: 8, top: 4, bottom: 4 })
      .backgroundColor(COLORS.card).borderRadius(8)
      .margin({ top: 2 })
    }, (item: PasteItem) => item.name)
  }.width('100%')
}

拼贴列表的时间轴左侧不仅有圆形节点和竖线,还在节点上方增加了一个序号文字——Text((idx + 1).toString()) 以11号奶油色粗体显示工序序号。这个序号让时间轴从单纯的视觉装饰变成了携带信息的"工序编号轴",每道工序在时间轴上都有一个明确的序号位置。竖线高度使用了 cardHeight(idx) - 20 的计算方式,减去20像素是为了给序号文字留出空间,避免竖线与序号重叠。

右侧内容区包含名称、步骤数标签(绿色背景的"X步"标签,与新增按钮的绿色形成统一)、种类、描述文本和编辑/删除操作按钮,信息密度在六个 Tab 中最高。

9.4 烫画列表 ironTab()

@Builder
ironTab() {
  Column({ space: 6 }) {
    ForEach(this.ironList, (item: IronItem, idx: number) => {
      Row() {
        Column() {
          Circle().width(10).height(10)
            .fill(idx % 3 === 0 ? COLORS.wheat : idx % 3 === 1 ? COLORS.grainB : COLORS.amber)
            .stroke(COLORS.wheatL)
            .strokeWidth(1)
          Column()
            .width(2).height(cardHeight(idx))
            .backgroundColor(COLORS.wheatL)
            .margin({ top: 2 })
        }.width(30)
        Column({ space: 4 }) {
          Row() {
            Text(item.name).fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
            Column().layoutWeight(1)
            Text(item.temp + 'C').fontSize(11).fontColor(COLORS.red).fontWeight(FontWeight.Bold)
          }.width('100%')
          Text(item.kind).fontSize(10).fontColor(COLORS.sub)
          Text('Y' + item.price).fontSize(12).fontColor(COLORS.wheatD).fontWeight(FontWeight.Bold)
        }
        .layoutWeight(1).alignItems(HorizontalAlign.Start).padding({ left: 6 })
      }
      .width('100%').padding({ left: 8, right: 8, top: 4, bottom: 4 })
      .backgroundColor(idx % 2 === 0 ? COLORS.card : COLORS.paper)
      .borderRadius(8).margin({ top: 2 })
      .onClick(() => { this.editIdx = idx; this.showEdit = true; })
    }, (item: IronItem) => item.name)
  }.width('100%')
}

烫画列表的独特之处在于温度的显示方式——Text(item.temp + 'C') 以11号红色粗体显示在工作温度区域。红色 COLORS.red#D32F2F)的使用不仅是为了视觉醒目,更是一种安全提示——高温烫画工具具有危险性,红色文字在潜意识层面提醒用户注意操作温度。温度值从80度到500度,跨度很大,不同温度对应不同的烫画效果和工具类型。

9.5 匠人列表 artisanTab() 与画作列表 artTab()

匠人列表和画作列表的结构与前面的 Tab 保持统一的时间轴布局。匠人列表展示了姓名、工种、从业年限和"编""删"操作按钮。画作列表在顶部增加了"新增画作"按钮行,展示了画作名称、种类、尺寸和价格,价格以深麦色粗体显示——作为系统中价格最高的数据集(最高2800元的"清明上河图"仿古画),深麦色粗体在视觉上强调了画作的价值分量。


十、月度产量图表 salesChart()

@Builder
salesChart() {
  Column({ space: 8 }) {
    Row() {
      Text('月度画作产量').fontSize(14).fontColor(COLORS.title).layoutWeight(1)
      Text('幅').fontSize(11).fontColor(COLORS.text3)
    }.width('100%')
    Row({ space: 4 }) {
      ForEach(MONTH_IDX, (m: number) => {
        Column({ space: 4 }) {
          Text(SALES_IDX[m].toString())
            .fontSize(9).fontColor(COLORS.sub)
          Column()
            .width(24)
            .height(SALES_IDX[m] * 0.15)
            .backgroundColor(m % 2 === 0 ? COLORS.wheatD : COLORS.wheat)
            .borderRadius(4)
          Text(MONTH_NAME_IDX[m])
            .fontSize(9)
            .fontColor(COLORS.text3)
        }
        .layoutWeight(1)
        .alignItems(HorizontalAlign.Center)
      }, (m: number) => 'ch' + m)
    }
    .width('100%').height(60)
    .alignItems(VerticalAlign.Bottom)
  }
  .width('100%').padding(12)
  .backgroundColor(COLORS.card).borderRadius(10)
  .margin({ top: 10 })
}

图表标题为"月度画作产量",单位"幅"。柱子颜色通过 m % 2 在深麦色和麦色之间交替,形成金黄色的双色柱状图——这是麦秆画系统最符合主题的图表配色。SALES_IDX 的数值从25到78,五月达到峰值78幅,反映了麦收季节前后麦秆画产量最丰的季节性规律。高度系数0.15使最高柱78×0.15=11.7像素,在60像素的图表区域内呈现合理的高度比例。


十一、底部标签栏与模态弹窗系统

底部标签栏的结构与前序系统一致,选中态背景色使用 COLORS.axisBgrgba(212,160,32,0.1)),与金黄色基调保持统一。模态弹窗系统包含 modalOverlayaddModaleditModaldelModal 四个构建器,结构与前序系统完全一致。差异在于确定/保存按钮的背景色使用了 COLORS.wheatD(深麦色),与系统的金黄色主题保持统一。

时间轴+瀑布流布局核心

列表项 Row

左侧时间轴 30px宽

右侧内容区 layoutWeight1

Circle 时间节点 三色循环

Column 竖线 cardHeight交替

Text 序号 pasteTab特有

Text 名称 粗体

Row 种类+产地/尺寸

Text 价格 深麦色

可选标签 优/步骤数/温度

统一时间轴视觉

瀑布流高低交替

稳定+变化的混合美感


十二、系统架构总览流程图

视图层

状态层

数据层

ColorPalette 金麦暖色体系

TabMeta 六大工艺标签

六大 @Observed 实体类

六组模拟数据集

grainX/grainY/timelineLeft/cardHeight

selTab/selTag 选中态

breath 600ms呼吸驱动

showAdd/Edit/Del 弹窗控制

editIdx/delIdx 操作索引

六大 @State 数据列表

headerType 金黄渐变头部

strawTab 麦秆时间轴

dyeTab 染色色卡

pasteTab 拼贴工序序号

ironTab 烫画温度红色

artisanTab 匠人编删

artTab 画作尺寸

salesChart 产量图表

tabBar 底部导航

模态弹窗组


十三、技术对比总结表

技术维度 实现方式 核心特点 与前序系统差异
色彩体系 interface + const 常量 24色金麦暖色基调 以麦黄为核心色域
状态管理 @State + @Observed 13个状态变量,6个实体类 结构一致
动画驱动 setInterval 600ms 中等频率动画周期 介于500ms和700ms之间
渐变头部 linearGradient 135° 三色 深麦→中麦→背景色 金黄色调独特
麦穗动画 ForEach + height + rotate 7根麦穗,高度+角度双驱动 比扇骨多了rotate属性
布局策略 时间轴+瀑布流混合 左侧节点竖线+右侧交替高度 全新布局策略
时间轴 Circle节点 + Column竖线 cardHeight函数交替高度 麦秆画系统核心创新
色卡展示 Circle fill = item.color 数据驱动颜色显示 染色Tab独有设计
模态弹窗 Stack + zIndex(999) 标准遮罩+弹窗 按钮色用wheatD
柱状图表 纯CSS Column高度 数值×0.15系数 颜色用wheatD/wheat交替
辅助函数 grainX/grainY/timelineLeft/cardHeight 4个函数驱动动画与布局 时间轴+瀑布流核心

安装DevEco Studio程序

在这里插入图片描述

第一次打开需要点击同意一下:
在这里插入图片描述

新建一个空白模板:

在这里插入图片描述
设置API为24的模板项目:
在这里插入图片描述

初始化项目,自动下载相关依赖:

在这里插入图片描述


完整代码:

// ============================================================
// 770 麦秆画坊 · 金麦流光
// 头部样式:金黄暖色渐变Banner + 麦穗摇曳动画 + 麦芒飘动 + 巨字水印
// 布局风格:时间轴+瀑布流混合布局(左侧时间轴竖线节点 + 右侧交替高度卡片)
// 底部 6 tab 两行
// ============================================================

interface ColorPalette {
  bg: string;
  card: string;
  paper: string;
  title: string;
  sub: string;
  text3: string;
  ink: string;
  wheat: string;
  wheatL: string;
  wheatD: string;
  straw: string;
  grainA: string;
  grainB: string;
  grainC: string;
  amber: string;
  gold: string;
  cream: string;
  red: string;
  green: string;
  line: string;
  tabOn: string;
  mask: string;
  transparent: string;
  axisBg: string;
}

const COLORS: ColorPalette = {
  bg: '#FAF5E8',
  card: '#FFFFFF',
  paper: '#F0E8D0',
  title: '#3E2A0A',
  sub: '#6B5A28',
  text3: '#9B8B58',
  ink: '#2A1A00',
  wheat: '#D4A020',
  wheatL: '#E8C04A',
  wheatD: '#A88010',
  straw: '#C4A040',
  grainA: '#D4A020',
  grainB: '#E8C04A',
  grainC: '#B89010',
  amber: '#CA8A10',
  gold: '#B8860B',
  cream: '#FFF8E1',
  red: '#D32F2F',
  green: '#388E3C',
  line: '#E0D8B8',
  tabOn: '#3E2A0A',
  mask: 'rgba(0,0,0,0.5)',
  transparent: 'rgba(0,0,0,0)',
  axisBg: 'rgba(212,160,32,0.1)'
};

interface TabMeta {
  icon: string;
  label: string;
}

const TAB_LIST: TabMeta[] = [
  { icon: '麦', label: '麦秆' },
  { icon: '染', label: '染色' },
  { icon: '拼', label: '拼贴' },
  { icon: '烫', label: '烫画' },
  { icon: '匠', label: '匠人' },
  { icon: '画', label: '画作' }
];

const ROW1_IDX: number[] = [0, 1, 2];
const ROW2_IDX: number[] = [3, 4, 5];
const TAG_IDX: number[] = [0, 1, 2, 3];
const TAG_NAME_IDX: string[] = ['麦秆', '染色', '拼贴', '画作'];
const GRAIN_IDX: number[] = [0, 1, 2, 3, 4, 5, 6];
const MONTH_IDX: number[] = [0, 1, 2, 3, 4, 5];
const MONTH_NAME_IDX: string[] = ['正月', '二月', '三月', '四月', '五月', '六月'];
const SALES_IDX: number[] = [25, 40, 52, 65, 78, 55];

@Observed
export class StrawItem {
  name: string;
  kind: string;
  origin: string;
  price: number;
  constructor(name: string, kind: string, origin: string, price: number) {
    this.name = name; this.kind = kind; this.origin = origin; this.price = price;
  }
}

@Observed
export class DyeItem {
  name: string;
  kind: string;
  color: string;
  price: number;
  constructor(name: string, kind: string, color: string, price: number) {
    this.name = name; this.kind = kind; this.color = color; this.price = price;
  }
}

@Observed
export class PasteItem {
  name: string;
  kind: string;
  steps: number;
  price: number;
  desc: string;
  constructor(name: string, kind: string, steps: number, price: number, desc: string) {
    this.name = name; this.kind = kind; this.steps = steps; this.price = price; this.desc = desc;
  }
}

@Observed
export class IronItem {
  name: string;
  kind: string;
  temp: number;
  price: number;
  constructor(name: string, kind: string, temp: number, price: number) {
    this.name = name; this.kind = kind; this.temp = temp; this.price = price;
  }
}

@Observed
export class ArtisanItem {
  name: string;
  kind: string;
  years: number;
  price: number;
  constructor(name: string, kind: string, years: number, price: number) {
    this.name = name; this.kind = kind; this.years = years; this.price = price;
  }
}

@Observed
export class PaintingArtItem {
  name: string;
  kind: string;
  size: string;
  price: number;
  constructor(name: string, kind: string, size: string, price: number) {
    this.name = name; this.kind = kind; this.size = size; this.price = price;
  }
}

const mockStrawList: StrawItem[] = [
  new StrawItem('大麦秆', '常用麦秆', '河南信阳', 15),
  new StrawItem('小麦秆', '常用麦秆', '山东莱阳', 18),
  new StrawItem('燕麦秆', '特种麦秆', '河北张家口', 20),
  new StrawItem('黑麦秆', '特种麦秆', '内蒙古赤峰', 22),
  new StrawItem('荞麦秆', '杂粮秆', '陕西延安', 12),
  new StrawItem('莜麦秆', '杂粮秆', '山西大同', 14),
  new StrawItem('青稞秆', '高原麦秆', '西藏拉萨', 25),
  new StrawItem('水稻秆', '稻草', '江苏苏州', 10),
  new StrawItem('芦苇秆', '草本秆', '白洋淀', 8),
  new StrawItem('荻草秆', '草本秆', '湖北洪湖', 8)
];

const mockDyeList: DyeItem[] = [
  new DyeItem('金黄', '矿物染料', '#D4A020', 20),
  new DyeItem('赭石', '矿物染料', '#A05820', 25),
  new DyeItem('朱砂', '矿物染料', '#D42020', 30),
  new DyeItem('石青', '矿物染料', '#2050D4', 28),
  new DyeItem('藤黄', '植物染料', '#E8B820', 22),
  new DyeItem('靛蓝', '植物染料', '#2040A0', 24),
  new DyeItem('槐黄', '植物染料', '#D4A040', 18),
  new DyeItem('栀子黄', '植物染料', '#E8C040', 20),
  new DyeItem('红花', '植物染料', '#C02020', 26),
  new DyeItem('五倍子', '植物染料', '#403020', 22)
];

const mockPasteList: PasteItem[] = [
  new PasteItem('选秆', '准备工序', 1, 15, '挑选色泽均匀的麦秆。'),
  new PasteItem('浸泡', '软化工序', 1, 12, '水中浸泡使麦秆软化。'),
  new PasteItem('剖秆', '加工工序', 2, 20, '纵向剖开麦秆成薄片。'),
  new PasteItem('刮平', '加工工序', 2, 22, '刮去内芯使麦片平整。'),
  new PasteItem('裁切', '成型工序', 1, 18, '按稿样裁切麦片形状。'),
  new PasteItem('拼贴', '核心工序', 3, 35, '将麦片按设计拼贴成图。'),
  new PasteItem('熨烫', '定型工序', 1, 20, '熨斗加热使麦片服帖。'),
  new PasteItem('压平', '修整工序', 1, 15, '重物压平使画面平整。'),
  new PasteItem('装框', '收尾工序', 1, 25, '装裱入框完成作品。'),
  new PasteItem('封漆', '保护工序', 1, 30, '表面封漆保护画面。')
];

const mockIronList: IronItem[] = [
  new IronItem('低温烫', '基础烫法', 80, 15),
  new IronItem('中温烫', '基础烫法', 120, 20),
  new IronItem('高温烫', '高温烫法', 180, 30),
  new IronItem('烙铁', '手工工具', 150, 35),
  new IronItem('电烙笔', '电动工具', 200, 40),
  new IronItem('喷火枪', '高温工具', 500, 50),
  new IronItem('热风机', '辅助工具', 300, 25),
  new IronItem('温控烙铁', '精密工具', 220, 55),
  new IronItem('弧形烙', '特殊工具', 160, 38),
  new IronItem('点烫笔', '精细工具', 180, 42)
];

const mockArtisanList: ArtisanItem[] = [
  new ArtisanItem('刘麦画', '麦秆画大师', 40, 0),
  new ArtisanItem('陈剖秆', '剖秆师傅', 25, 0),
  new ArtisanItem('张拼贴', '拼贴师傅', 30, 0),
  new ArtisanItem('赵染色', '染色师傅', 22, 0),
  new ArtisanItem('王烫画', '烫画师傅', 28, 0),
  new ArtisanItem('孙装裱', '装裱师傅', 20, 0),
  new ArtisanItem('李选秆', '选秆师傅', 18, 0),
  new ArtisanItem('周封漆', '封漆师傅', 22, 0),
  new ArtisanItem('吴传承', '非遗传承', 42, 0),
  new ArtisanItem('冯学徒', '学徒', 3, 0)
];

const mockArtList: PaintingArtItem[] = [
  new PaintingArtItem('丰收图', '风景画', '40x50cm', 880),
  new PaintingArtItem('松鹤延年', '花鸟画', '35x45cm', 680),
  new PaintingArtItem('梅兰竹菊', '花卉画', '30x40cm', 580),
  new PaintingArtItem('清明上河图', '仿古画', '80x30cm', 2800),
  new PaintingArtItem('百鸟朝凤', '花鸟画', '50x70cm', 1200),
  new PaintingArtItem('岁朝清供', '花卉画', '35x50cm', 650),
  new PaintingArtItem('荷塘月色', '风景画', '40x55cm', 750),
  new PaintingArtItem('十二生肖', '动物画', '30x40cm', 550),
  new PaintingArtItem('飞天图', '人物画', '45x65cm', 980),
  new PaintingArtItem('金陵十二钗', '人物画', '50x70cm', 1500),
  new PaintingArtItem('山水图', '风景画', '60x80cm', 1800),
  new PaintingArtItem('百寿图', '书法画', '40x50cm', 880)
];

function grainX(i: number): number {
  return 15 + i * 42;
}

function grainY(i: number): number {
  return 128 + (i % 2) * 12;
}

function timelineLeft(idx: number): number {
  return 30;
}

function cardHeight(idx: number): number {
  return idx % 2 === 0 ? 70 : 55;
}

@Entry
@Component
struct Page770 {
  @State selTab: number = 0;
  @State selTag: number = 0;
  @State breath: boolean = false;
  @State showAdd: boolean = false;
  @State showEdit: boolean = false;
  @State showDel: boolean = false;
  @State editIdx: number = 0;
  @State delIdx: number = 0;
  @State strawList: StrawItem[] = mockStrawList;
  @State dyeList: DyeItem[] = mockDyeList;
  @State pasteList: PasteItem[] = mockPasteList;
  @State ironList: IronItem[] = mockIronList;
  @State artisanList: ArtisanItem[] = mockArtisanList;
  @State artList: PaintingArtItem[] = mockArtList;

  aboutToAppear() {
    setInterval(() => { this.breath = !this.breath; }, 600);
  }

  build() {
    Column() {
      this.headerType()
      Divider().strokeWidth(1).color(COLORS.line)
      Scroll() {
        Column() {
          if (this.selTab === 0) {
            this.strawTab()
          } else if (this.selTab === 1) {
            this.dyeTab()
          } else if (this.selTab === 2) {
            this.pasteTab()
          } else if (this.selTab === 3) {
            this.ironTab()
          } else if (this.selTab === 4) {
            this.artisanTab()
          } else {
            this.artTab()
          }
          this.salesChart()
        }.width('100%').padding({ left: 10, right: 10, top: 10, bottom: 100 })
      }.constraintSize({ maxHeight: '70%' }).scrollBar(BarState.Off)
      this.tabBar()
      if (this.showAdd) {
        this.addModal()
      }
      if (this.showEdit) {
        this.editModal()
      }
      if (this.showDel) {
        this.delModal()
      }
    }.width('100%').height('100%').backgroundColor(COLORS.bg)
  }

  @Builder
  headerType() {
    Stack({ alignContent: Alignment.TopStart }) {
      Column()
        .width('100%')
        .height(170)
        .linearGradient({
          angle: 135,
          colors: [[COLORS.wheatD, 0.0], [COLORS.wheat, 0.5], [COLORS.bg, 1.0]]
        })
      Row() {
        Text('麦秆画')
          .fontSize(22)
          .fontColor(COLORS.cream)
          .fontWeight(FontWeight.Bold)
        Text('·金麦流光')
          .fontSize(14)
          .fontColor(COLORS.wheatL)
          .margin({ left: 6 })
      }
      .position({ x: 14, y: 12 })
      TextInput({ placeholder: '搜索麦秆/染料/画作' })
        .fontSize(13).fontColor(COLORS.title)
        .placeholderColor(COLORS.text3)
        .backgroundColor(COLORS.card).borderRadius(8)
        .margin({ left: 12, right: 12, top: 44 })
      Row({ space: 6 }) {
        ForEach(TAG_IDX, (t: number) => {
          Column() {
            Text(TAG_NAME_IDX[t]).fontSize(11)
              .fontColor(this.selTag === t ? COLORS.cream : COLORS.wheat)
          }
          .padding({ left: 10, right: 10, top: 4, bottom: 4 })
          .backgroundColor(this.selTag === t ? COLORS.wheatD : COLORS.axisBg)
          .borderRadius(12)
          .onClick(() => { this.selTag = t; this.selTab = t; })
        }, (t: number) => 'tg' + t)
      }
      .width('100%').padding({ left: 12, right: 12 })
      .position({ x: 0, y: 88 })
      Row() {
        Text('+').fontSize(14).fontColor(COLORS.cream)
        Text('新增').fontSize(12).fontColor(COLORS.cream).margin({ left: 4 })
      }
      .padding({ left: 12, right: 12, top: 6, bottom: 6 })
      .backgroundColor(COLORS.green).borderRadius(8)
      .position({ x: 270, y: 88 })
      .onClick(() => { this.showAdd = true; })
      ForEach(GRAIN_IDX, (g: number) => {
        Column()
          .width(2)
          .height(this.breath ? 30 : 12)
          .backgroundColor(g % 2 === 0 ? COLORS.wheat : COLORS.wheatL)
          .borderRadius(1)
          .rotate({ angle: this.breath ? 8 : -8 })
          .position({ x: grainX(g), y: grainY(g) })
          .animation({ duration: 600, iterations: -1, playMode: PlayMode.Alternate })
      }, (g: number) => 'gr' + g)
      Text('麦')
        .fontSize(48)
        .fontColor('rgba(255,255,255,0.1)')
        .fontWeight(FontWeight.Bold)
        .position({ x: 260, y: 100 })
    }
    .width('100%').height(170)
  }

  @Builder
  strawTab() {
    Column({ space: 6 }) {
      Text('麦秆 · 时间轴瀑布流').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
        .width('100%').padding({ left: 4, top: 4, bottom: 4 })
      ForEach(this.strawList, (item: StrawItem, idx: number) => {
        Row() {
          Column() {
            Circle().width(12).height(12)
              .fill(idx % 3 === 0 ? COLORS.wheat : idx % 3 === 1 ? COLORS.grainB : COLORS.amber)
              .stroke(COLORS.wheatL)
              .strokeWidth(1)
            Column()
              .width(2).height(cardHeight(idx))
              .backgroundColor(COLORS.wheatL)
              .margin({ top: 2 })
          }.width(30)
          Column({ space: 4 }) {
            Text(item.name).fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
            Row() {
              Text(item.kind).fontSize(10).fontColor(COLORS.sub)
              Text(' · ' + item.origin).fontSize(10).fontColor(COLORS.text3)
            }.width('100%')
            Row() {
              Text('Y' + item.price).fontSize(12).fontColor(COLORS.wheatD).fontWeight(FontWeight.Bold)
              Column().layoutWeight(1)
              if (idx % 3 === 0) {
                Text('优').fontSize(9).fontColor(COLORS.cream)
                  .backgroundColor(COLORS.green).padding({ left: 4, right: 4, top: 1, bottom: 1 }).borderRadius(3)
              }
            }.width('100%')
          }
          .layoutWeight(1).alignItems(HorizontalAlign.Start).padding({ left: 6 })
        }
        .width('100%').padding({ left: 8, right: 8, top: 4, bottom: 4 })
        .backgroundColor(COLORS.card).borderRadius(8)
        .margin({ top: 2 })
        .onClick(() => { this.editIdx = idx; this.showEdit = true; })
      }, (item: StrawItem) => item.name)
    }.width('100%')
  }

  @Builder
  dyeTab() {
    Column({ space: 6 }) {
      Row() {
        Text('+').fontSize(16).fontColor(COLORS.tabOn)
        Text('新增染料').fontSize(13).fontColor(COLORS.tabOn).margin({ left: 4 })
      }
      .width('100%').padding({ left: 12, top: 8, bottom: 8 })
      .backgroundColor(COLORS.card).borderRadius(8)
      .onClick(() => { this.showAdd = true; })
      ForEach(this.dyeList, (item: DyeItem, idx: number) => {
        Row() {
          Column() {
            Circle().width(12).height(12)
              .fill(idx % 3 === 0 ? COLORS.wheat : idx % 3 === 1 ? COLORS.grainB : COLORS.amber)
              .stroke(COLORS.wheatL)
              .strokeWidth(1)
            Column()
              .width(2).height(cardHeight(idx))
              .backgroundColor(COLORS.wheatL)
              .margin({ top: 2 })
          }.width(30)
          Column({ space: 4 }) {
            Row() {
              Circle().width(16).height(16)
                .fill(item.color)
                .stroke(COLORS.line)
                .strokeWidth(1)
              Text(item.name).fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
                .margin({ left: 6 })
            }
            Text(item.kind).fontSize(10).fontColor(COLORS.sub)
            Text('Y' + item.price).fontSize(12).fontColor(COLORS.wheatD).fontWeight(FontWeight.Bold)
          }
          .layoutWeight(1).alignItems(HorizontalAlign.Start).padding({ left: 6 })
        }
        .width('100%').padding({ left: 8, right: 8, top: 4, bottom: 4 })
        .backgroundColor(idx % 2 === 0 ? COLORS.card : COLORS.paper)
        .borderRadius(8).margin({ top: 2 })
        .onClick(() => { this.editIdx = idx; this.showEdit = true; })
      }, (item: DyeItem) => item.name)
    }.width('100%')
  }

  @Builder
  pasteTab() {
    Column({ space: 6 }) {
      ForEach(this.pasteList, (item: PasteItem, idx: number) => {
        Row() {
          Column() {
            Text((idx + 1).toString())
              .fontSize(11).fontColor(COLORS.cream).fontWeight(FontWeight.Bold)
            Circle().width(10).height(10)
              .fill(idx % 3 === 0 ? COLORS.wheat : idx % 3 === 1 ? COLORS.grainB : COLORS.amber)
              .stroke(COLORS.wheatL)
              .strokeWidth(1)
              .margin({ top: 2 })
            Column()
              .width(2).height(cardHeight(idx) - 20)
              .backgroundColor(COLORS.wheatL)
              .margin({ top: 2 })
          }.width(30)
          Column({ space: 4 }) {
            Row() {
              Text(item.name).fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
              Column().layoutWeight(1)
              Text(item.steps + '步').fontSize(9).fontColor(COLORS.cream)
                .backgroundColor(COLORS.green).padding({ left: 4, right: 4, top: 1, bottom: 1 }).borderRadius(3)
              Text(' Y' + item.price).fontSize(12).fontColor(COLORS.wheatD).fontWeight(FontWeight.Bold)
                .margin({ left: 6 })
            }.width('100%')
            Text(item.kind).fontSize(10).fontColor(COLORS.sub)
            Text(item.desc).fontSize(10).fontColor(COLORS.text3)
            Row({ space: 8 }) {
              Text('编辑').fontSize(10).fontColor(COLORS.tabOn)
                .onClick(() => { this.editIdx = idx; this.showEdit = true; })
              Text('删除').fontSize(10).fontColor(COLORS.red)
                .onClick(() => { this.delIdx = idx; this.showDel = true; })
            }.width('100%')
          }
          .layoutWeight(1).alignItems(HorizontalAlign.Start).padding({ left: 6 })
        }
        .width('100%').padding({ left: 8, right: 8, top: 4, bottom: 4 })
        .backgroundColor(COLORS.card).borderRadius(8)
        .margin({ top: 2 })
      }, (item: PasteItem) => item.name)
    }.width('100%')
  }

  @Builder
  ironTab() {
    Column({ space: 6 }) {
      ForEach(this.ironList, (item: IronItem, idx: number) => {
        Row() {
          Column() {
            Circle().width(10).height(10)
              .fill(idx % 3 === 0 ? COLORS.wheat : idx % 3 === 1 ? COLORS.grainB : COLORS.amber)
              .stroke(COLORS.wheatL)
              .strokeWidth(1)
            Column()
              .width(2).height(cardHeight(idx))
              .backgroundColor(COLORS.wheatL)
              .margin({ top: 2 })
          }.width(30)
          Column({ space: 4 }) {
            Row() {
              Text(item.name).fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
              Column().layoutWeight(1)
              Text(item.temp + 'C').fontSize(11).fontColor(COLORS.red).fontWeight(FontWeight.Bold)
            }.width('100%')
            Text(item.kind).fontSize(10).fontColor(COLORS.sub)
            Text('Y' + item.price).fontSize(12).fontColor(COLORS.wheatD).fontWeight(FontWeight.Bold)
          }
          .layoutWeight(1).alignItems(HorizontalAlign.Start).padding({ left: 6 })
        }
        .width('100%').padding({ left: 8, right: 8, top: 4, bottom: 4 })
        .backgroundColor(idx % 2 === 0 ? COLORS.card : COLORS.paper)
        .borderRadius(8).margin({ top: 2 })
        .onClick(() => { this.editIdx = idx; this.showEdit = true; })
      }, (item: IronItem) => item.name)
    }.width('100%')
  }

  @Builder
  artisanTab() {
    Column({ space: 6 }) {
      ForEach(this.artisanList, (item: ArtisanItem, idx: number) => {
        Row() {
          Column() {
            Circle().width(10).height(10)
              .fill(idx % 3 === 0 ? COLORS.wheat : idx % 3 === 1 ? COLORS.grainB : COLORS.amber)
              .stroke(COLORS.wheatL)
              .strokeWidth(1)
            Column()
              .width(2).height(cardHeight(idx))
              .backgroundColor(COLORS.wheatL)
              .margin({ top: 2 })
          }.width(30)
          Column({ space: 4 }) {
            Text(item.name).fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
            Text(item.kind).fontSize(10).fontColor(COLORS.sub)
            Row() {
              Text(item.years + '年经验').fontSize(10).fontColor(COLORS.text3)
              Column().layoutWeight(1)
              Row({ space: 6 }) {
                Text('编').fontSize(9).fontColor(COLORS.tabOn)
                  .onClick(() => { this.editIdx = idx; this.showEdit = true; })
                Text('删').fontSize(9).fontColor(COLORS.red)
                  .onClick(() => { this.delIdx = idx; this.showDel = true; })
              }
            }.width('100%')
          }
          .layoutWeight(1).alignItems(HorizontalAlign.Start).padding({ left: 6 })
        }
        .width('100%').padding({ left: 8, right: 8, top: 4, bottom: 4 })
        .backgroundColor(COLORS.card).borderRadius(8)
        .margin({ top: 2 })
      }, (item: ArtisanItem) => item.name)
    }.width('100%')
  }

  @Builder
  artTab() {
    Column({ space: 6 }) {
      Row() {
        Text('+').fontSize(16).fontColor(COLORS.tabOn)
        Text('新增画作').fontSize(13).fontColor(COLORS.tabOn).margin({ left: 4 })
      }
      .width('100%').padding({ left: 12, top: 8, bottom: 8 })
      .backgroundColor(COLORS.card).borderRadius(8)
      .onClick(() => { this.showAdd = true; })
      ForEach(this.artList, (item: PaintingArtItem, idx: number) => {
        Row() {
          Column() {
            Circle().width(10).height(10)
              .fill(idx % 3 === 0 ? COLORS.wheat : idx % 3 === 1 ? COLORS.grainB : COLORS.amber)
              .stroke(COLORS.wheatL)
              .strokeWidth(1)
            Column()
              .width(2).height(cardHeight(idx))
              .backgroundColor(COLORS.wheatL)
              .margin({ top: 2 })
          }.width(30)
          Column({ space: 4 }) {
            Text(item.name).fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
            Row() {
              Text(item.kind).fontSize(10).fontColor(COLORS.sub)
              Text(' · ' + item.size).fontSize(10).fontColor(COLORS.text3)
            }.width('100%')
            Text('Y' + item.price).fontSize(13).fontColor(COLORS.wheatD).fontWeight(FontWeight.Bold)
          }
          .layoutWeight(1).alignItems(HorizontalAlign.Start).padding({ left: 6 })
        }
        .width('100%').padding({ left: 8, right: 8, top: 4, bottom: 4 })
        .backgroundColor(idx % 2 === 0 ? COLORS.card : COLORS.paper)
        .borderRadius(8).margin({ top: 2 })
        .onClick(() => { this.editIdx = idx; this.showEdit = true; })
      }, (item: PaintingArtItem) => item.name)
    }.width('100%')
  }

  @Builder
  salesChart() {
    Column({ space: 8 }) {
      Row() {
        Text('月度画作产量').fontSize(14).fontColor(COLORS.title).layoutWeight(1)
        Text('幅').fontSize(11).fontColor(COLORS.text3)
      }.width('100%')
      Row({ space: 4 }) {
        ForEach(MONTH_IDX, (m: number) => {
          Column({ space: 4 }) {
            Text(SALES_IDX[m].toString())
              .fontSize(9).fontColor(COLORS.sub)
            Column()
              .width(24)
              .height(SALES_IDX[m] * 0.15)
              .backgroundColor(m % 2 === 0 ? COLORS.wheatD : COLORS.wheat)
              .borderRadius(4)
            Text(MONTH_NAME_IDX[m])
              .fontSize(9)
              .fontColor(COLORS.text3)
          }
          .layoutWeight(1)
          .alignItems(HorizontalAlign.Center)
        }, (m: number) => 'ch' + m)
      }
      .width('100%').height(60)
      .alignItems(VerticalAlign.Bottom)
    }
    .width('100%').padding(12)
    .backgroundColor(COLORS.card).borderRadius(10)
    .margin({ top: 10 })
  }

  @Builder
  tabBar() {
    Column({ space: 6 }) {
      Row() {
        ForEach(ROW1_IDX, (i: number) => {
          Column({ space: 2 }) {
            Text(TAB_LIST[i].icon).fontSize(16).fontColor(this.selTab === i ? COLORS.tabOn : COLORS.sub)
            Text(TAB_LIST[i].label).fontSize(9).fontColor(this.selTab === i ? COLORS.tabOn : COLORS.sub)
          }
          .layoutWeight(1).height(48)
          .backgroundColor(this.selTab === i ? COLORS.axisBg : COLORS.card)
          .borderRadius(8)
          .justifyContent(FlexAlign.Center)
          .onClick(() => { this.selTab = i; })
        }, (i: number) => 't1' + i)
      }.width('100%')
      Row() {
        ForEach(ROW2_IDX, (i: number) => {
          Column({ space: 2 }) {
            Text(TAB_LIST[i].icon).fontSize(16).fontColor(this.selTab === i ? COLORS.tabOn : COLORS.sub)
            Text(TAB_LIST[i].label).fontSize(9).fontColor(this.selTab === i ? COLORS.tabOn : COLORS.sub)
          }
          .layoutWeight(1).height(48)
          .backgroundColor(this.selTab === i ? COLORS.axisBg : COLORS.card)
          .borderRadius(8)
          .justifyContent(FlexAlign.Center)
          .onClick(() => { this.selTab = i; })
        }, (i: number) => 't2' + i)
      }.width('100%')
    }
    .width('100%').padding({ left: 8, right: 8, top: 6, bottom: 6 })
    .backgroundColor(COLORS.card)
  }

  @Builder
  modalOverlay(onClose: () => void) {
    Column()
      .width('100%')
      .height('100%')
      .backgroundColor(COLORS.mask)
      .onClick(() => { onClose(); })
  }

  @Builder
  addModal() {
    Stack({ alignContent: Alignment.Center }) {
      this.modalOverlay(() => { this.showAdd = false; })
      Column({ space: 12 }) {
        Text('新增记录').fontSize(16).fontColor(COLORS.title)
        TextInput({ placeholder: '名称' })
          .fontSize(13).fontColor(COLORS.title)
          .backgroundColor(COLORS.paper).borderRadius(8)
        TextInput({ placeholder: '种类' })
          .fontSize(13).fontColor(COLORS.title)
          .backgroundColor(COLORS.paper).borderRadius(8)
        TextInput({ placeholder: '数值' })
          .fontSize(13).fontColor(COLORS.title)
          .backgroundColor(COLORS.paper).borderRadius(8)
        TextInput({ placeholder: '单价' })
          .fontSize(13).fontColor(COLORS.title)
          .backgroundColor(COLORS.paper).borderRadius(8)
        Row({ space: 10 }) {
          Column() {
            Text('确定').fontSize(13).fontColor(COLORS.cream)
          }
          .layoutWeight(1).height(38)
          .backgroundColor(COLORS.wheatD).borderRadius(8)
          .justifyContent(FlexAlign.Center)
          .onClick(() => { this.showAdd = false; })
          Column() {
            Text('取消').fontSize(13).fontColor(COLORS.sub)
          }
          .layoutWeight(1).height(38)
          .backgroundColor(COLORS.line).borderRadius(8)
          .justifyContent(FlexAlign.Center)
          .onClick(() => { this.showAdd = false; })
        }.width('100%')
      }
      .width('80%').padding(20)
      .backgroundColor(COLORS.card).borderRadius(12)
      .constraintSize({ maxHeight: '80%' })
    }
    .width('100%').height('100%')
    .position({ x: 0, y: 0 })
    .zIndex(999)
    .backgroundColor(COLORS.transparent)
  }

  @Builder
  editModal() {
    Stack({ alignContent: Alignment.Center }) {
      this.modalOverlay(() => { this.showEdit = false; })
      Column({ space: 12 }) {
        Text('编辑第 ' + (this.editIdx + 1) + ' 条记录').fontSize(16).fontColor(COLORS.title)
        TextInput({ placeholder: '名称' })
          .fontSize(13).fontColor(COLORS.title)
          .backgroundColor(COLORS.paper).borderRadius(8)
        TextInput({ placeholder: '种类' })
          .fontSize(13).fontColor(COLORS.title)
          .backgroundColor(COLORS.paper).borderRadius(8)
        TextInput({ placeholder: '数值' })
          .fontSize(13).fontColor(COLORS.title)
          .backgroundColor(COLORS.paper).borderRadius(8)
        TextInput({ placeholder: '单价' })
          .fontSize(13).fontColor(COLORS.title)
          .backgroundColor(COLORS.paper).borderRadius(8)
        Row({ space: 10 }) {
          Column() {
            Text('保存').fontSize(13).fontColor(COLORS.cream)
          }
          .layoutWeight(1).height(38)
          .backgroundColor(COLORS.wheatD).borderRadius(8)
          .justifyContent(FlexAlign.Center)
          .onClick(() => { this.showEdit = false; })
          Column() {
            Text('取消').fontSize(13).fontColor(COLORS.sub)
          }
          .layoutWeight(1).height(38)
          .backgroundColor(COLORS.line).borderRadius(8)
          .justifyContent(FlexAlign.Center)
          .onClick(() => { this.showEdit = false; })
        }.width('100%')
      }
      .width('80%').padding(20)
      .backgroundColor(COLORS.card).borderRadius(12)
      .constraintSize({ maxHeight: '80%' })
    }
    .width('100%').height('100%')
    .position({ x: 0, y: 0 })
    .zIndex(999)
    .backgroundColor(COLORS.transparent)
  }

  @Builder
  delModal() {
    Stack({ alignContent: Alignment.Center }) {
      this.modalOverlay(() => { this.showDel = false; })
      Column({ space: 12 }) {
        Text('确认删除').fontSize(16).fontColor(COLORS.title)
        Text('删除第 ' + (this.delIdx + 1) + ' 条记录?').fontSize(13).fontColor(COLORS.sub)
        Text('删除后不可恢复,请谨慎操作。').fontSize(11).fontColor(COLORS.text3)
        Row({ space: 10 }) {
          Column() {
            Text('确认删除').fontSize(13).fontColor(COLORS.cream)
          }
          .layoutWeight(1).height(38)
          .backgroundColor(COLORS.red).borderRadius(8)
          .justifyContent(FlexAlign.Center)
          .onClick(() => { this.showDel = false; })
          Column() {
            Text('取消').fontSize(13).fontColor(COLORS.sub)
          }
          .layoutWeight(1).height(38)
          .backgroundColor(COLORS.line).borderRadius(8)
          .justifyContent(FlexAlign.Center)
          .onClick(() => { this.showDel = false; })
        }.width('100%')
      }
      .width('70%').padding(20)
      .backgroundColor(COLORS.card).borderRadius(12)
      .constraintSize({ maxHeight: '80%' })
    }
    .width('100%').height('100%')
    .position({ x: 0, y: 0 })
    .zIndex(999)
    .backgroundColor(COLORS.transparent)
  }
}


![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/0a81f6ab0dca4e1fbff9ab6774ee09e5在这里插入图片描述
png#pic_center)

十四、深度总结

麦秆画工艺数字化管理系统以其独创的"时间轴+瀑布流"混合布局和金麦暖色视觉基调,在传统手工艺数字化管理系统中开辟了全新的设计范式。

在布局创新方面,系统突破了前序系统的"扇形倾斜排列"和"3列网格"两种布局模式,引入了"时间轴+瀑布流"的混合信息架构。这一布局的核心由两个函数驱动:timelineLeft 确保所有时间节点和连接线位于距左边缘30像素的统一位置,形成贯穿整个列表的竖直时间轴;cardHeight 通过 idx % 2 返回70或55像素的交替高度,使右侧内容区呈现"高-矮-高-矮"的瀑布流节奏。左侧的稳定与右侧的变化形成了一种"静中有动"的视觉张力——时间轴提供了数据的序列感和历史纵深感,瀑布流提供了视觉的灵动感和浏览的趣味性。这种布局特别适合麦秆画工艺的数据特点——从选秆到封漆的十道工序本身就是一个时间序列,时间轴布局让这一工艺流程的时序性得到了直观呈现。

在动画设计方面,系统以麦穗摇曳动画替代了前序系统的扇骨开合和木屑飞溅。七根麦穗通过 grainXgrainY 函数定位在头部区域,配合 breath 状态的600毫秒翻转驱动高度和旋转角度的同步变化。与折扇系统仅有高度变化的扇骨动画相比,麦穗动画增加了 rotate 旋转属性——角度在-8度和+8度之间切换,使得麦穗不仅在垂直方向上伸缩,还在水平方向上左右摇摆,更真实地模拟了风吹麦田的动态效果。600毫秒的间隔恰好处于折扇的700毫秒和核雕的500毫秒之间,模拟了自然风的温和节奏——不急不缓,恰到好处。

在色彩策略方面,系统以金黄色为核心色域,从背景色 #FAF5E8 到麦色系列 wheat/wheatL/wheatD,整体色调温暖明亮,契合麦秆画"金麦流光"的艺术特质。新增按钮使用绿色背景是一个精心的设计决策——绿色代表麦秆的植物起源,与金黄色的成品色调形成了"从绿叶到金穗"的工艺转化叙事。染色列表中的"数据驱动颜色"设计更是系统在信息可视化方面的亮点——每种染料以其真实颜色值直接渲染为色块,让列表本身成为一个可视化色卡,用户无需阅读文字即可快速辨识不同染料的色彩属性。

在数据建模方面,系统针对麦秆画工艺的六大维度设计了差异化的数据模型。DyeItemcolor 字段(存储十六进制颜色值)和 IronItemtemp 字段(存储工作温度值)是两个最具工艺特色的数据字段——前者直接驱动 UI 渲染,后者以红色文字突出显示作为安全提示。PasteItemstepsdesc 字段为拼贴工序提供了步骤复杂度和文字说明两个维度的信息。这种"因工艺而异"的建模方式确保了数据表达与业务需求的精准匹配。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐