HarmonyOS 6.1.1街头涂鸦艺术装备喷漆马克笔贴纸电商系统技术实战——基于HarmonyOS API 24暗黑霓虹风UI设计全解析

街头涂鸦文化作为亚文化的重要组成部分,正从小众走向大众视野。从墙绘艺术到潮流周边,涂鸦创作者需要专业的工具装备来实现创意表达。本文将深入剖析一款以暗黑霓虹为视觉风格的街头涂鸦艺术装备应用,从架构设计、动画效果到交互体验,全方位展示HarmonyOS ArkTS在潮流文化领域的技术表现力。

该应用以"GRAFFITI ZONE"为品牌核心,围绕涂鸦创作生态构建了喷漆、马克笔、贴纸、防护装备、个人中心六大模块。采用纯黑背景搭配荧光绿与电粉紫的霓虹配色,营造出强烈的街头艺术氛围。技术上采用组件化分层架构与函数式数据处理,确保代码质量与运行性能。

引言

在这里插入图片描述

街头涂鸦艺术自上世纪60年代诞生于费城和纽约以来,已经从城市边缘的亚文化现象发展成为全球性的艺术潮流。在中国,随着年轻一代审美意识的觉醒和潮流文化的兴起,涂鸦艺术正在以更包容的姿态走进公众视野。涂鸦创作者们需要专业的喷漆、马克笔、贴纸等工具来释放创意,而一款专为涂鸦爱好者打造的装备选购应用,能够精准满足这一细分市场的需求。

本应用正是为涂鸦创作者量身打造的专业装备平台,它的视觉设计大胆而前卫——纯黑背景如同深夜的城市街头,荧光绿作为主色象征着喷漆罐喷射出的明亮色彩,电粉紫作为强调色增添了迷幻的霓虹感。整体风格强烈而统一,第一眼就能让用户感受到浓厚的街头文化气息。技术架构上,应用采用模块化组件设计,将喷漆、马克笔、贴纸、防护装备等品类分别封装为独立的Tab组件,每个组件内部实现完整的浏览、筛选、详情查看和数据管理功能。

在动画与交互设计方面,应用更是将涂鸦美学发挥到极致。喷漆扩散动画模拟颜料喷射的瞬间张力,滴落效果重现油漆流淌的视觉记忆,发光呼吸营造霓虹招牌的闪烁感,旋转光效增添动感活力。这些精心设计的微动画不仅提升了视觉体验,更成为品牌识别的重要元素。此外,应用还内置了创作社区功能,让涂鸦爱好者们能够交流作品、分享技巧,构建活跃的创作者社群。

暗黑霓虹主题系统

在这里插入图片描述

配色方案设计

应用的主题配色采用经典的暗黑霓虹风格,以纯黑为底,荧光绿为主色,电粉紫为强调色。这种高对比度的配色方案在黑暗背景下产生强烈的视觉冲击力,完美契合街头涂鸦文化的反叛精神与潮流特质。

interface GrTheme {
  bg: string
  primary: string
  accent: string
  card: string
  text: string
  sub: string
  line: string
  glow: string
  dark: string
}

const GR: GrTheme = {
  bg: '#0D0D0D',
  primary: '#39FF14',
  accent: '#FF2D95',
  card: '#1A1A1A',
  text: '#F0F0F0',
  sub: '#8C8C8C',
  line: '#2A2A2A',
  glow: '#39FF14',
  dark: '#080808'
}

暗黑霓虹风格的核心在于"黑得纯粹,亮得耀眼"。背景色使用接近纯黑的#0D0D0D,确保霓虹色的饱和度能够充分展现;主色选用经典的荧光绿#39FF14,这是涂鸦喷漆中最具代表性的颜色之一;强调色选用电粉紫#FF2D95,与荧光绿形成冷暖对比,增强视觉层次。这种配色方案虽然饱和度极高,但通过大面积黑色的平衡,整体视觉并不会显得过于刺眼。

主题系统中的glow字段与primary使用相同色值,这是因为在霓虹风格设计中,发光效果通常就是主色的半透明扩散。dark字段则使用了更深的#080808,用于头部导航栏和底部Tab栏,与稍浅的卡片背景形成层次区分。

数据模型与业务品类

在这里插入图片描述

应用的数据模型围绕涂鸦装备的核心品类展开,包括喷漆、马克笔、贴纸、防护装备四大类,每一类都有其独特的属性字段,真实反映了涂鸦装备的专业特性。

interface GrSpray {
  name: string
  brand: string
  color: string
  colorHex: string
  pressure: number
  volume: number
  coverage: number
  stock: number
  tag: string
  sold: number
}

interface GrMarker {
  name: string
  brand: string
  tip: string
  ink: number
  waterproof: number
  size: number
  stock: number
  sold: number
}

interface GrSticker {
  name: string
  size: string
  material: string
  count: number
  stock: number
  sold: number
}

interface GrGuard {
  name: string
  kind: string
  material: string
  level: number
  stock: number
  sold: number
}

喷漆数据是应用中最丰富的品类,包含了品牌、颜色名称、色值、压力、容量、覆盖面积、库存、标签和销量等完整属性。其中colorHex字段存储颜色的十六进制值,使得应用可以直接用真实颜色展示喷漆效果,而不仅仅是文字描述。马克笔数据则关注笔头类型、墨水量、防水性和尺寸等创作者关心的参数。防护装备数据包含防护种类、材质和防护等级,体现了对创作者健康安全的重视。

动画系统:涂鸦美学的动态表达

在这里插入图片描述

喷漆扩散与发光效果

在这里插入图片描述

应用最具特色的动画是喷漆扩散效果,它模拟了喷漆从喷嘴喷出后逐渐扩散的过程。色点的大小随时间周期性变化,配合透明度的呼吸效果,仿佛颜料正在空气中弥漫开来。

function grSprayR(wave: number, i: number): number {
  let base: number = (wave * 2 + i * 37) % 80
  return 0.8 + base / 200
}

function grGlow(wave: number, i: number): number {
  let base: number = (wave + i * 13) % 100
  return 0.4 + Math.abs(Math.sin(base / 15)) * 0.6
}

function grSpin(wave: number, i: number): number {
  return (wave * 3 + i * 45) % 360
}

喷漆扩散动画的设计灵感来源于真实的喷漆过程。当按下喷漆罐的喷嘴时,颜料以一定的压力喷出,在空气中形成逐渐扩大的圆形雾状区域。grSprayR函数通过模运算实现半径的周期性变化,模拟了连续喷射时的脉冲效果。配合grGlow函数实现的透明度变化,进一步增强了喷漆气雾的层次感和真实感。

grSprayR函数计算色点的缩放比例,取值范围在0.8到1.2之间,形成大小交替的脉动效果。grGlow函数基于正弦函数计算透明度,产生呼吸灯般的发光效果。grSpin函数则实现旋转动画,可用于装饰元素的旋转或喷漆罐的转动效果。这些函数都接受索引参数i,通过索引偏移实现同组元素的错峰动画,避免单调感。

滴落与浮动效果

在这里插入图片描述

滴落动画模拟了油漆在垂直表面上向下流淌的效果,是涂鸦视觉语言中的经典元素。浮动动画则让元素产生上下漂浮的动感,增强页面的活力。

function grDrip(wave: number, i: number): number {
  let base: number = (wave * 2 + i * 29) % 100
  return base * 0.25
}

function grFloatY(wave: number, i: number): number {
  let base: number = (wave + i * 23) % 100
  return Math.sin(base / 16) * 5
}

function grBlink(wave: number): number {
  let base: number = wave % 100
  return base < 50 ? 1 : 0.4
}

grDrip函数通过线性递增的位移值模拟油漆向下流淌的效果,结合模运算实现循环。grFloatY函数基于正弦函数实现平滑的上下浮动,振幅为5像素。grBlink函数实现了闪烁效果,通过模运算将时间分为亮和暗两个阶段,产生类似霓虹灯闪烁的视觉效果。这些动画函数虽然实现简单,但在涂鸦风格的UI中能够产生强烈的风格化视觉效果。

主应用架构与导航

在这里插入图片描述

根组件设计

应用的根组件采用Stack + Column的经典布局结构,黑色背景贯穿整个应用,为霓虹色元素提供了完美的展示舞台。头部导航栏、搜索框、内容区和底部Tab栏自上而下排列,结构清晰明了。

@Entry
@Component
struct GrApp {
  @State currentTab: number = 0
  @State wave: number = 0
  private timer: number = -1

  aboutToAppear() {
    this.timer = setInterval(() => {
      this.wave = (this.wave + 1) % 10000
    }, 60)
  }

  aboutToDisappear() {
    if (this.timer !== -1) {
      clearInterval(this.timer)
    }
  }

  build() {
    Stack() {
      Column() {
        Row() {
          Text('GRAFFITI')
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor(GR.primary)
          Text(' ZONE')
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor(GR.text)
          Column().layoutWeight(1)
          Text('search')
            .fontSize(12)
            .fontColor(GR.sub)
            .margin({ right: 12 })
          Text('cart')
            .fontSize(12)
            .fontColor(GR.sub)
        }
        .width('100%')
        .height(48)
        .padding({ left: 16, right: 16 })
        .backgroundColor(GR.dark)
        .alignItems(VerticalAlign.Center)
        .border({ width: { bottom: 1 }, color: GR.line })
        // ... 搜索栏、内容区、Tab栏
      }
      .width('100%')
      .height('100%')
      .backgroundColor(GR.bg)
    }
  }
}

品牌Logo的设计颇具巧思——"GRAFFITI"使用荧光绿色,"ZONE"使用白色,两个词拼接在一起形成鲜明的视觉对比,同时也呼应了喷漆双色喷涂的创作手法。根组件在全局层面维护了一个动画帧计数器wave,这个计数器可以传递给子组件使用,确保全局动画的同步性。

底部导航栏

底部Tab栏采用深色背景配合荧光色选中态,六个Tab分别对应首页、喷漆、马克笔、贴纸、防护和我的页面。图标使用emoji实现,简洁而富有表现力。

const GR_TABS: GrTab[] = [
  { name: '首页', icon: '🏠' },
  { name: '喷漆', icon: '🎨' },
  { name: '马克笔', icon: '✏️' },
  { name: '贴纸', icon: '📎' },
  { name: '防护', icon: '🦺' },
  { name: '我的', icon: '👤' }
]

Tab栏的选中态通过透明度和颜色变化来区分,当前选中的Tab使用荧光绿主色,未选中的Tab使用灰色次级色,透明度从1降低到0.4。这种设计简洁而有效,在深色背景上提供了清晰的导航指引。

功能流程图

首页功能

喷漆

马克笔

贴纸

防护

我的

编辑

删除

新增

应用启动

加载GrApp根组件

初始化动画定时器

显示首页Tab

品牌展示区

色卡喷雾动画

数据统计区

品牌数/款色数/活动数

功能入口

线下活动

涂鸦课堂

限定预购

创作社区

切换Tab

选择品类

GrSprayTab

GrMarkerTab

GrStickerTab

GrGuardTab

GrMineTab

按标签筛选

双列色卡展示

点击商品

详情弹窗

操作选择

编辑商品信息

确认删除

上架新商品

数据更新

活动报名弹窗

课程报名弹窗

预购下单弹窗

社区加入弹窗

喷漆Tab:色卡展示与销量排行

筛选与商品网格

喷漆Tab是应用的核心功能模块,它以横向滚动的标签筛选器 + 双列色卡网格 + 销量排行柱状图的组合布局,全面展示喷漆产品信息。筛选标签支持全部、速干、荧光、高压、夜光、金属等多个维度,帮助用户快速定位所需的喷漆类型。

@Component
struct GrSprayTab {
  @State wave: number = 0
  @State chip: number = 0
  @State sprays: GrSpray[] = []
  @State ranked: GrSpray[] = []
  @State showDetail: boolean = false
  @State showAdd: boolean = false
  @State showEdit: boolean = false
  @State showDel: boolean = false
  @State selIdx: number = 0
  private timer: number = -1

  aboutToAppear() {
    this.sprays = GR_SPRAYS.slice()
    this.ranked = grSoldRank().slice(0, 6)
    this.timer = setInterval(() => {
      this.wave = (this.wave + 1) % 10000
    }, 60)
  }
  // ...
}

组件在初始化时不仅加载了完整的喷漆数据,还预先计算了销量排名前6的商品数据,用于销量排行柱状图的展示。这种预计算策略避免了每次渲染时都进行排序操作,有助于提升性能。动画帧计数器的模值设置为10000,确保动画能够长时间运行而不会出现明显的跳变。

色卡商品卡片设计

喷漆商品的展示采用了色卡式设计,每个商品卡片顶部是一个圆形色点,直接展示喷漆的真实颜色。色点应用了喷漆扩散和发光动画,仿佛正在喷射的颜料,极具视觉冲击力。

Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
  ForEach(this.sprays, (s: GrSpray, i: number) => {
    Column() {
      Column()
        .width(48)
        .height(48)
        .borderRadius(24)
        .backgroundColor(s.colorHex)
        .scale({ x: grSprayR(this.wave, i), y: grSprayR(this.wave, i) })
        .opacity(grGlow(this.wave, i))
        .border({ width: 2, color: GR.dark })

      Text(s.name)
        .fontSize(11)
        .fontColor(GR.text)
        .margin({ top: 8 })
      Text(s.brand + ' · ' + s.color)
        .fontSize(9)
        .fontColor(GR.sub)
        .margin({ top: 2 })

      Row() {
        Text('覆盖')
          .fontSize(8)
          .fontColor(GR.sub)
          .width(24)
        Stack({ alignContent: Alignment.Start }) {
          Row()
            .width('100%')
            .height(3)
            .borderRadius(2)
            .backgroundColor(GR.dark)
          Row()
            .width(grCoverageW(s.coverage))
            .height(3)
            .borderRadius(2)
            .backgroundColor(GR.primary)
        }
        .layoutWeight(1)
        .height(3)
      }
      .width('100%')
      .margin({ top: 6 })

      Row() {
        Text('压力')
          .fontSize(8)
          .fontColor(GR.sub)
          .width(24)
        Stack({ alignContent: Alignment.Start }) {
          Row()
            .width('100%')
            .height(3)
            .borderRadius(2)
            .backgroundColor(GR.dark)
          Row()
            .width(grPressureW(s.pressure))
            .height(3)
            .borderRadius(2)
            .backgroundColor(GR.accent)
        }
        .layoutWeight(1)
        .height(3)
      }
      .width('100%')
      .margin({ top: 4 })

      Row() {
        Text('¥' + (s.volume / 10 + 29))
          .fontSize(13)
          .fontWeight(FontWeight.Bold)
          .fontColor(GR.primary)
        Column().layoutWeight(1)
        Text('销' + s.sold)
          .fontSize(9)
          .fontColor(GR.sub)
      }
      .width('100%')
      .margin({ top: 6 })

      Row() {
        Text('详情')
          .fontSize(9)
          .fontColor(GR.sub)
          .layoutWeight(1)
          .textAlign(TextAlign.Center)
          .padding({ top: 4, bottom: 4 })
          .borderRadius(6)
          .backgroundColor(GR.dark)
          .onClick(() => {
            this.selIdx = i
            this.showDetail = true
          })
        Text('编辑')
          .fontSize(9)
          .fontColor(GR.sub)
          .layoutWeight(1)
          .textAlign(TextAlign.Center)
          .padding({ top: 4, bottom: 4 })
          .borderRadius(6)
          .backgroundColor(GR.dark)
          .margin({ left: 4 })
          .onClick(() => {
            this.selIdx = i
            this.showEdit = true
          })
        Text('删除')
          .fontSize(9)
          .fontColor(GR.accent)
          .layoutWeight(1)
          .textAlign(TextAlign.Center)
          .padding({ top: 4, bottom: 4 })
          .borderRadius(6)
          .backgroundColor(GR.dark)
          .margin({ left: 4 })
          .onClick(() => {
            this.selIdx = i
            this.showDel = true
          })
      }
      .width('100%')
      .margin({ top: 6 })
    }
    .width('48.5%')
    .padding(12)
    .backgroundColor(GR.card)
    .borderRadius(14)
    .border({ width: 1, color: GR.line })
    .margin({ top: 8 })
  }, (s: GrSpray) => s.name)
}
.width('92%')
.margin({ top: 6 })

色卡式商品展示是喷漆类电商的最佳实践。相比于文字描述,真实的颜色展示能够让用户更直观地了解产品,大幅提升购物决策效率。在技术实现上,直接使用颜色的十六进制值作为背景色,配合黑色边框增强轮廓,即使在暗黑模式下也能准确呈现颜色效果。同时,喷漆扩散动画的加入让静态的色卡"活"了起来,大大增强了品牌的记忆点。

每个商品卡片中还包含了两条微型进度条,分别展示覆盖面积和喷漆压力两个关键参数。覆盖面积使用荧光绿(主色),压力使用电粉紫(强调色),通过颜色编码让用户一眼就能区分不同参数。这种将数据可视化融入商品卡片的设计,既节省了空间,又提升了信息密度。

销量排行柱状图

喷漆Tab底部展示了销量排行柱状图,通过柱形高度直观对比各款喷漆的销量差异。前三名使用荧光绿色突出显示,其余使用电粉紫色,清晰地划分了第一梯队和第二梯队。

Text('销量排行')
  .fontSize(14)
  .fontWeight(FontWeight.Bold)
  .fontColor(GR.text)
  .margin({ top: 14 })

Row() {
  ForEach(this.ranked, (s: GrSpray, i: number) => {
    Column() {
      Column()
        .width(18)
        .height(Math.min(s.sold / 410 * 50, 50))
        .backgroundColor(i < 3 ? GR.primary : GR.accent)
        .borderRadius(4)
      Text(s.sold + '')
        .fontSize(8)
        .fontColor(GR.sub)
        .margin({ top: 4 })
    }
    .layoutWeight(1)
    .alignItems(HorizontalAlign.Center)
  }, (s: GrSpray) => s.name)
}
.width('92%')
.height(70)
.alignItems(VerticalAlign.Bottom)
.padding({ top: 10, bottom: 4 })
.backgroundColor(GR.card)
.borderRadius(14)

柱状图的高度通过简单的比例计算得出,以销量最高的商品(410件)为基准,最大柱高为50像素。前三名使用主色荧光绿,其他使用强调色电粉紫,这种色彩编码方式让用户能够快速识别热销产品。整个图表区域采用卡片式设计,与上方的商品网格形成视觉上的统一。

首页设计:品牌展示与社区入口

品牌横幅与数据统计

首页顶部是品牌展示区域,大号的品牌名称配合一排动态色点,直观展示了应用的喷漆主题。色点应用了喷漆扩散和发光动画,如同从喷嘴中喷射出的彩色气雾,极具视觉张力。

Column() {
  Row() {
    Text('GRAFFITI')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
      .fontColor(GR.primary)
    Text(' ZONE')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
      .fontColor(GR.text)
    Column().layoutWeight(1)
    Text('HOT')
      .fontSize(9)
      .fontColor(GR.dark)
      .padding({ left: 6, right: 6, top: 2, bottom: 2 })
      .borderRadius(4)
      .backgroundColor(GR.accent)
  }
  .width('100%')

  Row() {
    ForEach(GR_SPRAYS.slice(0, 6), (s: GrSpray, i: number) => {
      Column()
        .width(28)
        .height(28)
        .borderRadius(14)
        .backgroundColor(s.colorHex)
        .scale({ x: grSprayR(this.wave, i), y: grSprayR(this.wave, i) })
        .opacity(grGlow(this.wave, i))
        .margin({ left: i > 0 ? 6 : 0 })
    }, (s: GrSpray) => s.name)
  }
  .margin({ top: 12 })

  Text('1000+ 款色 | 30+ 品牌 | 全球直邮')
    .fontSize(10)
    .fontColor(GR.sub)
    .margin({ top: 8 })
}
.width('92%')
.padding(16)
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.line })
.margin({ top: 8 })
.alignItems(HorizontalAlign.Start)

品牌横幅的右上角有一个"HOT"标签,使用电粉紫背景配黑色文字,非常醒目。横幅下方的六个彩色圆点取自销量前六的喷漆颜色,它们以不同的节奏脉动和发光,形成一道动态的色带,完美诠释了涂鸦文化的多彩与活力。

创作社区弹窗

首页的创作社区入口是应用的社交功能核心,点击后弹出社区加入窗口,展示社区成员数、作品数、本周新作数等关键数据,并提供自我介绍和创作类型选择的入社流程。

if (this.showCommunity) {
  Stack() {
    this.modalOverlay()
    Column() {
      Row() {
        Text('💬 创作社区')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor(GR.text)
          .layoutWeight(1)
        Text('×')
          .fontSize(18)
          .fontColor(GR.sub)
          .onClick(() => {
            this.showCommunity = false
          })
      }
      .width('100%')
      .padding({ bottom: 12 })
      .border({ width: { bottom: 1 }, color: GR.line })

      Row() {
        Column() {
          Text('2.8万')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor(GR.primary)
          Text('成员')
            .fontSize(9)
            .fontColor(GR.sub)
            .margin({ top: 2 })
        }
        .layoutWeight(1)
        .alignItems(HorizontalAlign.Center)

        Column() {
          Text('1200+')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor(GR.accent)
          Text('作品')
            .fontSize(9)
            .fontColor(GR.sub)
            .margin({ top: 2 })
        }
        .layoutWeight(1)
        .alignItems(HorizontalAlign.Center)

        Column() {
          Text('86')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor(GR.primary)
          Text('本周新作')
            .fontSize(9)
            .fontColor(GR.sub)
            .margin({ top: 2 })
        }
        .layoutWeight(1)
        .alignItems(HorizontalAlign.Center)
      }
      .width('100%')
      .padding({ top: 10, bottom: 10 })
      .backgroundColor(GR.dark)
      .borderRadius(12)
      .margin({ top: 10 })
      // ... 自我介绍、创作类型选择
    }
    .width('86%')
    .constraintSize({ maxHeight: '80%' })
    .backgroundColor(GR.card)
    .borderRadius(16)
    .border({ width: 1, color: GR.line })
    .padding(16)
    .zIndex(1000)
  }
}

社区功能是提升用户粘性的重要手段。对于涂鸦这类创作型应用来说,社区不仅是用户交流作品的场所,更是品牌文化传播的重要阵地。通过设计简洁的入社流程(自我介绍 + 创作类型选择),可以有效降低用户的加入门槛,同时收集基本的用户画像数据,为后续的个性化推荐和社区运营奠定基础。

社区弹窗的数据统计区域采用三栏布局,分别展示成员数、作品数和本周新作数,使用荧光绿和电粉紫交替配色,数据突出而标签弱化,让用户一眼就能看到社区的活跃程度。整体设计延续了应用的暗黑霓虹风格,深色背景配合亮色数据,形成强烈的视觉对比。

技术点对比分析

功能模块 技术方案 设计亮点 开发效率
主题系统 接口约束 + 常量对象 暗黑霓虹配色,色值丰富层次分明 高,配置即用
色卡展示 背景色 + 扩散缩放动画 真实颜色预览,喷漆动效增强记忆点 中,需调试动画参数
参数可视化 微型进度条 + 颜色编码 高密度信息展示,双参数对比直观 高,基础组件组合
销量排行 动态柱高 + Top色彩区分 前三名高亮,快速识别热销产品 高,简单比例计算
商品筛选 索引映射 + 纯函数过滤 多标签维度,筛选逻辑清晰 高,数组操作易实现
弹窗系统 Stack叠加 + 状态控制 半透明遮罩,多弹窗复用遮罩Builder 高,@Builder复用
社区功能 数据统计 + 表单收集 三栏数据展示,入社流程简洁 中,需处理用户输入
全局动画 单定时器 + 多函数组合 动画同步性好,组合效果丰富 中,需精心设计函数

安装DevEco Studio程序

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

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

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

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

在这里插入图片描述


完整代码:

// GRAFFITI ZONE 街头涂鸦艺术装备
// GR前缀 暗黑×荧光绿×电粉紫 涂鸦街头风

interface GrTheme {
  bg: string
  primary: string
  accent: string
  card: string
  text: string
  sub: string
  line: string
  glow: string
  dark: string
}

const GR: GrTheme = {
  bg: '#0D0D0D',
  primary: '#39FF14',
  accent: '#FF2D95',
  card: '#1A1A1A',
  text: '#F0F0F0',
  sub: '#8C8C8C',
  line: '#2A2A2A',
  glow: '#39FF14',
  dark: '#080808'
}

interface GrSpray {
  name: string
  brand: string
  color: string
  colorHex: string
  pressure: number
  volume: number
  coverage: number
  stock: number
  tag: string
  sold: number
}

interface GrMarker {
  name: string
  brand: string
  tip: string
  ink: number
  waterproof: number
  size: number
  stock: number
  sold: number
}

interface GrSticker {
  name: string
  size: string
  material: string
  count: number
  stock: number
  sold: number
}

interface GrGuard {
  name: string
  kind: string
  material: string
  level: number
  stock: number
  sold: number
}

interface GrOrder {
  id: string
  item: string
  date: string
  amount: number
  status: string
}

interface GrCollect {
  name: string
  brand: string
  price: number
  date: string
}

interface GrMonth {
  month: string
  amount: number
}

interface GrTab {
  name: string
  icon: string
}

const GR_TABS: GrTab[] = [
  { name: '首页', icon: '🏠' },
  { name: '喷漆', icon: '🎨' },
  { name: '马克笔', icon: '✏️' },
  { name: '贴纸', icon: '📎' },
  { name: '防护', icon: '🦺' },
  { name: '我的', icon: '👤' }
]

const GR_SPRAYS: GrSpray[] = [
  { name: 'Hardcore 600ml', brand: 'MONTANA', color: '纯白', colorHex: '#F0F0F0', pressure: 8, volume: 600, coverage: 12, stock: 45, tag: '速干', sold: 320 },
  { name: 'MTN94 400ml', brand: 'MONTANA', color: '荧光绿', colorHex: '#39FF14', pressure: 6, volume: 400, coverage: 10, stock: 38, tag: '低压', sold: 280 },
  { name: 'Krylon Max', brand: 'KRYLON', color: '荧光粉', colorHex: '#FF2D95', pressure: 9, volume: 400, coverage: 9, stock: 22, tag: '荧光', sold: 195 },
  { name: 'Black 500ml', brand: 'MONTANA', color: '深黑', colorHex: '#0A0A0A', pressure: 10, volume: 500, coverage: 11, stock: 50, tag: '高压', sold: 410 },
  { name: 'Flame Orange', brand: 'FLAME', color: '焦橙', colorHex: '#FF6B1A', pressure: 7, volume: 400, coverage: 8, stock: 30, tag: '暖色', sold: 165 },
  { name: 'Loop Glow', brand: 'LOOP', color: '电青', colorHex: '#00F5D4', pressure: 5, volume: 400, coverage: 7, stock: 18, tag: '夜光', sold: 120 },
  { name: 'Mega 600ml', brand: 'MONTANA', color: '金黄', colorHex: '#FFD700', pressure: 8, volume: 600, coverage: 12, stock: 35, tag: '大容量', sold: 240 },
  { name: 'Hardcore2 400ml', brand: 'MONTANA', color: '紫罗兰', colorHex: '#8B5CF6', pressure: 7, volume: 400, coverage: 9, stock: 28, tag: '纯色', sold: 155 },
  { name: 'Kobra 300ml', brand: 'KOBRA', color: '湖蓝', colorHex: '#1B9AAA', pressure: 6, volume: 300, coverage: 6, stock: 42, tag: '精细', sold: 130 },
  { name: 'Banner 500ml', brand: 'BANNER', color: '银灰', colorHex: '#C0C0C0', pressure: 9, volume: 500, coverage: 10, stock: 15, tag: '金属', sold: 89 }
]

const GR_MARKERS: GrMarker[] = [
  { name: 'Sketch 0.3', brand: 'COPIC', tip: '软头', ink: 2.8, waterproof: 1, size: 3, stock: 60, sold: 450 },
  { name: 'One4All 1mm', brand: 'MOLOTOW', tip: '圆头', ink: 4, waterproof: 1, size: 1, stock: 45, sold: 380 },
  { name: 'PC-1MR 0.7', brand: 'POSCA', tip: '针头', ink: 1.5, waterproof: 0, size: 0.7, stock: 55, sold: 520 },
  { name: 'PC-5M 1.8', brand: 'POSCA', tip: '圆头', ink: 2.5, waterproof: 0, size: 1.8, stock: 70, sold: 610 },
  { name: 'Microperm 0.4', brand: 'SAKURA', tip: '针头', ink: 1.2, waterproof: 1, size: 0.4, stock: 40, sold: 290 },
  { name: 'Marker 2mm', brand: 'MONTANA', tip: '方头', ink: 5, waterproof: 1, size: 2, stock: 35, sold: 210 },
  { name: 'K-42 1mm', brand: 'KRINK', tip: '圆头', ink: 7, waterproof: 1, size: 1, stock: 25, sold: 180 },
  { name: 'Ciao 0.5', brand: 'COPIC', tip: '软头', ink: 2, waterproof: 0, size: 0.5, stock: 50, sold: 340 }
]

const GR_STICKERS: GrSticker[] = [
  { name: '街头涂鸦贴', size: '10x15cm', material: 'PVC', count: 50, stock: 80, sold: 200 },
  { name: '荧光字母贴', size: '5x5cm', material: 'PVC', count: 100, stock: 120, sold: 350 },
  { name: '喷漆罐造型', size: '8x12cm', material: '全息', count: 30, stock: 45, sold: 180 },
  { name: '复古标签贴', size: '4x6cm', material: '纸质', count: 200, stock: 150, sold: 280 },
  { name: '荧光色块贴', size: '3x3cm', material: 'PVC', count: 150, stock: 100, sold: 310 },
  { name: '涂鸦人物贴', size: '15x20cm', material: '全息', count: 20, stock: 30, sold: 120 },
  { name: '城市天际线', size: '20x30cm', material: 'PVC', count: 15, stock: 25, sold: 85 },
  { name: '迷你印章贴', size: '2x2cm', material: 'PVC', count: 300, stock: 200, sold: 420 }
]

const GR_GUARDS: GrGuard[] = [
  { name: '防毒面具Pro', kind: '呼吸防护', material: '硅胶活性炭', level: 5, stock: 35, sold: 180 },
  { name: '护目镜防雾', kind: '眼部防护', material: 'PC镜片', level: 3, stock: 50, sold: 220 },
  { name: '手套防滑', kind: '手部防护', material: '丁腈橡胶', level: 2, stock: 80, sold: 350 },
  { name: '防尘罩衣', kind: '全身防护', material: '无纺布', level: 2, stock: 40, sold: 120 },
  { name: '耳塞降噪', kind: '听力防护', material: '泡棉', level: 3, stock: 100, sold: 280 },
  { name: '安全鞋头', kind: '脚部防护', material: '钢头', level: 4, stock: 25, sold: 95 }
]

const GR_ORDERS: GrOrder[] = [
  { id: 'GR250801', item: 'Montana Hardcore 600ml', date: '08-01', amount: 89, status: '已签收' },
  { id: 'GR250728', item: 'Copic Sketch 0.3', date: '07-28', amount: 45, status: '已发货' },
  { id: 'GR250725', item: '防毒面具Pro', date: '07-25', amount: 298, status: '已签收' },
  { id: 'GR250720', item: '荧光字母贴x100', date: '07-20', amount: 35, status: '已签收' },
  { id: 'GR250715', item: 'MTN94 荧光绿', date: '07-15', amount: 78, status: '已签收' },
  { id: 'GR250710', item: 'Posca PC-5M 1.8', date: '07-10', amount: 28, status: '已签收' }
]

const GR_COLLECTS: GrCollect[] = [
  { name: 'Black 深黑', brand: 'MONTANA', price: 89, date: '08-01' },
  { name: 'Max 荧光粉', brand: 'KRYLON', price: 128, date: '07-28' },
  { name: 'Glow 电青', brand: 'LOOP', price: 158, date: '07-20' },
  { name: 'Sketch 0.3', brand: 'COPIC', price: 45, date: '07-15' },
  { name: 'K-42 1mm', brand: 'KRINK', price: 198, date: '07-10' },
  { name: 'Banner 银灰', brand: 'BANNER', price: 168, date: '07-05' }
]

const GR_MONTHS: GrMonth[] = [
  { month: '3月', amount: 320 },
  { month: '4月', amount: 450 },
  { month: '5月', amount: 280 },
  { month: '6月', amount: 520 },
  { month: '7月', amount: 380 },
  { month: '8月', amount: 610 }
]

function grSprayR(wave: number, i: number): number {
  let base: number = (wave * 2 + i * 37) % 80
  return 0.8 + base / 200
}

function grGlow(wave: number, i: number): number {
  let base: number = (wave + i * 13) % 100
  return 0.4 + Math.abs(Math.sin(base / 15)) * 0.6
}

function grSpin(wave: number, i: number): number {
  return (wave * 3 + i * 45) % 360
}

function grDrip(wave: number, i: number): number {
  let base: number = (wave * 2 + i * 29) % 100
  return base * 0.25
}

function grFloatY(wave: number, i: number): number {
  let base: number = (wave + i * 23) % 100
  return Math.sin(base / 16) * 5
}

function grBlink(wave: number): number {
  let base: number = wave % 100
  return base < 50 ? 1 : 0.4
}

function grFilterSprays(idx: number): GrSpray[] {
  let r: GrSpray[] = []
  let tags: string[] = ['', '速干', '荧光', '高压', '夜光', '金属']
  let tag: string = idx < tags.length ? tags[idx] : ''
  if (idx === 0) {
    r = GR_SPRAYS.slice()
  } else {
    for (let i = 0; i < GR_SPRAYS.length; i++) {
      if (GR_SPRAYS[i].tag === tag) {
        r.push(GR_SPRAYS[i])
      }
    }
  }
  if (r.length === 0) {
    r = GR_SPRAYS.slice()
  }
  return r
}

function grSoldRank(): GrSpray[] {
  let r: GrSpray[] = GR_SPRAYS.slice()
  r.sort((a: GrSpray, b: GrSpray) => b.sold - a.sold)
  return r
}

function grCoverageW(c: number): string {
  let pct: number = Math.min(c / 15 * 100, 100)
  return pct + '%'
}

function grPressureW(p: number): string {
  return (p * 10) + '%'
}

function grInkW(ink: number): string {
  let pct: number = Math.min(ink / 8 * 100, 100)
  return pct + '%'
}

function grStickerCountW(c: number): string {
  let pct: number = Math.min(c / 300 * 100, 100)
  return pct + '%'
}

function grGuardLevelW(l: number): string {
  return (l * 20) + '%'
}

function grMonthH(i: number): number {
  let amounts: number[] = [320, 450, 280, 520, 380, 610]
  let max: number = 610
  return amounts[i] / max * 60
}

function grTopN(i: number): string {
  let labels: string[] = ['No.1', 'No.2', 'No.3', 'No.4', 'No.5', 'No.6', 'No.7', 'No.8', 'No.9', 'No.10']
  return i < labels.length ? labels[i] : ''
}

@Entry
@Component
struct GrApp {
  @State currentTab: number = 0
  @State wave: number = 0
  private timer: number = -1

  aboutToAppear() {
    this.timer = setInterval(() => {
      this.wave = (this.wave + 1) % 10000
    }, 60)
  }

  aboutToDisappear() {
    if (this.timer !== -1) {
      clearInterval(this.timer)
    }
  }

  build() {
    Stack() {
      Column() {
        Row() {
          Text('GRAFFITI')
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor(GR.primary)
          Text(' ZONE')
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor(GR.text)
          Column().layoutWeight(1)
          Text('search')
            .fontSize(12)
            .fontColor(GR.sub)
            .margin({ right: 12 })
          Text('cart')
            .fontSize(12)
            .fontColor(GR.sub)
        }
        .width('100%')
        .height(48)
        .padding({ left: 16, right: 16 })
        .backgroundColor(GR.dark)
        .alignItems(VerticalAlign.Center)
        .border({ width: { bottom: 1 }, color: GR.line })

        Row() {
          Text('search')
            .fontSize(11)
            .fontColor(GR.sub)
            .margin({ right: 8 })
          Text('搜索喷漆 / 马克笔 / 贴纸...')
            .fontSize(12)
            .fontColor(GR.sub)
            .layoutWeight(1)
        }
        .width('92%')
        .height(34)
        .margin({ top: 6, bottom: 6 })
        .padding({ left: 12, right: 12 })
        .backgroundColor(GR.card)
        .borderRadius(17)
        .border({ width: 1, color: GR.line })
        .alignItems(VerticalAlign.Center)

        Column() {
          if (this.currentTab === 0) {
            GrHomeTab()
          } else if (this.currentTab === 1) {
            GrSprayTab()
          } else if (this.currentTab === 2) {
            GrMarkerTab()
          } else if (this.currentTab === 3) {
            GrStickerTab()
          } else if (this.currentTab === 4) {
            GrGuardTab()
          } else {
            GrMineTab()
          }
        }
        .layoutWeight(1)

        Row() {
          ForEach(GR_TABS, (t: GrTab, i: number) => {
            Column() {
              Text(t.icon)
                .fontSize(18)
                .opacity(this.currentTab === i ? 1 : 0.4)
              Text(t.name)
                .fontSize(9)
                .fontColor(this.currentTab === i ? GR.primary : GR.sub)
                .margin({ top: 2 })
            }
            .layoutWeight(1)
            .onClick(() => {
              this.currentTab = i
            })
          }, (t: GrTab) => t.name)
        }
        .width('100%')
        .height(54)
        .backgroundColor(GR.dark)
        .border({ width: { top: 1 }, color: GR.line })
        .alignItems(VerticalAlign.Center)
      }
      .width('100%')
      .height('100%')
      .backgroundColor(GR.bg)
    }
  }
}

@Component
struct GrHomeTab {
  @State wave: number = 0
  @State showEvent: boolean = false
  @State showClass: boolean = false
  @State showLimited: boolean = false
  @State showCommunity: boolean = false
  @State ranked: GrSpray[] = []
  private timer: number = -1

  aboutToAppear() {
    this.ranked = grSoldRank().slice(0, 5)
    this.timer = setInterval(() => {
      this.wave = (this.wave + 1) % 10000
    }, 60)
  }

  aboutToDisappear() {
    if (this.timer !== -1) {
      clearInterval(this.timer)
    }
  }

  @Builder modalOverlay() {
    Column()
      .width('100%')
      .height('100%')
      .backgroundColor('rgba(0,0,0,0.72)')
      .position({ x: 0, y: 0 })
      .zIndex(999)
      .onClick(() => {
        this.showEvent = false
        this.showClass = false
        this.showLimited = false
        this.showCommunity = false
      })
  }

  build() {
    Stack() {
      Scroll() {
        Column() {
          Column() {
            Row() {
              Text('GRAFFITI')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.primary)
              Text(' ZONE')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.text)
              Column().layoutWeight(1)
              Text('HOT')
                .fontSize(9)
                .fontColor(GR.dark)
                .padding({ left: 6, right: 6, top: 2, bottom: 2 })
                .borderRadius(4)
                .backgroundColor(GR.accent)
            }
            .width('100%')

            Row() {
              ForEach(GR_SPRAYS.slice(0, 6), (s: GrSpray, i: number) => {
                Column()
                  .width(28)
                  .height(28)
                  .borderRadius(14)
                  .backgroundColor(s.colorHex)
                  .scale({ x: grSprayR(this.wave, i), y: grSprayR(this.wave, i) })
                  .opacity(grGlow(this.wave, i))
                  .margin({ left: i > 0 ? 6 : 0 })
              }, (s: GrSpray) => s.name)
            }
            .margin({ top: 12 })

            Text('1000+ 款色 | 30+ 品牌 | 全球直邮')
              .fontSize(10)
              .fontColor(GR.sub)
              .margin({ top: 8 })
          }
          .width('92%')
          .padding(16)
          .backgroundColor(GR.card)
          .borderRadius(16)
          .border({ width: 1, color: GR.line })
          .margin({ top: 8 })
          .alignItems(HorizontalAlign.Start)

          Row() {
            Column() {
              Text('30+')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.primary)
              Text('品牌')
                .fontSize(9)
                .fontColor(GR.sub)
                .margin({ top: 2 })
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Center)

            Column() {
              Text('1000+')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.accent)
              Text('款色')
                .fontSize(9)
                .fontColor(GR.sub)
                .margin({ top: 2 })
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Center)

            Column() {
              Text('5')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.primary)
              Text('活动')
                .fontSize(9)
                .fontColor(GR.sub)
                .margin({ top: 2 })
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Center)

            Column() {
              Text('2.8万')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.accent)
              Text('会员')
                .fontSize(9)
                .fontColor(GR.sub)
                .margin({ top: 2 })
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Center)
          }
          .width('92%')
          .padding({ top: 12, bottom: 12 })
          .backgroundColor(GR.card)
          .borderRadius(14)
          .border({ width: 1, color: GR.line })
          .margin({ top: 8 })

          Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
            Column() {
              Text('🎨')
                .fontSize(24)
              Text('涂鸦活动')
                .fontSize(11)
                .fontColor(GR.text)
                .margin({ top: 6 })
              Text('5场进行中')
                .fontSize(9)
                .fontColor(GR.sub)
                .margin({ top: 2 })
            }
            .width('48.5%')
            .padding(14)
            .backgroundColor('rgba(57,255,20,0.08)')
            .borderRadius(14)
            .border({ width: 1, color: GR.line })
            .alignItems(HorizontalAlign.Center)
            .onClick(() => {
              this.showEvent = true
            })

            Column() {
              Text('📚')
                .fontSize(24)
              Text('喷漆课堂')
                .fontSize(11)
                .fontColor(GR.text)
                .margin({ top: 6 })
              Text('8门课程')
                .fontSize(9)
                .fontColor(GR.sub)
                .margin({ top: 2 })
            }
            .width('48.5%')
            .padding(14)
            .backgroundColor('rgba(255,45,149,0.08)')
            .borderRadius(14)
            .border({ width: 1, color: GR.line })
            .alignItems(HorizontalAlign.Center)
            .onClick(() => {
              this.showClass = true
            })

            Column() {
              Text('⭐')
                .fontSize(24)
              Text('联名限定')
                .fontSize(11)
                .fontColor(GR.text)
                .margin({ top: 6 })
              Text('3款预售中')
                .fontSize(9)
                .fontColor(GR.sub)
                .margin({ top: 2 })
            }
            .width('48.5%')
            .padding(14)
            .backgroundColor('rgba(57,255,20,0.08)')
            .borderRadius(14)
            .border({ width: 1, color: GR.line })
            .alignItems(HorizontalAlign.Center)
            .margin({ top: 8 })
            .onClick(() => {
              this.showLimited = true
            })

            Column() {
              Text('💬')
                .fontSize(24)
              Text('创作社区')
                .fontSize(11)
                .fontColor(GR.text)
                .margin({ top: 6 })
              Text('1200+ 作品')
                .fontSize(9)
                .fontColor(GR.sub)
                .margin({ top: 2 })
            }
            .width('48.5%')
            .padding(14)
            .backgroundColor('rgba(255,45,149,0.08)')
            .borderRadius(14)
            .border({ width: 1, color: GR.line })
            .alignItems(HorizontalAlign.Center)
            .margin({ top: 8 })
            .onClick(() => {
              this.showCommunity = true
            })
          }
          .width('92%')
          .margin({ top: 10 })

          Row() {
            Text('热销榜单')
              .fontSize(14)
              .fontWeight(FontWeight.Bold)
              .fontColor(GR.text)
            Column().layoutWeight(1)
            Text('查看全部')
              .fontSize(11)
              .fontColor(GR.sub)
          }
          .width('92%')
          .margin({ top: 14 })

          Column() {
            ForEach(this.ranked, (s: GrSpray, i: number) => {
              Row() {
                Text(grTopN(i))
                  .fontSize(12)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(i < 3 ? GR.primary : GR.sub)
                  .width(36)

                Column()
                  .width(24)
                  .height(24)
                  .borderRadius(12)
                  .backgroundColor(s.colorHex)
                  .margin({ left: 4, right: 8 })

                Column() {
                  Text(s.name)
                    .fontSize(12)
                    .fontColor(GR.text)
                  Text(s.brand + ' · ' + s.color)
                    .fontSize(9)
                    .fontColor(GR.sub)
                    .margin({ top: 2 })
                }
                .alignItems(HorizontalAlign.Start)
                .layoutWeight(1)

                Text(s.sold + '件')
                  .fontSize(11)
                  .fontColor(GR.primary)
                  .fontWeight(FontWeight.Bold)
              }
              .width('100%')
              .padding({ top: 10, bottom: 10 })
              .border({ width: { bottom: 1 }, color: GR.line })
            }, (s: GrSpray) => s.name)
          }
          .width('92%')
          .padding({ left: 14, right: 14 })
          .backgroundColor(GR.card)
          .borderRadius(14)
          .border({ width: 1, color: GR.line })
          .margin({ top: 6 })

          Row() {
            Text('联名限定')
              .fontSize(14)
              .fontWeight(FontWeight.Bold)
              .fontColor(GR.text)
            Column().layoutWeight(1)
            Text('更多 >')
              .fontSize(11)
              .fontColor(GR.sub)
          }
          .width('92%')
          .margin({ top: 14 })

          Scroll() {
            Row({ space: 10 }) {
              ForEach(GR_COLLECTS, (c: GrCollect, i: number) => {
                Column() {
                  Column()
                    .width('100%')
                    .height(70)
                    .backgroundColor(i % 2 === 0 ? GR.dark : GR.line)
                    .borderRadius(10)
                    .justifyContent(FlexAlign.Center)

                  Text(c.name)
                    .fontSize(11)
                    .fontColor(GR.text)
                    .margin({ top: 6 })

                  Text(c.brand)
                    .fontSize(9)
                    .fontColor(GR.sub)
                    .margin({ top: 2 })

                  Text('¥' + c.price)
                    .fontSize(12)
                    .fontWeight(FontWeight.Bold)
                    .fontColor(GR.primary)
                    .margin({ top: 4 })
                }
                .width(120)
                .padding(10)
                .backgroundColor(GR.card)
                .borderRadius(12)
                .border({ width: 1, color: GR.line })
                .onClick(() => {
                  this.showLimited = true
                })
              }, (c: GrCollect) => c.name)
            }
            .padding({ left: 16, right: 16 })
          }
          .scrollable(ScrollDirection.Horizontal)
          .width('100%')
          .margin({ top: 6, bottom: 12 })
        }
      }
      .width('100%')
      .height('100%')

      if (this.showEvent) {
        Stack() {
          this.modalOverlay()
          Column() {
            Row() {
              Text('涂鸦活动报名')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.text)
                .layoutWeight(1)
              Text('×')
                .fontSize(18)
                .fontColor(GR.sub)
                .onClick(() => {
                  this.showEvent = false
                })
            }
            .width('100%')
            .padding({ bottom: 12 })
            .border({ width: { bottom: 1 }, color: GR.line })

            Text('街头涂鸦大赛 2025')
              .fontSize(14)
              .fontColor(GR.primary)
              .margin({ top: 12 })
            Text('时间:08月20日 14:00-17:00')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 6 })
            Text('地点:798艺术区 B3 厂房')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 4 })
            Text('报名费:¥50 / 限50人')
              .fontSize(11)
              .fontColor(GR.accent)
              .margin({ top: 4 })

            Column() {
              Text('参赛须知')
                .fontSize(12)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.text)
              Text('· 自备喷漆与防护装备')
                .fontSize(10)
                .fontColor(GR.sub)
                .margin({ top: 6 })
              Text('· 创作时间 3 小时')
                .fontSize(10)
                .fontColor(GR.sub)
                .margin({ top: 3 })
              Text('· 评委打分 + 现场投票')
                .fontSize(10)
                .fontColor(GR.sub)
                .margin({ top: 3 })
              Text('· 一等奖 ¥3000 喷漆礼包')
                .fontSize(10)
                .fontColor(GR.accent)
                .margin({ top: 3 })
            }
            .width('100%')
            .alignItems(HorizontalAlign.Start)
            .margin({ top: 12 })

            Row({ space: 10 }) {
              Text('取消')
                .fontSize(13)
                .fontColor(GR.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.dark)
                .onClick(() => {
                  this.showEvent = false
                })
              Text('立即报名')
                .fontSize(13)
                .fontColor(Color.Black)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.primary)
                .onClick(() => {
                  this.showEvent = false
                })
            }
            .width('100%')
            .margin({ top: 16 })
          }
          .width('86%')
          .constraintSize({ maxHeight: '80%' })
          .backgroundColor(GR.card)
          .borderRadius(16)
          .border({ width: 1, color: GR.line })
          .padding(16)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showClass) {
        Stack() {
          this.modalOverlay()
          Column() {
            Row() {
              Column()
                .width(4)
                .height(16)
                .borderRadius(2)
                .backgroundColor(GR.primary)
              Text('喷漆课堂')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.text)
                .margin({ left: 8 })
              Column().layoutWeight(1)
              Text('×')
                .fontSize(18)
                .fontColor(GR.sub)
                .onClick(() => {
                  this.showClass = false
                })
            }
            .width('100%')
            .padding({ bottom: 12 })
            .border({ width: { bottom: 1 }, color: GR.line })

            Column() {
              Row() {
                Text('基础入门')
                  .fontSize(12)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(GR.primary)
                Column().layoutWeight(1)
                Text('¥99')
                  .fontSize(12)
                  .fontColor(GR.accent)
              }
              .width('100%')
              Text('2课时 · 喷漆控制与线条')
                .fontSize(10)
                .fontColor(GR.sub)
                .margin({ top: 4 })
            }
            .width('100%')
            .padding({ top: 10, bottom: 10 })
            .border({ width: { bottom: 1 }, color: GR.line })
            .margin({ top: 8 })

            Column() {
              Row() {
                Text('进阶叠色')
                  .fontSize(12)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(GR.accent)
                Column().layoutWeight(1)
                Text('¥199')
                  .fontSize(12)
                  .fontColor(GR.accent)
              }
              .width('100%')
              Text('3课时 · 渐变与叠色技法')
                .fontSize(10)
                .fontColor(GR.sub)
                .margin({ top: 4 })
            }
            .width('100%')
            .padding({ top: 10, bottom: 10 })
            .border({ width: { bottom: 1 }, color: GR.line })

            Column() {
              Row() {
                Text('大师创作')
                  .fontSize(12)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(GR.primary)
                Column().layoutWeight(1)
                Text('¥399')
                  .fontSize(12)
                  .fontColor(GR.accent)
              }
              .width('100%')
              Text('5课时 · 完整作品从草图到上墙')
                .fontSize(10)
                .fontColor(GR.sub)
                .margin({ top: 4 })
            }
            .width('100%')
            .padding({ top: 10, bottom: 10 })

            Row({ space: 10 }) {
              Text('以后再说')
                .fontSize(13)
                .fontColor(GR.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.dark)
                .onClick(() => {
                  this.showClass = false
                })
              Text('预约课程')
                .fontSize(13)
                .fontColor(GR.dark)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.primary)
                .onClick(() => {
                  this.showClass = false
                })
            }
            .width('100%')
            .margin({ top: 14 })
          }
          .width('86%')
          .constraintSize({ maxHeight: '80%' })
          .backgroundColor(GR.card)
          .borderRadius(16)
          .border({ width: 1, color: GR.line })
          .padding(16)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showLimited) {
        Stack() {
          this.modalOverlay()
          Column() {
            Row() {
              Text('联名限定预购')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.accent)
                .layoutWeight(1)
              Text('×')
                .fontSize(18)
                .fontColor(GR.sub)
                .onClick(() => {
                  this.showLimited = false
                })
            }
            .width('100%')
            .padding({ bottom: 12 })
            .border({ width: { bottom: 1 }, color: GR.line })

            Row() {
              Column()
                .width(60)
                .height(60)
                .borderRadius(12)
                .backgroundColor(GR.dark)
                .border({ width: 1, color: GR.line })

              Column() {
                Text('MTN × GRAFFITI')
                  .fontSize(13)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(GR.text)
                Text('联名限定套装')
                  .fontSize(11)
                  .fontColor(GR.sub)
                  .margin({ top: 4 })
                Text('限量 500 套')
                  .fontSize(10)
                  .fontColor(GR.accent)
                  .margin({ top: 4 })
              }
              .alignItems(HorizontalAlign.Start)
              .margin({ left: 12 })
              .layoutWeight(1)
            }
            .width('100%')
            .margin({ top: 12 })

            Text('配色选择')
              .fontSize(12)
              .fontColor(GR.text)
              .margin({ top: 14 })
            Row({ space: 10 }) {
              Column()
                .width(28)
                .height(28)
                .borderRadius(14)
                .backgroundColor('#39FF14')
                .border({ width: 2, color: GR.primary })
              Column()
                .width(28)
                .height(28)
                .borderRadius(14)
                .backgroundColor('#FF2D95')
                .border({ width: 1, color: GR.line })
              Column()
                .width(28)
                .height(28)
                .borderRadius(14)
                .backgroundColor('#00F5D4')
                .border({ width: 1, color: GR.line })
              Column()
                .width(28)
                .height(28)
                .borderRadius(14)
                .backgroundColor('#FFD700')
                .border({ width: 1, color: GR.line })
            }
            .margin({ top: 8 })

            Row() {
              Text('预购价')
                .fontSize(12)
                .fontColor(GR.sub)
              Column().layoutWeight(1)
              Text('¥468')
                .fontSize(20)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.accent)
            }
            .width('100%')
            .margin({ top: 14 })

            Row({ space: 10 }) {
              Text('取消')
                .fontSize(13)
                .fontColor(GR.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.dark)
                .onClick(() => {
                  this.showLimited = false
                })
              Text('预购')
                .fontSize(13)
                .fontColor(Color.Black)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.primary)
                .onClick(() => {
                  this.showLimited = false
                })
            }
            .width('100%')
            .margin({ top: 16 })
          }
          .width('86%')
          .constraintSize({ maxHeight: '80%' })
          .backgroundColor(GR.card)
          .borderRadius(16)
          .border({ width: 1, color: GR.line })
          .padding(16)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showCommunity) {
        Stack() {
          this.modalOverlay()
          Column() {
            Row() {
              Text('💬 创作社区')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.text)
                .layoutWeight(1)
              Text('×')
                .fontSize(18)
                .fontColor(GR.sub)
                .onClick(() => {
                  this.showCommunity = false
                })
            }
            .width('100%')
            .padding({ bottom: 12 })
            .border({ width: { bottom: 1 }, color: GR.line })

            Row() {
              Column() {
                Text('2.8万')
                  .fontSize(16)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(GR.primary)
                Text('成员')
                  .fontSize(9)
                  .fontColor(GR.sub)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Center)

              Column() {
                Text('1200+')
                  .fontSize(16)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(GR.accent)
                Text('作品')
                  .fontSize(9)
                  .fontColor(GR.sub)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Center)

              Column() {
                Text('86')
                  .fontSize(16)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(GR.primary)
                Text('本周新作')
                  .fontSize(9)
                  .fontColor(GR.sub)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Center)
            }
            .width('100%')
            .padding({ top: 10, bottom: 10 })
            .backgroundColor(GR.dark)
            .borderRadius(12)
            .margin({ top: 10 })

            Text('自我介绍')
              .fontSize(12)
              .fontColor(GR.text)
              .margin({ top: 12 })
            TextInput({ placeholder: '一句话介绍你的创作风格...' })
              .height(36)
              .fontSize(11)
              .fontColor(GR.text)
              .backgroundColor(GR.dark)
              .borderRadius(8)
              .border({ width: 1, color: GR.line })
              .margin({ top: 6 })

            Text('创作类型')
              .fontSize(12)
              .fontColor(GR.text)
              .margin({ top: 12 })
            Row({ space: 8 }) {
              Text('墙绘')
                .fontSize(11)
                .fontColor(GR.dark)
                .padding({ left: 12, right: 12, top: 6, bottom: 6 })
                .borderRadius(14)
                .backgroundColor(GR.primary)
              Text('布面')
                .fontSize(11)
                .fontColor(GR.sub)
                .padding({ left: 12, right: 12, top: 6, bottom: 6 })
                .borderRadius(14)
                .backgroundColor(GR.dark)
              Text('涂鸦')
                .fontSize(11)
                .fontColor(GR.sub)
                .padding({ left: 12, right: 12, top: 6, bottom: 6 })
                .borderRadius(14)
                .backgroundColor(GR.dark)
            }
            .margin({ top: 6 })

            Row({ space: 10 }) {
              Text('取消')
                .fontSize(13)
                .fontColor(GR.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.dark)
                .onClick(() => {
                  this.showCommunity = false
                })
              Text('加入社区')
                .fontSize(13)
                .fontColor(GR.dark)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.primary)
                .onClick(() => {
                  this.showCommunity = false
                })
            }
            .width('100%')
            .margin({ top: 16 })
          }
          .width('86%')
          .constraintSize({ maxHeight: '80%' })
          .backgroundColor(GR.card)
          .borderRadius(16)
          .border({ width: 1, color: GR.line })
          .padding(16)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }
    }
    .width('100%')
    .height('100%')
  }
}

@Component
struct GrSprayTab {
  @State wave: number = 0
  @State chip: number = 0
  @State sprays: GrSpray[] = []
  @State ranked: GrSpray[] = []
  @State showDetail: boolean = false
  @State showAdd: boolean = false
  @State showEdit: boolean = false
  @State showDel: boolean = false
  @State selIdx: number = 0
  private timer: number = -1

  aboutToAppear() {
    this.sprays = GR_SPRAYS.slice()
    this.ranked = grSoldRank().slice(0, 6)
    this.timer = setInterval(() => {
      this.wave = (this.wave + 1) % 10000
    }, 60)
  }

  aboutToDisappear() {
    if (this.timer !== -1) {
      clearInterval(this.timer)
    }
  }

  @Builder modalOverlay() {
    Column()
      .width('100%')
      .height('100%')
      .backgroundColor('rgba(0,0,0,0.72)')
      .position({ x: 0, y: 0 })
      .zIndex(999)
      .onClick(() => {
        this.showDetail = false
        this.showAdd = false
        this.showEdit = false
        this.showDel = false
      })
  }

  build() {
    Stack() {
      Scroll() {
        Column() {
          Row() {
            Text('喷漆色卡')
              .fontSize(14)
              .fontWeight(FontWeight.Bold)
              .fontColor(GR.text)
            Column().layoutWeight(1)
            Text('+' + '新增')
              .fontSize(11)
              .fontColor(GR.primary)
              .onClick(() => {
                this.showAdd = true
              })
          }
          .width('92%')
          .margin({ top: 8 })

          Scroll() {
            Row({ space: 8 }) {
              ForEach(['全部', '速干', '荧光', '高压', '夜光', '金属'], (tag: string, i: number) => {
                Text(tag)
                  .fontSize(11)
                  .fontColor(this.chip === i ? GR.dark : GR.sub)
                  .padding({ left: 14, right: 14, top: 6, bottom: 6 })
                  .borderRadius(14)
                  .backgroundColor(this.chip === i ? GR.primary : GR.dark)
                  .border({ width: 1, color: GR.line })
                  .onClick(() => {
                    this.chip = i
                    this.sprays = grFilterSprays(i)
                  })
              }, (tag: string) => tag)
            }
            .padding({ left: 16, right: 16 })
          }
          .scrollable(ScrollDirection.Horizontal)
          .width('100%')
          .margin({ top: 6 })

          Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
            ForEach(this.sprays, (s: GrSpray, i: number) => {
              Column() {
                Column()
                  .width(48)
                  .height(48)
                  .borderRadius(24)
                  .backgroundColor(s.colorHex)
                  .scale({ x: grSprayR(this.wave, i), y: grSprayR(this.wave, i) })
                  .opacity(grGlow(this.wave, i))
                  .border({ width: 2, color: GR.dark })

                Text(s.name)
                  .fontSize(11)
                  .fontColor(GR.text)
                  .margin({ top: 8 })
                Text(s.brand + ' · ' + s.color)
                  .fontSize(9)
                  .fontColor(GR.sub)
                  .margin({ top: 2 })

                Row() {
                  Text('覆盖')
                    .fontSize(8)
                    .fontColor(GR.sub)
                    .width(24)
                  Stack({ alignContent: Alignment.Start }) {
                    Row()
                      .width('100%')
                      .height(3)
                      .borderRadius(2)
                      .backgroundColor(GR.dark)
                    Row()
                      .width(grCoverageW(s.coverage))
                      .height(3)
                      .borderRadius(2)
                      .backgroundColor(GR.primary)
                  }
                  .layoutWeight(1)
                  .height(3)
                }
                .width('100%')
                .margin({ top: 6 })

                Row() {
                  Text('压力')
                    .fontSize(8)
                    .fontColor(GR.sub)
                    .width(24)
                  Stack({ alignContent: Alignment.Start }) {
                    Row()
                      .width('100%')
                      .height(3)
                      .borderRadius(2)
                      .backgroundColor(GR.dark)
                    Row()
                      .width(grPressureW(s.pressure))
                      .height(3)
                      .borderRadius(2)
                      .backgroundColor(GR.accent)
                  }
                  .layoutWeight(1)
                  .height(3)
                }
                .width('100%')
                .margin({ top: 4 })

                Row() {
                  Text('¥' + (s.volume / 10 + 29))
                    .fontSize(13)
                    .fontWeight(FontWeight.Bold)
                    .fontColor(GR.primary)
                  Column().layoutWeight(1)
                  Text('销' + s.sold)
                    .fontSize(9)
                    .fontColor(GR.sub)
                }
                .width('100%')
                .margin({ top: 6 })

                Row() {
                  Text('详情')
                    .fontSize(9)
                    .fontColor(GR.sub)
                    .layoutWeight(1)
                    .textAlign(TextAlign.Center)
                    .padding({ top: 4, bottom: 4 })
                    .borderRadius(6)
                    .backgroundColor(GR.dark)
                    .onClick(() => {
                      this.selIdx = i
                      this.showDetail = true
                    })
                  Text('编辑')
                    .fontSize(9)
                    .fontColor(GR.sub)
                    .layoutWeight(1)
                    .textAlign(TextAlign.Center)
                    .padding({ top: 4, bottom: 4 })
                    .borderRadius(6)
                    .backgroundColor(GR.dark)
                    .margin({ left: 4 })
                    .onClick(() => {
                      this.selIdx = i
                      this.showEdit = true
                    })
                  Text('删除')
                    .fontSize(9)
                    .fontColor(GR.accent)
                    .layoutWeight(1)
                    .textAlign(TextAlign.Center)
                    .padding({ top: 4, bottom: 4 })
                    .borderRadius(6)
                    .backgroundColor(GR.dark)
                    .margin({ left: 4 })
                    .onClick(() => {
                      this.selIdx = i
                      this.showDel = true
                    })
                }
                .width('100%')
                .margin({ top: 6 })
              }
              .width('48.5%')
              .padding(12)
              .backgroundColor(GR.card)
              .borderRadius(14)
              .border({ width: 1, color: GR.line })
              .margin({ top: 8 })
            }, (s: GrSpray) => s.name)
          }
          .width('92%')
          .margin({ top: 6 })

          Text('销量排行')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor(GR.text)
            .margin({ top: 14 })

          Row() {
            ForEach(this.ranked, (s: GrSpray, i: number) => {
              Column() {
                Column()
                  .width(18)
                  .height(Math.min(s.sold / 410 * 50, 50))
                  .backgroundColor(i < 3 ? GR.primary : GR.accent)
                  .borderRadius(4)
                Text(s.sold + '')
                  .fontSize(8)
                  .fontColor(GR.sub)
                  .margin({ top: 4 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Center)
            }, (s: GrSpray) => s.name)
          }
          .width('92%')
          .height(70)
          .alignItems(VerticalAlign.Bottom)
          .padding({ top: 10, bottom: 4 })
          .backgroundColor(GR.card)
          .borderRadius(14)
          .border({ width: 1, color: GR.line })
          .margin({ top: 6, bottom: 12 })
        }
      }
      .width('100%')
      .height('100%')

      if (this.showDetail) {
        Stack() {
          this.modalOverlay()
          Column() {
            Row() {
              Text('喷漆详情')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.text)
                .layoutWeight(1)
              Text('×')
                .fontSize(18)
                .fontColor(GR.sub)
                .onClick(() => {
                  this.showDetail = false
                })
            }
            .width('100%')
            .padding({ bottom: 12 })
            .border({ width: { bottom: 1 }, color: GR.line })

            Row() {
              Column()
                .width(56)
                .height(56)
                .borderRadius(28)
                .backgroundColor(this.sprays[this.selIdx].colorHex)
                .border({ width: 2, color: GR.dark })
                .scale({ x: grSprayR(this.wave, 0), y: grSprayR(this.wave, 0) })

              Column() {
                Text(this.sprays[this.selIdx].name)
                  .fontSize(14)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(GR.text)
                Text(this.sprays[this.selIdx].brand + ' · ' + this.sprays[this.selIdx].color)
                  .fontSize(11)
                  .fontColor(GR.sub)
                  .margin({ top: 4 })
                Text('¥' + (this.sprays[this.selIdx].volume / 10 + 29))
                  .fontSize(18)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(GR.primary)
                  .margin({ top: 4 })
              }
              .alignItems(HorizontalAlign.Start)
              .margin({ left: 14 })
              .layoutWeight(1)
            }
            .width('100%')
            .margin({ top: 12 })

            Column() {
              Text('覆盖力')
                .fontSize(11)
                .fontColor(GR.sub)
              Stack({ alignContent: Alignment.Start }) {
                Row()
                  .width('100%')
                  .height(6)
                  .borderRadius(3)
                  .backgroundColor(GR.dark)
                Row()
                  .width(grCoverageW(this.sprays[this.selIdx].coverage))
                  .height(6)
                  .borderRadius(3)
                  .backgroundColor(GR.primary)
              }
              .width('100%')
              .height(6)
              .margin({ top: 4 })
            }
            .width('100%')
            .margin({ top: 14 })

            Column() {
              Text('喷射压力')
                .fontSize(11)
                .fontColor(GR.sub)
              Stack({ alignContent: Alignment.Start }) {
                Row()
                  .width('100%')
                  .height(6)
                  .borderRadius(3)
                  .backgroundColor(GR.dark)
                Row()
                  .width(grPressureW(this.sprays[this.selIdx].pressure))
                  .height(6)
                  .borderRadius(3)
                  .backgroundColor(GR.accent)
              }
              .width('100%')
              .height(6)
              .margin({ top: 4 })
            }
            .width('100%')
            .margin({ top: 10 })

            Row() {
              Column() {
                Text('容量')
                  .fontSize(9)
                  .fontColor(GR.sub)
                Text(this.sprays[this.selIdx].volume + 'ml')
                  .fontSize(13)
                  .fontColor(GR.text)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Center)

              Column() {
                Text('库存')
                  .fontSize(9)
                  .fontColor(GR.sub)
                Text(this.sprays[this.selIdx].stock + '瓶')
                  .fontSize(13)
                  .fontColor(GR.text)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Center)

              Column() {
                Text('已售')
                  .fontSize(9)
                  .fontColor(GR.sub)
                Text(this.sprays[this.selIdx].sold + '件')
                  .fontSize(13)
                  .fontColor(GR.primary)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Center)
            }
            .width('100%')
            .padding({ top: 10, bottom: 10 })
            .backgroundColor(GR.dark)
            .borderRadius(10)
            .margin({ top: 12 })

            Text('标签:' + this.sprays[this.selIdx].tag)
              .fontSize(10)
              .fontColor(GR.accent)
              .margin({ top: 10 })

            Row({ space: 10 }) {
              Text('关闭')
                .fontSize(13)
                .fontColor(GR.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.dark)
                .onClick(() => {
                  this.showDetail = false
                })
              Text('加入收藏')
                .fontSize(13)
                .fontColor(Color.Black)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.primary)
                .onClick(() => {
                  this.showDetail = false
                })
            }
            .width('100%')
            .margin({ top: 16 })
          }
          .width('86%')
          .constraintSize({ maxHeight: '80%' })
          .backgroundColor(GR.card)
          .borderRadius(16)
          .border({ width: 1, color: GR.line })
          .padding(16)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showAdd) {
        Stack() {
          this.modalOverlay()
          Column() {
            Row() {
              Text('新增喷漆')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.primary)
                .layoutWeight(1)
              Text('×')
                .fontSize(18)
                .fontColor(GR.sub)
                .onClick(() => {
                  this.showAdd = false
                })
            }
            .width('100%')
            .padding({ bottom: 12 })
            .border({ width: { bottom: 1 }, color: GR.line })

            Text('喷漆名称')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            TextInput({ placeholder: '如:Hardcore 600ml' })
              .height(36)
              .fontSize(11)
              .fontColor(GR.text)
              .backgroundColor(GR.dark)
              .borderRadius(8)
              .border({ width: 1, color: GR.line })
              .margin({ top: 4 })

            Text('品牌')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            TextInput({ placeholder: '如:MONTANA' })
              .height(36)
              .fontSize(11)
              .fontColor(GR.text)
              .backgroundColor(GR.dark)
              .borderRadius(8)
              .border({ width: 1, color: GR.line })
              .margin({ top: 4 })

            Text('选择颜色')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            Row({ space: 10 }) {
              Column()
                .width(28)
                .height(28)
                .borderRadius(14)
                .backgroundColor('#39FF14')
                .border({ width: 2, color: GR.primary })
              Column()
                .width(28)
                .height(28)
                .borderRadius(14)
                .backgroundColor('#FF2D95')
                .border({ width: 1, color: GR.line })
              Column()
                .width(28)
                .height(28)
                .borderRadius(14)
                .backgroundColor('#00F5D4')
                .border({ width: 1, color: GR.line })
              Column()
                .width(28)
                .height(28)
                .borderRadius(14)
                .backgroundColor('#FFD700')
                .border({ width: 1, color: GR.line })
              Column()
                .width(28)
                .height(28)
                .borderRadius(14)
                .backgroundColor('#8B5CF6')
                .border({ width: 1, color: GR.line })
              Column()
                .width(28)
                .height(28)
                .borderRadius(14)
                .backgroundColor('#F0F0F0')
                .border({ width: 1, color: GR.line })
            }
            .margin({ top: 6 })

            Text('容量(ml)')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            TextInput({ placeholder: '如:400' })
              .height(36)
              .fontSize(11)
              .fontColor(GR.text)
              .backgroundColor(GR.dark)
              .borderRadius(8)
              .border({ width: 1, color: GR.line })
              .margin({ top: 4 })

            Row({ space: 10 }) {
              Text('取消')
                .fontSize(13)
                .fontColor(GR.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.dark)
                .onClick(() => {
                  this.showAdd = false
                })
              Text('确认新增')
                .fontSize(13)
                .fontColor(Color.Black)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.primary)
                .onClick(() => {
                  this.showAdd = false
                })
            }
            .width('100%')
            .margin({ top: 16 })
          }
          .width('86%')
          .constraintSize({ maxHeight: '80%' })
          .backgroundColor(GR.card)
          .borderRadius(16)
          .border({ width: 1, color: GR.line })
          .padding(16)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showEdit) {
        Stack() {
          this.modalOverlay()
          Column() {
            Row() {
              Text('编辑喷漆')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.accent)
                .layoutWeight(1)
              Text('×')
                .fontSize(18)
                .fontColor(GR.sub)
                .onClick(() => {
                  this.showEdit = false
                })
            }
            .width('100%')
            .padding({ bottom: 12 })
            .border({ width: { bottom: 1 }, color: GR.line })

            Text('当前:' + this.sprays[this.selIdx].name)
              .fontSize(12)
              .fontColor(GR.sub)
              .margin({ top: 10 })

            Text('修改名称')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            TextInput({ placeholder: this.sprays[this.selIdx].name })
              .height(36)
              .fontSize(11)
              .fontColor(GR.text)
              .backgroundColor(GR.dark)
              .borderRadius(8)
              .border({ width: 1, color: GR.line })
              .margin({ top: 4 })

            Text('修改品牌')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            TextInput({ placeholder: this.sprays[this.selIdx].brand })
              .height(36)
              .fontSize(11)
              .fontColor(GR.text)
              .backgroundColor(GR.dark)
              .borderRadius(8)
              .border({ width: 1, color: GR.line })
              .margin({ top: 4 })

            Text('当前色值')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            Row() {
              Column()
                .width(28)
                .height(28)
                .borderRadius(14)
                .backgroundColor(this.sprays[this.selIdx].colorHex)
                .border({ width: 2, color: GR.primary })
              Text(this.sprays[this.selIdx].colorHex)
                .fontSize(11)
                .fontColor(GR.sub)
                .margin({ left: 10 })
            }
            .margin({ top: 6 })

            Text('修改标签')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            TextInput({ placeholder: this.sprays[this.selIdx].tag })
              .height(36)
              .fontSize(11)
              .fontColor(GR.text)
              .backgroundColor(GR.dark)
              .borderRadius(8)
              .border({ width: 1, color: GR.line })
              .margin({ top: 4 })

            Row({ space: 10 }) {
              Text('取消')
                .fontSize(13)
                .fontColor(GR.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.dark)
                .onClick(() => {
                  this.showEdit = false
                })
              Text('保存修改')
                .fontSize(13)
                .fontColor(GR.dark)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.accent)
                .onClick(() => {
                  this.showEdit = false
                })
            }
            .width('100%')
            .margin({ top: 16 })
          }
          .width('86%')
          .constraintSize({ maxHeight: '80%' })
          .backgroundColor(GR.card)
          .borderRadius(16)
          .border({ width: 1, color: GR.line })
          .padding(16)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showDel) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('确认删除')
              .fontSize(16)
              .fontWeight(FontWeight.Bold)
              .fontColor(GR.accent)
              .textAlign(TextAlign.Center)
              .width('100%')

            Column()
              .width(48)
              .height(48)
              .borderRadius(24)
              .backgroundColor(this.sprays[this.selIdx].colorHex)
              .border({ width: 2, color: GR.dark })
              .margin({ top: 16 })

            Text(this.sprays[this.selIdx].name)
              .fontSize(13)
              .fontColor(GR.text)
              .margin({ top: 10 })

            Text('删除后不可恢复,确定要删除这款喷漆吗?')
              .fontSize(11)
              .fontColor(GR.sub)
              .textAlign(TextAlign.Center)
              .width('100%')
              .margin({ top: 8 })

            Row({ space: 10 }) {
              Text('取消')
                .fontSize(13)
                .fontColor(GR.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.dark)
                .onClick(() => {
                  this.showDel = false
                })
              Text('确认删除')
                .fontSize(13)
                .fontColor(Color.White)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.accent)
                .onClick(() => {
                  this.showDel = false
                })
            }
            .width('100%')
            .margin({ top: 20 })
          }
          .width('80%')
          .padding(20)
          .backgroundColor(GR.card)
          .borderRadius(16)
          .border({ width: 1, color: GR.accent })
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }
    }
    .width('100%')
    .height('100%')
  }
}

@Component
struct GrMarkerTab {
  @State wave: number = 0
  @State showDetail: boolean = false
  @State showAdd: boolean = false
  @State showEdit: boolean = false
  @State selIdx: number = 0
  @State tipChip: number = 0
  private timer: number = -1

  aboutToAppear() {
    this.timer = setInterval(() => {
      this.wave = (this.wave + 1) % 10000
    }, 60)
  }

  aboutToDisappear() {
    if (this.timer !== -1) {
      clearInterval(this.timer)
    }
  }

  @Builder modalOverlay() {
    Column()
      .width('100%')
      .height('100%')
      .backgroundColor('rgba(0,0,0,0.72)')
      .position({ x: 0, y: 0 })
      .zIndex(999)
      .onClick(() => {
        this.showDetail = false
        this.showAdd = false
        this.showEdit = false
      })
  }

  build() {
    Stack() {
      Scroll() {
        Column() {
          Row() {
            Text('马克笔')
              .fontSize(14)
              .fontWeight(FontWeight.Bold)
              .fontColor(GR.text)
            Column().layoutWeight(1)
            Text('+ 新增')
              .fontSize(11)
              .fontColor(GR.primary)
              .onClick(() => {
                this.showAdd = true
              })
          }
          .width('92%')
          .margin({ top: 8 })

          ForEach(GR_MARKERS, (m: GrMarker, i: number) => {
            Column() {
              Row() {
                Text(m.brand)
                  .fontSize(11)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(GR.primary)
                Column().layoutWeight(1)
                Text(m.tip)
                  .fontSize(9)
                  .fontColor(GR.dark)
                  .padding({ left: 8, right: 8, top: 3, bottom: 3 })
                  .borderRadius(10)
                  .backgroundColor(GR.primary)
              }
              .width('100%')

              Text(m.name)
                .fontSize(13)
                .fontColor(GR.text)
                .margin({ top: 4 })

              Row() {
                Text('墨水')
                  .fontSize(8)
                  .fontColor(GR.sub)
                  .width(24)
                Stack({ alignContent: Alignment.Start }) {
                  Row()
                    .width('100%')
                    .height(4)
                    .borderRadius(2)
                    .backgroundColor(GR.dark)
                  Row()
                    .width(grInkW(m.ink))
                    .height(4)
                    .borderRadius(2)
                    .backgroundColor(GR.accent)
                }
                .layoutWeight(1)
                .height(4)
              }
              .width('100%')
              .margin({ top: 8 })

              Row() {
                Text(m.waterproof === 1 ? '防水 ✓' : '非防水')
                  .fontSize(10)
                  .fontColor(m.waterproof === 1 ? GR.primary : GR.sub)
                Column().layoutWeight(1)
                Text(m.size + 'mm')
                  .fontSize(10)
                  .fontColor(GR.sub)
                Text(' ¥' + (m.ink * 15 + 15))
                  .fontSize(13)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(GR.primary)
                  .margin({ left: 8 })
              }
              .width('100%')
              .margin({ top: 6 })

              Row() {
                Text('详情')
                  .fontSize(9)
                  .fontColor(GR.sub)
                  .layoutWeight(1)
                  .textAlign(TextAlign.Center)
                  .padding({ top: 4, bottom: 4 })
                  .borderRadius(6)
                  .backgroundColor(GR.dark)
                  .onClick(() => {
                    this.selIdx = i
                    this.showDetail = true
                  })
                Text('编辑')
                  .fontSize(9)
                  .fontColor(GR.sub)
                  .layoutWeight(1)
                  .textAlign(TextAlign.Center)
                  .padding({ top: 4, bottom: 4 })
                  .borderRadius(6)
                  .backgroundColor(GR.dark)
                  .margin({ left: 4 })
                  .onClick(() => {
                    this.selIdx = i
                    this.showEdit = true
                  })
              }
              .width('100%')
              .margin({ top: 8 })
            }
            .width('92%')
            .padding(14)
            .backgroundColor(GR.card)
            .borderRadius(14)
            .border({ width: 1, color: GR.line })
            .margin({ top: 8 })
            .alignItems(HorizontalAlign.Start)
          }, (m: GrMarker) => m.name)

          Text('笔尖尺寸对比')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor(GR.text)
            .margin({ top: 14 })

          Column() {
            ForEach(GR_MARKERS, (m: GrMarker, i: number) => {
              Row() {
                Text(m.size + 'mm')
                  .fontSize(9)
                  .fontColor(GR.sub)
                  .width(36)
                Stack({ alignContent: Alignment.Start }) {
                  Row()
                    .width('100%')
                    .height(6)
                    .borderRadius(3)
                    .backgroundColor(GR.dark)
                  Row()
                    .width(m.size * 18 + '%')
                    .height(6)
                    .borderRadius(3)
                    .backgroundColor(i % 2 === 0 ? GR.primary : GR.accent)
                }
                .layoutWeight(1)
                .height(6)
                Text(m.brand)
                  .fontSize(8)
                  .fontColor(GR.sub)
                  .width(60)
                  .margin({ left: 8 })
              }
              .width('100%')
              .margin({ top: 5 })
            }, (m: GrMarker) => m.name)
          }
          .width('92%')
          .padding(14)
          .backgroundColor(GR.card)
          .borderRadius(14)
          .border({ width: 1, color: GR.line })
          .margin({ top: 6, bottom: 12 })
        }
      }
      .width('100%')
      .height('100%')

      if (this.showDetail) {
        Stack() {
          this.modalOverlay()
          Column() {
            Row() {
              Text('马克笔详情')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.text)
                .layoutWeight(1)
              Text('×')
                .fontSize(18)
                .fontColor(GR.sub)
                .onClick(() => {
                  this.showDetail = false
                })
            }
            .width('100%')
            .padding({ bottom: 12 })
            .border({ width: { bottom: 1 }, color: GR.line })

            Row() {
              Text(GR_MARKERS[this.selIdx].brand)
                .fontSize(12)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.primary)
              Column().layoutWeight(1)
              Text(GR_MARKERS[this.selIdx].tip)
                .fontSize(10)
                .fontColor(GR.dark)
                .padding({ left: 10, right: 10, top: 4, bottom: 4 })
                .borderRadius(12)
                .backgroundColor(GR.primary)
            }
            .width('100%')
            .margin({ top: 12 })

            Text(GR_MARKERS[this.selIdx].name)
              .fontSize(16)
              .fontWeight(FontWeight.Bold)
              .fontColor(GR.text)
              .margin({ top: 6 })

            Column() {
              Text('墨水容量')
                .fontSize(11)
                .fontColor(GR.sub)
              Stack({ alignContent: Alignment.Start }) {
                Row()
                  .width('100%')
                  .height(6)
                  .borderRadius(3)
                  .backgroundColor(GR.dark)
                Row()
                  .width(grInkW(GR_MARKERS[this.selIdx].ink))
                  .height(6)
                  .borderRadius(3)
                  .backgroundColor(GR.accent)
              }
              .width('100%')
              .height(6)
              .margin({ top: 4 })
              Text(GR_MARKERS[this.selIdx].ink + 'ml')
                .fontSize(10)
                .fontColor(GR.accent)
                .margin({ top: 4 })
            }
            .width('100%')
            .margin({ top: 12 })

            Row() {
              Column() {
                Text('笔尖')
                  .fontSize(9)
                  .fontColor(GR.sub)
                Text(GR_MARKERS[this.selIdx].size + 'mm')
                  .fontSize(13)
                  .fontColor(GR.text)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Center)

              Column() {
                Text('防水')
                  .fontSize(9)
                  .fontColor(GR.sub)
                Text(GR_MARKERS[this.selIdx].waterproof === 1 ? '是' : '否')
                  .fontSize(13)
                  .fontColor(GR_MARKERS[this.selIdx].waterproof === 1 ? GR.primary : GR.sub)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Center)

              Column() {
                Text('库存')
                  .fontSize(9)
                  .fontColor(GR.sub)
                Text(GR_MARKERS[this.selIdx].stock + '支')
                  .fontSize(13)
                  .fontColor(GR.text)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Center)

              Column() {
                Text('已售')
                  .fontSize(9)
                  .fontColor(GR.sub)
                Text(GR_MARKERS[this.selIdx].sold + '支')
                  .fontSize(13)
                  .fontColor(GR.primary)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Center)
            }
            .width('100%')
            .padding({ top: 10, bottom: 10 })
            .backgroundColor(GR.dark)
            .borderRadius(10)
            .margin({ top: 12 })

            Text('¥' + (GR_MARKERS[this.selIdx].ink * 15 + 15))
              .fontSize(20)
              .fontWeight(FontWeight.Bold)
              .fontColor(GR.primary)
              .margin({ top: 12 })

            Row({ space: 10 }) {
              Text('关闭')
                .fontSize(13)
                .fontColor(GR.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.dark)
                .onClick(() => {
                  this.showDetail = false
                })
              Text('加入购物车')
                .fontSize(13)
                .fontColor(Color.Black)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.primary)
                .onClick(() => {
                  this.showDetail = false
                })
            }
            .width('100%')
            .margin({ top: 16 })
          }
          .width('86%')
          .constraintSize({ maxHeight: '80%' })
          .backgroundColor(GR.card)
          .borderRadius(16)
          .border({ width: 1, color: GR.line })
          .padding(16)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showAdd) {
        Stack() {
          this.modalOverlay()
          Column() {
            Row() {
              Text('新增马克笔')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.primary)
                .layoutWeight(1)
              Text('×')
                .fontSize(18)
                .fontColor(GR.sub)
                .onClick(() => {
                  this.showAdd = false
                })
            }
            .width('100%')
            .padding({ bottom: 12 })
            .border({ width: { bottom: 1 }, color: GR.line })

            Text('名称')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            TextInput({ placeholder: '如:Sketch 0.3' })
              .height(36)
              .fontSize(11)
              .fontColor(GR.text)
              .backgroundColor(GR.dark)
              .borderRadius(8)
              .border({ width: 1, color: GR.line })
              .margin({ top: 4 })

            Text('品牌')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            TextInput({ placeholder: '如:COPIC' })
              .height(36)
              .fontSize(11)
              .fontColor(GR.text)
              .backgroundColor(GR.dark)
              .borderRadius(8)
              .border({ width: 1, color: GR.line })
              .margin({ top: 4 })

            Text('笔尖类型')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            Row({ space: 8 }) {
              ForEach(['软头', '圆头', '针头', '方头'], (t: string, i: number) => {
                Text(t)
                  .fontSize(10)
                  .fontColor(this.tipChip === i ? GR.dark : GR.sub)
                  .padding({ left: 12, right: 12, top: 6, bottom: 6 })
                  .borderRadius(12)
                  .backgroundColor(this.tipChip === i ? GR.primary : GR.dark)
                  .border({ width: 1, color: GR.line })
                  .onClick(() => {
                    this.tipChip = i
                  })
              }, (t: string) => t)
            }
            .margin({ top: 6 })

            Text('墨水容量(ml)')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            TextInput({ placeholder: '如:2.8' })
              .height(36)
              .fontSize(11)
              .fontColor(GR.text)
              .backgroundColor(GR.dark)
              .borderRadius(8)
              .border({ width: 1, color: GR.line })
              .margin({ top: 4 })

            Row({ space: 10 }) {
              Text('取消')
                .fontSize(13)
                .fontColor(GR.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.dark)
                .onClick(() => {
                  this.showAdd = false
                })
              Text('确认新增')
                .fontSize(13)
                .fontColor(Color.Black)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.primary)
                .onClick(() => {
                  this.showAdd = false
                })
            }
            .width('100%')
            .margin({ top: 16 })
          }
          .width('86%')
          .constraintSize({ maxHeight: '80%' })
          .backgroundColor(GR.card)
          .borderRadius(16)
          .border({ width: 1, color: GR.line })
          .padding(16)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showEdit) {
        Stack() {
          this.modalOverlay()
          Column() {
            Row() {
              Text('编辑马克笔')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.accent)
                .layoutWeight(1)
              Text('×')
                .fontSize(18)
                .fontColor(GR.sub)
                .onClick(() => {
                  this.showEdit = false
                })
            }
            .width('100%')
            .padding({ bottom: 12 })
            .border({ width: { bottom: 1 }, color: GR.line })

            Text('当前:' + GR_MARKERS[this.selIdx].name)
              .fontSize(12)
              .fontColor(GR.sub)
              .margin({ top: 10 })

            Text('修改名称')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            TextInput({ placeholder: GR_MARKERS[this.selIdx].name })
              .height(36)
              .fontSize(11)
              .fontColor(GR.text)
              .backgroundColor(GR.dark)
              .borderRadius(8)
              .border({ width: 1, color: GR.line })
              .margin({ top: 4 })

            Text('修改品牌')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            TextInput({ placeholder: GR_MARKERS[this.selIdx].brand })
              .height(36)
              .fontSize(11)
              .fontColor(GR.text)
              .backgroundColor(GR.dark)
              .borderRadius(8)
              .border({ width: 1, color: GR.line })
              .margin({ top: 4 })

            Text('修改笔尖尺寸(mm)')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            TextInput({ placeholder: GR_MARKERS[this.selIdx].size + '' })
              .height(36)
              .fontSize(11)
              .fontColor(GR.text)
              .backgroundColor(GR.dark)
              .borderRadius(8)
              .border({ width: 1, color: GR.line })
              .margin({ top: 4 })

            Row({ space: 10 }) {
              Text('取消')
                .fontSize(13)
                .fontColor(GR.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.dark)
                .onClick(() => {
                  this.showEdit = false
                })
              Text('保存')
                .fontSize(13)
                .fontColor(GR.dark)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.accent)
                .onClick(() => {
                  this.showEdit = false
                })
            }
            .width('100%')
            .margin({ top: 16 })
          }
          .width('86%')
          .constraintSize({ maxHeight: '80%' })
          .backgroundColor(GR.card)
          .borderRadius(16)
          .border({ width: 1, color: GR.line })
          .padding(16)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }
    }
    .width('100%')
    .height('100%')
  }
}

@Component
struct GrStickerTab {
  @State wave: number = 0
  @State showDetail: boolean = false
  @State showAdd: boolean = false
  @State showDel: boolean = false
  @State selIdx: number = 0
  private timer: number = -1

  aboutToAppear() {
    this.timer = setInterval(() => {
      this.wave = (this.wave + 1) % 10000
    }, 60)
  }

  aboutToDisappear() {
    if (this.timer !== -1) {
      clearInterval(this.timer)
    }
  }

  @Builder modalOverlay() {
    Column()
      .width('100%')
      .height('100%')
      .backgroundColor('rgba(0,0,0,0.72)')
      .position({ x: 0, y: 0 })
      .zIndex(999)
      .onClick(() => {
        this.showDetail = false
        this.showAdd = false
        this.showDel = false
      })
  }

  build() {
    Stack() {
      Scroll() {
        Column() {
          Row() {
            Text('贴纸')
              .fontSize(14)
              .fontWeight(FontWeight.Bold)
              .fontColor(GR.text)
            Column().layoutWeight(1)
            Text('+ 新增')
              .fontSize(11)
              .fontColor(GR.primary)
              .onClick(() => {
                this.showAdd = true
              })
          }
          .width('92%')
          .margin({ top: 8 })

          Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
            ForEach(GR_STICKERS, (s: GrSticker, i: number) => {
              Column() {
                Column()
                  .width(36)
                  .height(36)
                  .borderRadius(8)
                  .backgroundColor(i % 3 === 0 ? GR.dark : GR.line)
                  .scale({ x: grSprayR(this.wave, i), y: grSprayR(this.wave, i) })
                  .opacity(grGlow(this.wave, i))

                Text(s.name)
                  .fontSize(10)
                  .fontColor(GR.text)
                  .margin({ top: 6 })
                Text(s.material)
                  .fontSize(8)
                  .fontColor(GR.sub)
                  .margin({ top: 2 })
                Text(s.count + '枚')
                  .fontSize(9)
                  .fontColor(GR.accent)
                  .margin({ top: 2 })

                Stack({ alignContent: Alignment.Start }) {
                  Row()
                    .width('100%')
                    .height(3)
                    .borderRadius(2)
                    .backgroundColor(GR.dark)
                  Row()
                    .width(grStickerCountW(s.count))
                    .height(3)
                    .borderRadius(2)
                    .backgroundColor(GR.primary)
                }
                .width('100%')
                .height(3)
                .margin({ top: 4 })

                Text('¥' + (s.count / 10 + 5))
                  .fontSize(12)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(GR.primary)
                  .margin({ top: 4 })

                Text('详情')
                  .fontSize(9)
                  .fontColor(GR.sub)
                  .width('100%')
                  .textAlign(TextAlign.Center)
                  .padding({ top: 4, bottom: 4 })
                  .borderRadius(6)
                  .backgroundColor(GR.dark)
                  .margin({ top: 4 })
                  .onClick(() => {
                    this.selIdx = i
                    this.showDetail = true
                  })
              }
              .width('31%')
              .padding(10)
              .backgroundColor(GR.card)
              .borderRadius(12)
              .border({ width: 1, color: GR.line })
              .margin({ top: 8 })
              .alignItems(HorizontalAlign.Center)
            }, (s: GrSticker) => s.name)
          }
          .width('92%')
          .margin({ top: 6 })

          Text('材质分布')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor(GR.text)
            .margin({ top: 14 })

          Column() {
            Row() {
              Text('PVC')
                .fontSize(9)
                .fontColor(GR.sub)
                .width(36)
              Stack({ alignContent: Alignment.Start }) {
                Row()
                  .width('100%')
                  .height(6)
                  .borderRadius(3)
                  .backgroundColor(GR.dark)
                Row()
                  .width('62%')
                  .height(6)
                  .borderRadius(3)
                  .backgroundColor(GR.primary)
              }
              .layoutWeight(1)
              .height(6)
              Text('5款')
                .fontSize(8)
                .fontColor(GR.sub)
                .width(28)
                .margin({ left: 6 })
            }
            .width('100%')
            .margin({ top: 6 })

            Row() {
              Text('全息')
                .fontSize(9)
                .fontColor(GR.sub)
                .width(36)
              Stack({ alignContent: Alignment.Start }) {
                Row()
                  .width('100%')
                  .height(6)
                  .borderRadius(3)
                  .backgroundColor(GR.dark)
                Row()
                  .width('25%')
                  .height(6)
                  .borderRadius(3)
                  .backgroundColor(GR.accent)
              }
              .layoutWeight(1)
              .height(6)
              Text('2款')
                .fontSize(8)
                .fontColor(GR.sub)
                .width(28)
                .margin({ left: 6 })
            }
            .width('100%')
            .margin({ top: 5 })

            Row() {
              Text('纸质')
                .fontSize(9)
                .fontColor(GR.sub)
                .width(36)
              Stack({ alignContent: Alignment.Start }) {
                Row()
                  .width('100%')
                  .height(6)
                  .borderRadius(3)
                  .backgroundColor(GR.dark)
                Row()
                  .width('13%')
                  .height(6)
                  .borderRadius(3)
                  .backgroundColor(GR.primary)
              }
              .layoutWeight(1)
              .height(6)
              Text('1款')
                .fontSize(8)
                .fontColor(GR.sub)
                .width(28)
                .margin({ left: 6 })
            }
            .width('100%')
            .margin({ top: 5 })
          }
          .width('92%')
          .padding(14)
          .backgroundColor(GR.card)
          .borderRadius(14)
          .border({ width: 1, color: GR.line })
          .margin({ top: 6, bottom: 12 })
        }
      }
      .width('100%')
      .height('100%')

      if (this.showDetail) {
        Stack() {
          this.modalOverlay()
          Column() {
            Row() {
              Text('贴纸详情')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.text)
                .layoutWeight(1)
              Text('×')
                .fontSize(18)
                .fontColor(GR.sub)
                .onClick(() => {
                  this.showDetail = false
                })
            }
            .width('100%')
            .padding({ bottom: 12 })
            .border({ width: { bottom: 1 }, color: GR.line })

            Text(GR_STICKERS[this.selIdx].name)
              .fontSize(16)
              .fontWeight(FontWeight.Bold)
              .fontColor(GR.text)
              .margin({ top: 12 })

            Row() {
              Column() {
                Text('尺寸')
                  .fontSize(9)
                  .fontColor(GR.sub)
                Text(GR_STICKERS[this.selIdx].size)
                  .fontSize(12)
                  .fontColor(GR.text)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Center)

              Column() {
                Text('材质')
                  .fontSize(9)
                  .fontColor(GR.sub)
                Text(GR_STICKERS[this.selIdx].material)
                  .fontSize(12)
                  .fontColor(GR.primary)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Center)

              Column() {
                Text('数量')
                  .fontSize(9)
                  .fontColor(GR.sub)
                Text(GR_STICKERS[this.selIdx].count + '枚')
                  .fontSize(12)
                  .fontColor(GR.accent)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Center)
            }
            .width('100%')
            .padding({ top: 10, bottom: 10 })
            .backgroundColor(GR.dark)
            .borderRadius(10)
            .margin({ top: 12 })

            Row() {
              Text('库存')
                .fontSize(11)
                .fontColor(GR.sub)
              Column().layoutWeight(1)
              Text(GR_STICKERS[this.selIdx].stock + ' 件')
                .fontSize(13)
                .fontColor(GR.text)
            }
            .width('100%')
            .margin({ top: 10 })

            Row() {
              Text('已售')
                .fontSize(11)
                .fontColor(GR.sub)
              Column().layoutWeight(1)
              Text(GR_STICKERS[this.selIdx].sold + ' 件')
                .fontSize(13)
                .fontColor(GR.primary)
            }
            .width('100%')
            .margin({ top: 6 })

            Text('¥' + (GR_STICKERS[this.selIdx].count / 10 + 5))
              .fontSize(20)
              .fontWeight(FontWeight.Bold)
              .fontColor(GR.primary)
              .margin({ top: 12 })

            Row({ space: 10 }) {
              Text('关闭')
                .fontSize(13)
                .fontColor(GR.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.dark)
                .onClick(() => {
                  this.showDetail = false
                })
              Text('删除')
                .fontSize(13)
                .fontColor(Color.White)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.accent)
                .onClick(() => {
                  this.showDetail = false
                  this.showDel = true
                })
            }
            .width('100%')
            .margin({ top: 16 })
          }
          .width('86%')
          .constraintSize({ maxHeight: '80%' })
          .backgroundColor(GR.card)
          .borderRadius(16)
          .border({ width: 1, color: GR.line })
          .padding(16)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showAdd) {
        Stack() {
          this.modalOverlay()
          Column() {
            Row() {
              Text('新增贴纸')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(GR.primary)
                .layoutWeight(1)
              Text('×')
                .fontSize(18)
                .fontColor(GR.sub)
                .onClick(() => {
                  this.showAdd = false
                })
            }
            .width('100%')
            .padding({ bottom: 12 })
            .border({ width: { bottom: 1 }, color: GR.line })

            Text('名称')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            TextInput({ placeholder: '如:荧光字母贴' })
              .height(36)
              .fontSize(11)
              .fontColor(GR.text)
              .backgroundColor(GR.dark)
              .borderRadius(8)
              .border({ width: 1, color: GR.line })
              .margin({ top: 4 })

            Text('尺寸')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            TextInput({ placeholder: '如:5x5cm' })
              .height(36)
              .fontSize(11)
              .fontColor(GR.text)
              .backgroundColor(GR.dark)
              .borderRadius(8)
              .border({ width: 1, color: GR.line })
              .margin({ top: 4 })

            Text('材质')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            Row({ space: 8 }) {
              ForEach(['PVC', '全息', '纸质'], (mat: string, i: number) => {
                Text(mat)
                  .fontSize(10)
                  .fontColor(this.selIdx === i ? GR.dark : GR.sub)
                  .padding({ left: 14, right: 14, top: 6, bottom: 6 })
                  .borderRadius(12)
                  .backgroundColor(this.selIdx === i ? GR.primary : GR.dark)
                  .border({ width: 1, color: GR.line })
                  .onClick(() => {
                    this.selIdx = i
                  })
              }, (mat: string) => mat)
            }
            .margin({ top: 6 })

            Text('数量(枚)')
              .fontSize(11)
              .fontColor(GR.sub)
              .margin({ top: 10 })
            TextInput({ placeholder: '如:100' })
              .height(36)
              .fontSize(11)
              .fontColor(GR.text)
              .backgroundColor(GR.dark)
              .borderRadius(8)
              .border({ width: 1, color: GR.line })
              .margin({ top: 4 })

            Row({ space: 10 }) {
              Text('取消')
                .fontSize(13)
                .fontColor(GR.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.dark)
                .onClick(() => {
                  this.showAdd = false
                })
              Text('确认新增')
                .fontSize(13)
                .fontColor(Color.Black)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.primary)
                .onClick(() => {
                  this.showAdd = false
                })
            }
            .width('100%')
            .margin({ top: 16 })
          }
          .width('86%')
          .constraintSize({ maxHeight: '80%' })
          .backgroundColor(GR.card)
          .borderRadius(16)
          .border({ width: 1, color: GR.line })
          .padding(16)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showDel) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('确认删除')
              .fontSize(16)
              .fontWeight(FontWeight.Bold)
              .fontColor(GR.accent)
              .textAlign(TextAlign.Center)
              .width('100%')

            Text(GR_STICKERS[this.selIdx].name)
              .fontSize(13)
              .fontColor(GR.text)
              .margin({ top: 14 })
              .textAlign(TextAlign.Center)
              .width('100%')

            Text('删除后不可恢复')
              .fontSize(11)
              .fontColor(GR.sub)
              .textAlign(TextAlign.Center)
              .width('100%')
              .margin({ top: 8 })

            Row({ space: 10 }) {
              Text('取消')
                .fontSize(13)
                .fontColor(GR.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.dark)
                .onClick(() => {
                  this.showDel = false
                })
              Text('确认删除')
                .fontSize(13)
                .fontColor(Color.White)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(10)
                .backgroundColor(GR.accent)
                .onClick(() => {
                  this.showDel = false
                })
            }
            .width('100%')
            .margin({ top: 20 })
          }
          .width('80%')
          .padding(20)
          .backgroundColor(GR.card)
          .borderRadius(16)
          .border({ width: 1, color: GR.accent })
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }
    }
    .width('100%')
    .height('100%')
  }
}

              .
}

总结

在这里插入图片描述

本文深入解析了一款基于HarmonyOS ArkTS构建的街头涂鸦艺术装备应用,从暗黑霓虹主题系统、涂鸦风格动画效果、色卡式商品展示到创作社区设计,全方位展示了声明式UI在潮流文化领域的创新应用。该应用以强烈的视觉风格和丰富的动效设计,成功塑造了专业而前卫的涂鸦装备品牌形象。

从技术实现的角度来看,该应用有几个特别值得借鉴的设计思路。首先是主题系统的色彩运用,通过高饱和度的霓虹色与纯黑背景的对比,创造出强烈的视觉冲击力,同时借助多层次的深色(背景、卡片、头部)确保了界面的深度感。其次是动画系统的设计,喷漆扩散、发光呼吸、滴落、闪烁等动画效果不仅增强了视觉吸引力,更成为品牌风格的重要组成部分,让应用在同类产品中脱颖而出。最后是数据可视化的巧妙运用,将覆盖面积、压力等参数以微型进度条的形式融入商品卡片,既提升了信息密度,又保持了界面的整洁。

Logo

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

更多推荐