基于HarmonyOS API 24的ArkTS彩绘玻璃艺术工坊深度解析——HarmonyOS 6.1.1光与色彩艺术UI实战

在HarmonyOS 6.1.1的技术舞台上,光与色彩的艺术正在以全新的方式绽放。本文将深入剖析一款彩绘玻璃艺术工坊应用的技术实现,从彩色玻璃光效到多色映射系统,从课程培训体系到作品商城,全方位展示HarmonyOS ArkTS API 24在艺术类应用中的创新表达。

靛蓝的深邃、琥珀金的璀璨、宝石红的艳丽,当这些彩绘玻璃的经典色彩在移动屏幕上流淌,光与影的艺术便有了全新的载体。这款彩绘玻璃工坊应用以前所未有的视觉表现力,将千年玻璃艺术带入了鸿蒙生态。

本文将从彩色主题系统、多色映射机制、光效动画引擎、数据模型设计、课程培训体系、六大Tab功能实现等多个维度,系统拆解这款彩绘玻璃艺术应用的技术内核,为HarmonyOS开发者提供艺术品类应用的设计范式与开发参考。

引言

在这里插入图片描述

随着HarmonyOS 6.1.1的正式发布,ArkTS声明式UI框架迎来了新的发展里程碑,为各类创意应用的开发提供了更加丰富的技术手段。这款彩绘玻璃艺术工坊应用正是基于HarmonyOS ArkTS API 24构建的一款艺术品类应用,它以"光与色彩的艺术"为核心理念,将传统彩绘玻璃工艺与现代移动技术完美融合。

应用整体采用"VG"(Vitrail Glass)前缀的命名规范,以靛蓝(#4338CA)为主色调,琥珀金(#D97706)为强调色,搭配宝石红(#BE123C)、翡翠绿(#047857)、钴蓝(#1D4ED8)等多种彩色玻璃专用色,构建出绚丽而不失雅致的视觉体系。在淡紫色(#F1EFF7)的背景衬托下,各色玻璃如宝石般熠熠生辉,完美诠释了彩绘玻璃艺术的精髓。

从业务架构来看,这是一款集玻璃材料销售、成品作品展示、工具耗材供应、课程培训教学于一体的综合性彩绘玻璃艺术平台。六大核心Tab页签——首页、玻璃、作品、工具、课程、我的——构建了完整的用户旅程,从选购玻璃材料到欣赏成品作品,从购买专业工具到参加系统课程,全方位满足彩绘玻璃爱好者从入门到进阶的全部需求。

技术实现方面,这款应用是四款应用中代码量最大、功能最丰富的一款。除了经典的接口定义层、纯函数工具层、组件渲染层三层架构外,还引入了独特的多色映射系统(根据玻璃颜色名称返回对应颜色值、根据作品风格返回对应主题色),以及完整的课程培训体系(入门/进阶/高级/亲子多级别课程)。这些特色功能使得应用在技术深度和业务丰富度上都更上一层楼。

彩色主题系统设计

在这里插入图片描述

主题接口与基础配色

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

const VG: VgTheme = {
  bg: '#F1EFF7',
  primary: '#4338CA',
  accent: '#D97706',
  card: '#FFFFFF',
  text: '#241E3E',
  sub: '#837CA8',
  line: '#E2DDF0',
  glow: '#7C6FF0',
  dark: '#241E3E'
}

基础主题系统采用靛蓝为主色、琥珀金为强调色的配色方案。靛蓝(#4338CA)深邃而高贵,象征着彩绘玻璃艺术的典雅气质;琥珀金(#D97706)温暖而璀璨,如同阳光透过彩色玻璃洒下的金色光芒。背景色使用淡紫色(#F1EFF7),既柔和又带有一丝神秘感,为各色玻璃元素提供了完美的展示舞台。

主色与强调色的选择是品牌视觉识别的核心。靛蓝与琥珀金的组合既对比鲜明又和谐统一,在传递艺术感的同时保持了良好的可读性和可用性。

彩色玻璃色板

在这里插入图片描述

const VG_RUBY: string = '#BE123C'
const VG_EMERALD: string = '#047857'
const VG_COBALT: string = '#1D4ED8'
const VG_AMBER: string = '#D97706'

除了基础主题色外,应用还定义了四个彩色玻璃专用色常量:宝石红、翡翠绿、钴蓝、琥珀金。这些颜色是彩绘玻璃中最经典、最具代表性的颜色,它们不仅用于玻璃产品的视觉展示,还广泛应用于各种装饰元素和强调效果中。

在彩绘玻璃艺术中,颜色是最重要的表现语言之一。每种颜色的玻璃都有其独特的名称和特性,如钴蓝、红宝石、祖母绿等。应用中保留这些专业色彩名称,既能提升产品的专业感,也能帮助用户建立准确的色彩认知。

Tab导航配置

const VG_TABS: VgTab[] = [
  { name: '首页', icon: '◈' },
  { name: '玻璃', icon: '▩' },
  { name: '作品', icon: '✦' },
  { name: '工具', icon: '⚒' },
  { name: '课程', icon: '✧' },
  { name: '我的', icon: '◉' }
]

与其他三款应用不同,这款彩绘玻璃应用的Tab图标使用了特殊符号而非图片资源。每个符号都与对应Tab的功能有所关联:首页用菱形(◈)象征宝石,玻璃用方格(▩)象征玻璃纹理,作品用星号(✦)象征艺术,工具用锤子(⚒)象征制作,课程用星芒(✧)象征学习,我的用圆环(◉)象征个人中心。这种设计方式既简洁又富有艺术气息。

使用特殊字符作为图标是一种轻量级的图标解决方案。它不需要额外的图片资源,加载速度快,风格统一,且可以通过fontColor属性轻松改变颜色。对于追求简约艺术风格的应用来说,这是一种非常优雅的选择。

数据模型与业务数据

在这里插入图片描述

玻璃材料数据

interface VgGlass {
  name: string
  colorName: string
  texture: string
  thickness: number
  trans: number
  price: number
  stock: number
  sold: number
  tag: string
}

const VG_GLASS: VgGlass[] = [
  { name: '钴蓝教堂玻璃', colorName: '钴蓝', texture: '水纹', thickness: 3, trans: 78, price: 42, stock: 120, sold: 356, tag: '热销' },
  { name: '宝石红玻璃', colorName: '宝石红', texture: '锤纹', thickness: 3, trans: 72, price: 45, stock: 86, sold: 312, tag: '热销' },
  { name: '琥珀金玻璃', colorName: '琥珀金', texture: '猫眼', thickness: 4, trans: 66, price: 52, stock: 64, sold: 288, tag: '进口' },
  { name: '翡翠绿玻璃', colorName: '翡翠绿', texture: '麻纹', thickness: 3, trans: 75, price: 38, stock: 150, sold: 274, tag: '经典' },
  { name: '虹彩玻璃', colorName: '虹彩', texture: '镀膜', thickness: 4, trans: 58, price: 68, stock: 40, sold: 245, tag: '进口' },
  // ... 更多数据
]

玻璃材料的数据模型设计非常专业,包含颜色名称、纹理、厚度、透光率等核心属性。纹理类型有水纹、锤纹、猫眼、麻纹、镀膜、雪花、水滴、起皱、手吹、镜面等多种,充分展示了彩绘玻璃材料的丰富性。透光率(trans)以百分比表示,是玻璃材料的重要参数之一。

数据包含10款玻璃产品,颜色涵盖钴蓝、宝石红、琥珀金、翡翠绿、虹彩、乳白、紫水晶、海蓝、琥珀、墨黑等多种色彩,价格从35元到76元不等,既有入门级产品也有高端进口材料。

材料类产品的数据模型需要特别关注物理属性。厚度、透光率、纹理这些参数对于彩绘玻璃从业者来说是重要的选购依据,它们直接影响作品的最终效果。专业的数据模型是赢得专业用户信任的基础。

作品数据

在这里插入图片描述

interface VgWork {
  name: string
  style: string
  size: string
  panels: number
  colorCount: number
  price: number
  sold: number
  tag: string
}

const VG_WORKS: VgWork[] = [
  { name: '哥特玫瑰窗', style: '哥特', size: '60×60cm', panels: 12, colorCount: 5, price: 1280, sold: 46, tag: '镇店' },
  { name: '蒂芙尼蜻蜓灯罩', style: '蒂芙尼', size: '40×25cm', panels: 9, colorCount: 4, price: 980, sold: 68, tag: '热销' },
  { name: '现代向日葵挂屏', style: '现代', size: '50×35cm', panels: 8, colorCount: 3, price: 720, sold: 92, tag: '热销' },
  { name: '新艺术鸢尾竖窗', style: '新艺术', size: '35×80cm', panels: 10, colorCount: 4, price: 1180, sold: 55, tag: '精品' },
  { name: '拜占庭穹顶灯', style: '拜占庭', size: 'Ø45cm', panels: 14, colorCount: 6, price: 1580, sold: 33, tag: '精品' },
  // ... 更多数据
]

成品作品的数据模型包含风格、尺寸、玻璃片数、颜色数等专业属性。作品风格涵盖哥特、蒂芙尼、现代、新艺术、拜占庭等多种经典流派,展示了彩绘玻璃艺术的多样性。玻璃片数(panels)和颜色数(colorCount)是衡量作品复杂度和工艺价值的重要指标。

作品数据中的"panels"(玻璃片数)是一个非常专业的属性。在彩绘玻璃艺术中,作品的价值往往与玻璃片的数量直接相关——更多的玻璃片意味着更复杂的图案和更高的工艺难度。将这一参数展示给用户,能够有效传递作品的工艺价值。

课程数据

在这里插入图片描述

interface VgCourse {
  name: string
  level: string
  lessons: number
  hours: number
  students: number
  price: number
}

const VG_COURSES: VgCourse[] = [
  { name: '彩绘玻璃入门八讲', level: '入门', lessons: 8, hours: 16, students: 326, price: 398 },
  { name: '蒂芙尼灯罩进阶班', level: '进阶', lessons: 12, hours: 30, students: 158, price: 1280 },
  { name: '割刀手法专训课', level: '入门', lessons: 4, hours: 8, students: 288, price: 268 },
  { name: '哥特花窗设计课', level: '高级', lessons: 10, hours: 26, students: 96, price: 1680 },
  { name: '玻璃彩绘着色课', level: '进阶', lessons: 6, hours: 12, students: 204, price: 588 },
  { name: '亲子玻璃体验营', level: '亲子', lessons: 3, hours: 5, students: 412, price: 198 }
]

课程培训是这款应用的特色业务板块。课程数据模型包含级别、课时数、时长、学员数、价格等属性。课程级别分为入门、进阶、高级、亲子四个等级,形成了完整的学习路径。从398元的入门课程到1680元的高级设计课,价格梯度合理,能够满足不同阶段学员的需求。

课程体系的设计需要遵循学习曲线规律。入门课侧重基础操作,进阶课侧重专项技能,高级课侧重设计创作,亲子课侧重体验互动。这种阶梯式的课程设计能够引导用户持续学习,提升平台的长期价值。

光效动画与颜色映射系统

在这里插入图片描述

光效动画函数

function vgRay(w: number, i: number): number {
  return (w + i * 47) % 100
}

function vgShine(w: number): number {
  return w % 2
}

function vgFloatY(w: number, i: number): number {
  return Math.sin((w + i * 30) * 0.08) * 4
}

function vgBlink(w: number): number {
  return w % 2
}

虽然光效动画函数数量不多(4个),但每一个都经过精心设计,服务于彩绘玻璃的视觉主题。vgRay函数模拟光线穿过玻璃的折射效果,返回0到100的相位值;vgShine函数产生闪烁效果,模拟玻璃在光线下的反光;vgFloatY函数实现轻盈的浮动效果,如同玻璃在微风中轻轻摇曳;vgBlink函数则产生眨眼般的开关效果。

彩绘玻璃的核心美感在于光与色彩的互动。当光线穿过彩色玻璃时,会产生折射、反射、色散等光学现象。虽然移动屏幕无法真正模拟这些物理效果,但通过精心设计的动画函数,可以在视觉上营造出类似的光感和动感。

多色映射系统

function vgGlassColor(c: string): string {
  if (c === '钴蓝') {
    return '#1D4ED8'
  }
  if (c === '宝石红') {
    return '#BE123C'
  }
  if (c === '琥珀金' || c === '琥珀') {
    return '#D97706'
  }
  if (c === '翡翠绿') {
    return '#047857'
  }
  if (c === '紫水晶') {
    return '#7C3AED'
  }
  if (c === '海蓝') {
    return '#0891B2'
  }
  if (c === '墨黑') {
    return '#312E3F'
  }
  if (c === '虹彩') {
    return '#DB2777'
  }
  return '#D8D3E8'
}

function vgStyleColor(s: string): string {
  if (s === '哥特') {
    return '#BE123C'
  }
  if (s === '蒂芙尼') {
    return '#D97706'
  }
  if (s === '现代') {
    return '#1D4ED8'
  }
  if (s === '拜占庭') {
    return '#7C3AED'
  }
  return '#047857'
}

多色映射系统是这款彩绘玻璃应用的核心技术特色之一。vgGlassColor函数根据玻璃颜色名称返回对应的十六进制颜色值,支持钴蓝、宝石红、琥珀金、翡翠绿、紫水晶、海蓝、墨黑、虹彩等8种颜色,几乎涵盖了所有经典的彩绘玻璃色彩。

vgStyleColor函数则根据作品风格返回对应的主题色:哥特风格用宝石红,蒂芙尼风格用琥珀金,现代风格用钴蓝,拜占庭风格用紫水晶,其他风格用翡翠绿。这种风格-颜色映射系统使得每种艺术风格都有其标志性的视觉色彩,增强了品牌识别度。

颜色映射函数是一种优雅的设计模式。它将颜色名称(业务语义)与颜色值(视觉表现)解耦,使得颜色修改和扩展变得简单。当需要新增一种玻璃颜色时,只需在映射函数中增加一个分支即可,无需改动所有使用颜色的UI代码。

统计计算函数

function vgStyleCount(s: string): number {
  let r: number = 0
  for (let i = 0; i < VG_WORKS.length; i++) {
    if (VG_WORKS[i].style === s) {
      r = r + 1
    }
  }
  return r
}

function vgTypeCount(ty: string): number {
  let r: number = 0
  for (let i = 0; i < VG_TOOLS.length; i++) {
    if (VG_TOOLS[i].type === ty) {
      r = r + 1
    }
  }
  return r
}

vgStyleCountvgTypeCount是两个统计类函数,分别用于计算某种风格的作品数量和某种类型的工具数量。这些统计函数为数据可视化提供了基础数据支持,使得应用可以展示风格分布图、工具类型占比等统计图表。

主组件与首页设计

品牌头部与搜索

        // 头部品牌栏
        Row() {
          Column() {
            Text('Vitrail').fontSize(22).fontWeight(900).fontColor(VG.primary)
            Text('GLASS STUDIO · 彩绘玻璃').fontSize(10).fontColor(VG.sub).letterSpacing(2)
          }
          .alignItems(HorizontalAlign.Start)
          Column().layoutWeight(1)
          Row() {
            Text('光影艺术').fontSize(12).fontColor(VG.sub)
            Column().width(2).height(12).backgroundColor(VG.line).margin({ left: 8, right: 8 })
            Text('手工创作').fontSize(10).fontColor(VG.accent).fontWeight(700)
          }
        }
        .width('100%').height(56).padding({ left: 16, right: 16 }).backgroundColor(VG.card)

品牌头部设计延续了艺术风格,“Vitrail”(法语中"彩色玻璃"之意)使用靛蓝色主色,下方是"GLASS STUDIO · 彩绘玻璃"的英中文组合,传递出专业的艺术工作室气质。右侧Slogan"光影艺术 · 手工创作"中,"手工创作"使用琥珀金强调,点明了手工艺术的核心价值。

首页功能架构

首页是应用内容最丰富的页面,包含Banner轮播、品类入口、热门作品、玻璃材料、工具推荐、课程推荐等多个模块。每个模块都采用卡片式设计,在淡紫色背景上错落有致地排列,形成富有节奏感的页面布局。

玻璃光效Banner是首页的视觉焦点,使用渐变色模拟光线穿过彩色玻璃的效果,配合动态光效动画,营造出梦幻般的视觉体验。品类入口则以彩色方块图标的形式展示玻璃、作品、工具、课程四大品类,引导用户快速进入相应板块。

首页设计的核心目标是"发现"——让用户在浏览过程中发现感兴趣的内容,从而激发进一步探索的欲望。通过多样化的内容模块和精心设计的视觉层次,可以有效提升用户的浏览深度和转化率。

应用架构流程图

home

glass

work

tool

course

mine

点击切换

应用入口 VgApp

彩色主题系统初始化

渲染艺术品牌头部

渲染搜索条

Tab切换逻辑

首页Tab

玻璃Tab

作品Tab

工具Tab

课程Tab

我的Tab

玻璃光效Banner

品类入口宫格

热门作品展示

精选玻璃材料

专业工具推荐

精品课程横滑

颜色筛选Chips

玻璃材料网格

透光率对比图

厚度分布柱状图

风格筛选Chips

作品列表

玻璃片数可视化

风格分布统计

课程级别筛选

课程卡片列表

课时/时长对比

学员人数统计

商品弹框层

详情弹框

新增弹框

编辑弹框

删除弹框

会员卡

消费统计

月度消费图

订单列表

收藏列表

设置列表

底部Tab栏

玻璃Tab深度解析

颜色筛选与材料展示

          // 颜色筛选 chips
          Scroll() {
            Row({ space: 8 }) {
              ForEach(['全部', '热销', '进口', '经典', '新品', '手工'], (t: string) => {
                Text(t).fontSize(12).fontColor(this.filterTag === t ? VG.card : VG.text)
                  .padding({ left: 16, right: 16, top: 6, bottom: 6 })
                  .borderRadius(16)
                  .backgroundColor(this.filterTag === t ? VG.primary : VG.bg)
                  .onClick(() => {
                    this.filterTag = t
                    this.list = vgFilterGlass(t)
                  })
              }, (t: string) => t)
            }
            .padding({ left: 12, right: 12 })
          }
          .scrollable(ScrollDirection.Horizontal)
          .scrollBar(BarState.Off)
          .width('100%')
          .height(36)

玻璃Tab的筛选Chips提供了"全部、热销、进口、经典、新品、手工"六个标签选项,从不同维度对玻璃材料进行分类。用户可以根据自己的需求快速筛选出感兴趣的玻璃类型。

玻璃材料卡片采用双列网格布局,每张卡片顶部是对应颜色的玻璃预览色块,使用vgGlassColor函数根据玻璃颜色名称动态获取颜色值。这种数据驱动的颜色渲染方式使得新增玻璃颜色时无需修改UI代码,只需在映射函数中增加对应颜色即可。

玻璃材料的展示方式对用户体验影响很大。将玻璃的真实颜色作为卡片主视觉,能够让用户直观地感受玻璃的色彩效果,大幅降低选购决策成本。颜色映射函数在其中扮演了关键角色。

透光率与厚度可视化

          // 透光率对比
          Column() {
            Text('透光率对比 (%)').fontSize(13).fontWeight(700).fontColor(VG.dark).margin({ bottom: 12 })
            ForEach(this.list.slice(0, 5), (g: VgGlass, i: number) => {
              Row() {
                Text(g.name.substring(0, 6)).fontSize(10).fontColor(VG.sub).width(60)
                Stack({ alignContent: Alignment.Start }) {
                  Column().width('100%').height(10).borderRadius(5).backgroundColor(VG.bg)
                  Column().width(vgTransW(g.trans).toString() + '%').height(10).borderRadius(5).backgroundColor(vgGlassColor(g.colorName))
                }
                .layoutWeight(1)
                Text(g.trans.toString()).fontSize(10).fontColor(VG.text).fontWeight(600).width(28)
              }
              .width('100%').margin({ bottom: 6 })
            }, (g: VgGlass) => g.name)
          }
          .width('100%').padding(16).backgroundColor(VG.card).margin({ top: 8 })

透光率对比图是玻璃Tab的特色数据可视化。每个玻璃的透光率以横向进度条的形式展示,进度条的颜色使用玻璃本身的颜色,通过vgGlassColor(g.colorName)动态获取。这种将产品本身颜色融入数据可视化的设计既美观又直观,用户一眼就能将颜色与透光率对应起来。

厚度分布柱状图则以竖向柱形展示各款玻璃的厚度,柱形高度由vgThickH函数计算得出。厚度是玻璃材料的重要参数,直接影响玻璃的质感和用途,将其可视化有助于用户快速比较和选择。

数据可视化的最高境界是"所见即所得"——图表本身就能传递信息,而不需要用户费力解读。将玻璃颜色融入透光率图表,就是一个很好的例子:彩色进度条不仅展示了透光率数值,还同时展示了玻璃本身的颜色,一举两得。

作品Tab与风格体系

作品风格分类

          // 风格筛选
          Scroll() {
            Row({ space: 8 }) {
              ForEach(['全部', '哥特', '蒂芙尼', '现代', '新艺术', '拜占庭'], (s: string) => {
                Text(s).fontSize(12).fontColor(this.filterStyle === s ? '#FFFFFF' : VG.text)
                  .padding({ left: 16, right: 16, top: 6, bottom: 6 })
                  .borderRadius(16)
                  .backgroundColor(this.filterStyle === s ? vgStyleColor(s) : VG.bg)
                  .onClick(() => {
                    this.filterStyle = s
                  })
              }, (s: string) => s)
            }
            .padding({ left: 12, right: 12 })
          }
          .scrollable(ScrollDirection.Horizontal)
          .scrollBar(BarState.Off)

作品Tab的风格筛选是vgStyleColor函数的典型应用场景。每个风格标签的选中状态背景色就是该风格对应的主题色:哥特是宝石红、蒂芙尼是琥珀金、现代是钴蓝、拜占庭是紫水晶。这种设计让每个风格都有了自己的视觉标识,用户可以快速建立风格与颜色的对应关系。

作品列表采用卡片式布局,每张作品卡片包含作品名称、风格标签、尺寸、玻璃片数、颜色数、价格等信息。玻璃片数和颜色数以数据条的形式可视化展示,让用户可以直观感受作品的复杂度。

风格是艺术品类应用的核心分类维度。为每种风格赋予独特的品牌色,不仅能提升视觉美感,更能在用户心中建立深刻的品牌记忆。这就像提到印象派就想到莫奈的光影、提到哥特就想到神秘的教堂一样。

风格分布统计

          // 风格分布
          Column() {
            Text('风格分布').fontSize(13).fontWeight(700).fontColor(VG.dark).margin({ bottom: 12 })
            Column() {
              Row() {
                Text('哥特').fontSize(11).fontColor(VG.sub).width(56)
                Stack({ alignContent: Alignment.Start }) {
                  Column().width('100%').height(8).borderRadius(4).backgroundColor(VG.bg)
                  Column().width((vgStyleCount('哥特') / VG_WORKS.length * 100).toString() + '%').height(8).borderRadius(4).backgroundColor(vgStyleColor('哥特'))
                }
                .layoutWeight(1)
                Text(vgStyleCount('哥特').toString()).fontSize(11).fontColor(VG.text).fontWeight(600).width(28)
              }
              .margin({ bottom: 8 })
              // ... 更多风格
            }
            .width('100%')
          }
          .width('100%').padding(16).backgroundColor(VG.card).margin({ top: 8 })

风格分布统计是作品Tab的数据可视化模块,使用横向进度条展示各种风格作品的数量分布。每个进度条的颜色使用对应风格的主题色,与上方的筛选标签形成呼应。vgStyleCount函数负责统计每种风格的作品数量,为图表提供数据支持。

这种统计图表不仅具有装饰性,更具有实际的信息价值——用户可以通过它快速了解平台上各种风格的作品数量分布,从而判断平台的风格偏向和自己的喜好匹配度。

课程Tab与培训体系

课程级别与卡片设计

          // 级别筛选
          Scroll() {
            Row({ space: 8 }) {
              ForEach(['全部', '入门', '进阶', '高级', '亲子'], (l: string) => {
                Text(l).fontSize(12).fontColor(this.filterLevel === l ? VG.card : VG.text)
                  .padding({ left: 16, right: 16, top: 6, bottom: 6 })
                  .borderRadius(16)
                  .backgroundColor(this.filterLevel === l ? VG.primary : VG.bg)
                  .onClick(() => {
                    this.filterLevel = l
                  })
              }, (l: string) => l)
            }
            .padding({ left: 12, right: 12 })
          }

课程Tab的筛选提供了"全部、入门、进阶、高级、亲子"五个级别选项,对应完整的学习路径体系。用户可以根据自己的水平选择合适级别的课程。

课程卡片是课程Tab的核心UI组件。每张卡片包含课程名称、级别标签、课时数、时长、学员人数、价格等信息。课时数和时长以图标+数字的形式展示,学员人数则反映了课程的受欢迎程度。卡片底部的价格使用琥珀金强调色突出显示,引导用户购买。

课程产品的信息展示需要平衡专业性和易懂性。课时数、时长、学员数这些数据能够帮助用户判断课程的含金量和受欢迎程度,但过多的数据也可能造成信息过载。合理的信息层级设计至关重要。

课程数据可视化

          // 课时对比
          Column() {
            Text('课时对比').fontSize(13).fontWeight(700).fontColor(VG.dark).margin({ bottom: 12 })
            Row({ space: 6 }) {
              ForEach(this.courseList.slice(0, 6), (c: VgCourse, i: number) => {
                Column() {
                  Column().width(20).height(vgLessonH(c.lessons)).borderRadius(4).backgroundColor(VG.primary)
                  Text(c.lessons.toString() + '节').fontSize(8).fontColor(VG.sub).margin({ top: 4 })
                }
                .alignItems(HorizontalAlign.Center)
              }, (c: VgCourse) => c.name)
            }
            .width('100%').justifyContent(FlexAlign.SpaceAround)
          }
          .width('100%').padding(16).backgroundColor(VG.card).margin({ top: 8, bottom: 8 })

课时对比柱状图直观展示了各门课程的课时数量差异,柱形高度由vgLessonH函数计算得出。学员人数分布图则展示了各门课程的受欢迎程度,使用横向进度条的形式呈现。

这些数据可视化图表不仅美化了页面,更重要的是帮助用户快速比较各门课程的规模和受欢迎程度,从而做出更明智的选择。对于教育类产品来说,这种数据透明化的做法能够有效提升用户的信任感。

"我的"Tab与会员体系

订单与收藏

          // 订单列表
          Column() {
            Text('我的订单').fontSize(13).fontWeight(700).fontColor(VG.dark).margin({ bottom: 8 })
            ForEach(this.orders, (o: VgOrder, i: number) => {
              Row() {
                Column() {
                  Text(o.name).fontSize(13).fontWeight(600).fontColor(VG.text)
                  Text(o.id + ' · ' + o.date).fontSize(10).fontColor(VG.sub).margin({ top: 2 })
                }
                .alignItems(HorizontalAlign.Start).layoutWeight(1)
                Column() {
                  Text('¥' + o.amount.toString()).fontSize(13).fontWeight(700).fontColor(VG.text)
                  Text(o.status).fontSize(10).fontColor(vgStatusColor(o.status)).margin({ top: 2 })
                }
                .alignItems(HorizontalAlign.End)
              }
              .width('100%').padding({ top: 8, bottom: 8 })
              .border({ width: { bottom: 1 }, color: VG.line })
            }, (o: VgOrder) => o.id)
          }

订单列表展示了用户的购买记录,状态颜色通过vgStatusColor函数动态计算。这款应用的订单状态有三种:"已完成"使用翡翠绿色,"制作中"使用琥珀金色,"待开课"使用靛蓝色。三种状态分别对应绿色(完成)、橙色(进行中)、蓝色(等待中)的语义,符合用户的直觉认知。

订单状态的颜色设计应该遵循通用的语义惯例:绿色表示完成/成功,橙色/黄色表示进行中/警告,蓝色/灰色表示等待/未开始。这种符合用户直觉的设计能够降低认知成本,提升使用效率。

技术点对比表格

技术维度 玻璃艺术 (VG) 盆景艺术 (BN) 皮具工坊 (LT) 纹身潮流 (IW)
代码规模 最大(4458行) 2274行 2251行 2124行
设计风格 彩绘玻璃艺术风 新中式禅意 复古手作风 潮流赛博风
主题模式 浅色多彩 浅色单彩 浅色单彩 深色霓虹
主色调 靛蓝 #4338CA 苔藓绿 #5B7C5A 马鞍棕 #8B4513 荧光青 #00FFFF
强调色 琥珀金 #D97706 陶土棕 #B5651D 焦糖橙 #D2691E 电粉 #FF1493
颜色系统 8色玻璃映射+风格映射 9色主题令牌 9色主题令牌 9色主题令牌
特色功能 课程培训体系 展览拍卖 匠人定制 图案设计社区
动画数量 4个 4个 6个 6个
数据接口数 7个 8个 8个 8个
Tab数量 6个 6个 6个 6个
图标方案 特殊字符符号 资源图片 资源图片 资源图片
筛选维度 风格/级别/标签 标签分类 标签分类 风格标签

安装DevEco Studio程序

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

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

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

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

在这里插入图片描述


完整代码:

// 场景:彩绘玻璃 / 花窗 / 蒂芙尼灯罩 工坊商城(得物风格)
// 主题:靛蓝 × 琥珀金 × 宝石红(浅色系,彩色玻璃点缀)
// 6 tab:首页 / 玻璃 / 作品 / 工具 / 课程 / 我的

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

interface VgTab {
  name: string
  icon: string
}

interface VgGlass {
  name: string
  colorName: string
  texture: string
  thickness: number
  trans: number
  price: number
  stock: number
  sold: number
  tag: string
}

interface VgWork {
  name: string
  style: string
  size: string
  panels: number
  colorCount: number
  price: number
  sold: number
  tag: string
}

interface VgTool {
  name: string
  type: string
  brand: string
  material: string
  use: string
  price: number
  sold: number
}

interface VgCourse {
  name: string
  level: string
  lessons: number
  hours: number
  students: number
  price: number
}

interface VgOrder {
  id: string
  name: string
  date: string
  amount: number
  status: string
}

interface VgFav {
  name: string
  kind: string
  price: number
  note: string
}

interface VgMonth {
  month: string
  amount: number
}

const VG: VgTheme = {
  bg: '#F1EFF7',
  primary: '#4338CA',
  accent: '#D97706',
  card: '#FFFFFF',
  text: '#241E3E',
  sub: '#837CA8',
  line: '#E2DDF0',
  glow: '#7C6FF0',
  dark: '#241E3E'
}

const VG_RUBY: string = '#BE123C'
const VG_EMERALD: string = '#047857'
const VG_COBALT: string = '#1D4ED8'
const VG_AMBER: string = '#D97706'

const VG_TABS: VgTab[] = [
  { name: '首页', icon: '◈' },
  { name: '玻璃', icon: '▩' },
  { name: '作品', icon: '✦' },
  { name: '工具', icon: '⚒' },
  { name: '课程', icon: '✧' },
  { name: '我的', icon: '◉' }
]

const VG_GLASS: VgGlass[] = [
  { name: '钴蓝教堂玻璃', colorName: '钴蓝', texture: '水纹', thickness: 3, trans: 78, price: 42, stock: 120, sold: 356, tag: '热销' },
  { name: '宝石红玻璃', colorName: '宝石红', texture: '锤纹', thickness: 3, trans: 72, price: 45, stock: 86, sold: 312, tag: '热销' },
  { name: '琥珀金玻璃', colorName: '琥珀金', texture: '猫眼', thickness: 4, trans: 66, price: 52, stock: 64, sold: 288, tag: '进口' },
  { name: '翡翠绿玻璃', colorName: '翡翠绿', texture: '麻纹', thickness: 3, trans: 75, price: 38, stock: 150, sold: 274, tag: '经典' },
  { name: '虹彩玻璃', colorName: '虹彩', texture: '镀膜', thickness: 4, trans: 58, price: 68, stock: 40, sold: 245, tag: '进口' },
  { name: '乳白雪花玻璃', colorName: '乳白', texture: '雪花', thickness: 3, trans: 52, price: 35, stock: 98, sold: 226, tag: '经典' },
  { name: '紫水晶玻璃', colorName: '紫水晶', texture: '水滴', thickness: 5, trans: 61, price: 58, stock: 55, sold: 208, tag: '新品' },
  { name: '海蓝起皱玻璃', colorName: '海蓝', texture: '起皱', thickness: 3, trans: 70, price: 40, stock: 110, sold: 196, tag: '新品' },
  { name: '琥珀手吹玻璃', colorName: '琥珀', texture: '手吹', thickness: 4, trans: 64, price: 76, stock: 32, sold: 178, tag: '手工' },
  { name: '墨黑镜面玻璃', colorName: '墨黑', texture: '镜面', thickness: 5, trans: 45, price: 62, stock: 48, sold: 154, tag: '手工' }
]

const VG_WORKS: VgWork[] = [
  { name: '哥特玫瑰窗', style: '哥特', size: '60×60cm', panels: 12, colorCount: 5, price: 1280, sold: 46, tag: '镇店' },
  { name: '蒂芙尼蜻蜓灯罩', style: '蒂芙尼', size: '40×25cm', panels: 9, colorCount: 4, price: 980, sold: 68, tag: '热销' },
  { name: '现代向日葵挂屏', style: '现代', size: '50×35cm', panels: 8, colorCount: 3, price: 720, sold: 92, tag: '热销' },
  { name: '新艺术鸢尾竖窗', style: '新艺术', size: '35×80cm', panels: 10, colorCount: 4, price: 1180, sold: 55, tag: '精品' },
  { name: '拜占庭穹顶灯', style: '拜占庭', size: 'Ø45cm', panels: 14, colorCount: 6, price: 1580, sold: 33, tag: '精品' },
  { name: '草原风格台灯', style: '新艺术', size: '30×20cm', panels: 7, colorCount: 3, price: 660, sold: 110, tag: '人气' },
  { name: '几何蝴蝶挂饰', style: '现代', size: '25×25cm', panels: 6, colorCount: 3, price: 380, sold: 186, tag: '人气' },
  { name: '海浪珠帘屏风', style: '现代', size: '80×40cm', panels: 11, colorCount: 5, price: 1080, sold: 62, tag: '新品' }
]

const VG_TOOLS: VgTool[] = [
  { name: '专业玻璃割刀', type: '切割', brand: 'Toyo', material: '碳化钨轮', use: '裁切玻璃', price: 168, sold: 428 },
  { name: '恒温焊锡台', type: '焊接', brand: 'Weller', material: '紫铜烙铁头', use: '锡线焊接', price: 298, sold: 356 },
  { name: '金刚石磨边机', type: '打磨', brand: 'Glastar', material: '金刚砂磨盘', use: '边缘打磨', price: 1180, sold: 128 },
  { name: '铜箔胶带', type: '包边', brand: 'Edco', material: '电解铜箔', use: '玻璃包边', price: 26, sold: 962 },
  { name: '平口玻璃钳', type: '夹持', brand: '世纪', material: '高碳钢', use: '掰断玻璃', price: 58, sold: 734 },
  { name: '助焊膏', type: '焊接', brand: 'Gedas', material: '松香脂', use: '助焊防氧化', price: 22, sold: 688 },
  { name: '油壶割刀笔', type: '切割', brand: '红鹰', material: '合金刀轮', use: '曲线裁切', price: 96, sold: 512 },
  { name: '花样剪刀', type: '切割', brand: '世纪', material: '弹簧钢', use: '纸样剪裁', price: 45, sold: 465 }
]

const VG_COURSES: VgCourse[] = [
  { name: '彩绘玻璃入门八讲', level: '入门', lessons: 8, hours: 16, students: 326, price: 398 },
  { name: '蒂芙尼灯罩进阶班', level: '进阶', lessons: 12, hours: 30, students: 158, price: 1280 },
  { name: '割刀手法专训课', level: '入门', lessons: 4, hours: 8, students: 288, price: 268 },
  { name: '哥特花窗设计课', level: '高级', lessons: 10, hours: 26, students: 96, price: 1680 },
  { name: '玻璃彩绘着色课', level: '进阶', lessons: 6, hours: 12, students: 204, price: 588 },
  { name: '亲子玻璃体验营', level: '亲子', lessons: 3, hours: 5, students: 412, price: 198 }
]

const VG_ORDERS: VgOrder[] = [
  { id: 'VG20260801', name: '蒂芙尼蜻蜓灯罩', date: '08-01', amount: 980, status: '已完成' },
  { id: 'VG20260805', name: '钴蓝教堂玻璃×5', date: '08-05', amount: 210, status: '制作中' },
  { id: 'VG20260809', name: '专业玻璃割刀', date: '08-09', amount: 168, status: '已完成' },
  { id: 'VG20260812', name: '亲子玻璃体验营', date: '08-12', amount: 198, status: '待开课' },
  { id: 'VG20260816', name: '几何蝴蝶挂饰', date: '08-16', amount: 380, status: '制作中' },
  { id: 'VG20260820', name: '铜箔胶带×6', date: '08-20', amount: 156, status: '已完成' }
]

const VG_FAVS: VgFav[] = [
  { name: '哥特玫瑰窗', kind: '作品', price: 1280, note: '镇店之宝' },
  { name: '虹彩玻璃', kind: '玻璃', price: 68, note: '镀膜超美' },
  { name: '恒温焊锡台', kind: '工具', price: 298, note: '新手必备' },
  { name: '海浪珠帘屏风', kind: '作品', price: 1080, note: '客厅绝配' },
  { name: '琥珀手吹玻璃', kind: '玻璃', price: 76, note: '手吹纹理' },
  { name: '哥特花窗设计课', kind: '课程', price: 1680, note: '想学设计' }
]

const VG_MONTHS: VgMonth[] = [
  { month: '3月', amount: 560 },
  { month: '4月', amount: 890 },
  { month: '5月', amount: 420 },
  { month: '6月', amount: 1260 },
  { month: '7月', amount: 780 },
  { month: '8月', amount: 1080 }
]

function vgRay(w: number, i: number): number {
  return (w + i * 47) % 100
}

function vgShine(w: number): number {
  return w % 2
}

function vgFloatY(w: number, i: number): number {
  return Math.sin((w + i * 30) * 0.08) * 4
}

function vgBlink(w: number): number {
  return w % 2
}

function vgFilterGlass(k: string): VgGlass[] {
  let r: VgGlass[] = []
  for (let i = 0; i < VG_GLASS.length; i++) {
    if (k === '全部' || VG_GLASS[i].tag === k) {
      r.push(VG_GLASS[i])
    }
  }
  return r
}

function vgSoldRank(): VgGlass[] {
  let r: VgGlass[] = VG_GLASS.slice()
  r.sort((a: VgGlass, b: VgGlass) => b.sold - a.sold)
  return r
}

function vgWorkRank(): VgWork[] {
  let r: VgWork[] = VG_WORKS.slice()
  r.sort((a: VgWork, b: VgWork) => b.sold - a.sold)
  return r
}

function vgToolRank(): VgTool[] {
  let r: VgTool[] = VG_TOOLS.slice()
  r.sort((a: VgTool, b: VgTool) => b.sold - a.sold)
  return r
}

function vgTransW(t: number): number {
  return t
}

function vgThickH(t: number): number {
  return t * 20
}

function vgPanelW(p: number): number {
  return p * 8
}

function vgLessonH(l: number): number {
  return l * 9
}

function vgStudentW(s: number): number {
  return Math.min(100, s / 5)
}

function vgMonthH(m: number): number {
  return Math.min(120, m / 12)
}

function vgStatusColor(s: string): string {
  if (s === '已完成') {
    return '#047857'
  }
  if (s === '制作中') {
    return '#D97706'
  }
  return '#4338CA'
}

function vgStyleColor(s: string): string {
  if (s === '哥特') {
    return '#BE123C'
  }
  if (s === '蒂芙尼') {
    return '#D97706'
  }
  if (s === '现代') {
    return '#1D4ED8'
  }
  if (s === '拜占庭') {
    return '#7C3AED'
  }
  return '#047857'
}

function vgGlassColor(c: string): string {
  if (c === '钴蓝') {
    return '#1D4ED8'
  }
  if (c === '宝石红') {
    return '#BE123C'
  }
  if (c === '琥珀金' || c === '琥珀') {
    return '#D97706'
  }
  if (c === '翡翠绿') {
    return '#047857'
  }
  if (c === '紫水晶') {
    return '#7C3AED'
  }
  if (c === '海蓝') {
    return '#0891B2'
  }
  if (c === '墨黑') {
    return '#312E3F'
  }
  if (c === '虹彩') {
    return '#DB2777'
  }
  return '#D8D3E8'
}

function vgStyleCount(s: string): number {
  let r: number = 0
  for (let i = 0; i < VG_WORKS.length; i++) {
    if (VG_WORKS[i].style === s) {
      r = r + 1
    }
  }
  return r
}

function vgTypeCount(ty: string): number {
  let r: number = 0
  for (let i = 0; i < VG_TOOLS.length; i++) {
    if (VG_TOOLS[i].type === ty) {
      r = r + 1
    }
  }
  return r
}

function vgGlassByName(n: string): VgGlass {
  let r: VgGlass = VG_GLASS[0]
  for (let i = 0; i < VG_GLASS.length; i++) {
    if (VG_GLASS[i].name === n) {
      r = VG_GLASS[i]
    }
  }
  return r
}

function vgWorkByName(n: string): VgWork {
  let r: VgWork = VG_WORKS[0]
  for (let i = 0; i < VG_WORKS.length; i++) {
    if (VG_WORKS[i].name === n) {
      r = VG_WORKS[i]
    }
  }
  return r
}

function vgToolByName(n: string): VgTool {
  let r: VgTool = VG_TOOLS[0]
  for (let i = 0; i < VG_TOOLS.length; i++) {
    if (VG_TOOLS[i].name === n) {
      r = VG_TOOLS[i]
    }
  }
  return r
}

function vgCourseByName(n: string): VgCourse {
  let r: VgCourse = VG_COURSES[0]
  for (let i = 0; i < VG_COURSES.length; i++) {
    if (VG_COURSES[i].name === n) {
      r = VG_COURSES[i]
    }
  }
  return r
}

@Entry
@Component
struct VgApp {
  @State currentTab: number = 0
  @State showCustom: boolean = false
  @State showExpo: boolean = false
  @State showClass: boolean = false
  @State showComm: boolean = false

  @Builder
  modalOverlay() {
    Column()
      .width('100%')
      .height('100%')
      .backgroundColor('rgba(36,30,62,0.62)')
      .position({ x: 0, y: 0 })
      .zIndex(999)
      .onClick(() => {
        this.showCustom = false
        this.showExpo = false
        this.showClass = false
        this.showComm = false
      })
  }

  build() {
    Stack() {
      Column() {
        Column() {
          Row() {
            Row({ space: 8 }) {
              Row({ space: 3 }) {
                Column().width(10).height(10).backgroundColor(VG_AMBER).borderRadius(2)
                Column().width(10).height(10).backgroundColor(VG_RUBY).borderRadius(2)
                Column().width(10).height(10).backgroundColor(VG_COBALT).borderRadius(2)
              }
              Column() {
                Text('VITRAIL')
                  .fontSize(22)
                  .fontColor('#FFFFFF')
                  .fontWeight(FontWeight.Bold)
                  .letterSpacing(2)
                Text('彩绘玻璃艺术工坊')
                  .fontSize(11)
                  .fontColor('rgba(255,255,255,0.72)')
                  .margin({ top: 2 })
              }
              .alignItems(HorizontalAlign.Start)
            }
            .alignItems(VerticalAlign.Center)

            Row({ space: 10 }) {
              Column() {
                Text('♢').fontSize(18).fontColor('#FFFFFF')
                Text('鉴赏').fontSize(9).fontColor('rgba(255,255,255,0.72)').margin({ top: 2 })
              }
              .alignItems(HorizontalAlign.Center)
              .onClick(() => {
                this.showExpo = true
              })

              Column() {
                Text('☰').fontSize(18).fontColor('#FFFFFF')
                Text('更多').fontSize(9).fontColor('rgba(255,255,255,0.72)').margin({ top: 2 })
              }
              .alignItems(HorizontalAlign.Center)
              .onClick(() => {
                this.showComm = true
              })
            }
            .alignItems(VerticalAlign.Center)
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .alignItems(VerticalAlign.Center)

          Row() {
            Text('⌕').fontSize(16).fontColor('rgba(255,255,255,0.8)')
            Text('搜索玻璃 / 作品 / 课程')
              .fontSize(13)
              .fontColor('rgba(255,255,255,0.6)')
              .margin({ left: 8 })
          }
          .width('100%')
          .height(38)
          .borderRadius(19)
          .backgroundColor('rgba(255,255,255,0.16)')
          .margin({ top: 12 })
          .alignItems(VerticalAlign.Center)
          .padding({ left: 14, right: 14 })
        }
        .width('100%')
        .padding({ left: 16, right: 16, top: 14, bottom: 12 })
        .backgroundColor(VG.primary)

        Column() {
          if (this.currentTab === 0) {
            VgHomeTab({
              onCustom: () => {
                this.showCustom = true
              },
              onExpo: () => {
                this.showExpo = true
              },
              onClass: () => {
                this.showClass = true
              },
              onComm: () => {
                this.showComm = true
              }
            })
          } else if (this.currentTab === 1) {
            VgGlassTab()
          } else if (this.currentTab === 2) {
            VgWorkTab()
          } else if (this.currentTab === 3) {
            VgToolTab()
          } else if (this.currentTab === 4) {
            VgCourseTab()
          } else {
            VgMineTab()
          }
        }
        .layoutWeight(1)
        .width('100%')

        Row() {
          ForEach(VG_TABS, (t: VgTab, i: number) => {
            Column({ space: 3 }) {
              Text(t.icon)
                .fontSize(20)
                .fontColor(this.currentTab === i ? VG.primary : VG.sub)
              Text(t.name)
                .fontSize(10)
                .fontColor(this.currentTab === i ? VG.primary : VG.sub)
                .fontWeight(this.currentTab === i ? FontWeight.Bold : FontWeight.Normal)
            }
            .alignItems(HorizontalAlign.Center)
            .onClick(() => {
              this.currentTab = i
            })
          }, (t: VgTab) => t.name)
        }
        .width('100%')
        .padding({ top: 8, bottom: 10 })
        .backgroundColor(VG.card)
        .justifyContent(FlexAlign.SpaceAround)
        .alignItems(VerticalAlign.Center)
      }
      .width('100%')
      .height('100%')
      .backgroundColor(VG.bg)

      if (this.showCustom) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('定制花窗')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor(VG.text)
            Text('告诉我们你想要的彩窗图案')
              .fontSize(12)
              .fontColor(VG.sub)
              .margin({ top: 6 })

            Column({ space: 10 }) {
              Column() {
                Text('作品风格').fontSize(13).fontColor(VG.text).fontWeight(FontWeight.Medium)
                Row({ space: 8 }) {
                  Text('哥特').fontSize(12).fontColor(VG.card).backgroundColor(VG_RUBY).padding({ left: 12, right: 12, top: 6, bottom: 6 }).borderRadius(12)
                  Text('蒂芙尼').fontSize(12).fontColor(VG.card).backgroundColor(VG_AMBER).padding({ left: 12, right: 12, top: 6, bottom: 6 }).borderRadius(12)
                  Text('现代').fontSize(12).fontColor(VG.card).backgroundColor(VG_COBALT).padding({ left: 12, right: 12, top: 6, bottom: 6 }).borderRadius(12)
                  Text('新艺术').fontSize(12).fontColor(VG.sub).backgroundColor(VG.bg).padding({ left: 12, right: 12, top: 6, bottom: 6 }).borderRadius(12)
                }
                .margin({ top: 8 })
              }
              .width('100%')
              .alignItems(HorizontalAlign.Start)

              Column() {
                Text('尺寸(cm)').fontSize(13).fontColor(VG.text).fontWeight(FontWeight.Medium)
                Row({ space: 10 }) {
                  Column() {
                    TextInput({ placeholder: '宽' })
                      .fontSize(13)
                      .height(36)
                      .backgroundColor(VG.bg)
                      .padding({ left: 0 })
                  }
                  .width(72)
                  .borderRadius(10)
                  .backgroundColor(VG.bg)
                  .padding({ left: 10 })

                  Text('×').fontSize(14).fontColor(VG.sub)

                  Column() {
                    TextInput({ placeholder: '高' })
                      .fontSize(13)
                      .height(36)
                      .backgroundColor(VG.bg)
                      .padding({ left: 0 })
                  }
                  .width(72)
                  .borderRadius(10)
                  .backgroundColor(VG.bg)
                  .padding({ left: 10 })
                }
                .margin({ top: 8 })
              }
              .width('100%')
              .alignItems(HorizontalAlign.Start)

              Column() {
                Text('玻璃色系').fontSize(13).fontColor(VG.text).fontWeight(FontWeight.Medium)
                Row({ space: 8 }) {
                  Column().width(24).height(24).borderRadius(12).backgroundColor(VG_COBALT).border({ width: 2, color: VG.line })
                  Column().width(24).height(24).borderRadius(12).backgroundColor(VG_RUBY).border({ width: 2, color: VG.line })
                  Column().width(24).height(24).borderRadius(12).backgroundColor(VG_AMBER).border({ width: 2, color: VG.line })
                  Column().width(24).height(24).borderRadius(12).backgroundColor(VG_EMERALD).border({ width: 2, color: VG.line })
                  Column().width(24).height(24).borderRadius(12).backgroundColor('#7C3AED').border({ width: 2, color: VG.line })
                  Text('可多选').fontSize(10).fontColor(VG.sub).margin({ left: 4 })
                }
                .margin({ top: 8 })
              }
              .width('100%')
              .alignItems(HorizontalAlign.Start)

              Column() {
                Text('参考图片').fontSize(13).fontColor(VG.text).fontWeight(FontWeight.Medium)
                Row({ space: 8 }) {
                  Column() {
                    Text('+').fontSize(20).fontColor(VG.sub)
                  }
                  .width(56)
                  .height(56)
                  .borderRadius(10)
                  .backgroundColor(VG.bg)
                  .justifyContent(FlexAlign.Center)
                  .alignItems(HorizontalAlign.Center)

                  Text('上传手稿或参考图,匠人 24h 内评估报价')
                    .fontSize(10)
                    .fontColor(VG.sub)
                    .margin({ left: 4 })
                    .layoutWeight(1)
                }
                .margin({ top: 8 })
              }
              .width('100%')
              .alignItems(HorizontalAlign.Start)

              Row({ space: 10 }) {
                Text('取消')
                  .fontSize(14)
                  .fontColor(VG.sub)
                  .layoutWeight(1)
                  .textAlign(TextAlign.Center)
                  .padding({ top: 10, bottom: 10 })
                  .borderRadius(20)
                  .backgroundColor(VG.bg)
                  .onClick(() => {
                    this.showCustom = false
                  })
                Text('提交定制')
                  .fontSize(14)
                  .fontColor('#FFFFFF')
                  .layoutWeight(1)
                  .textAlign(TextAlign.Center)
                  .padding({ top: 10, bottom: 10 })
                  .borderRadius(20)
                  .backgroundColor(VG.primary)
                  .onClick(() => {
                    this.showCustom = false
                  })
              }
              .width('100%')
              .margin({ top: 18 })
            }
            .width('100%')
            .margin({ top: 16 })
          }
          .width('86%')
          .constraintSize({ maxHeight: '80%' })
          .borderRadius(16)
          .backgroundColor(VG.card)
          .padding(20)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showExpo) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('光之巡展')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor(VG.text)
            Text('2026 彩绘玻璃艺术展 · 上海站')
              .fontSize(12)
              .fontColor(VG.sub)
              .margin({ top: 6 })

            Column({ space: 10 }) {
              Row() {
                Text('展期').fontSize(12).fontColor(VG.sub)
                Text('09.12 - 10.20').fontSize(12).fontColor(VG.text).fontWeight(FontWeight.Medium)
              }
              .width('100%')
              .padding(12)
              .borderRadius(10)
              .backgroundColor(VG.bg)
              .justifyContent(FlexAlign.SpaceBetween)

              Row() {
                Text('场馆').fontSize(12).fontColor(VG.sub)
                Text('西岸穹顶艺术中心').fontSize(12).fontColor(VG.text).fontWeight(FontWeight.Medium)
              }
              .width('100%')
              .padding(12)
              .borderRadius(10)
              .backgroundColor(VG.bg)
              .justifyContent(FlexAlign.SpaceBetween)

              Row() {
                Text('展品').fontSize(12).fontColor(VG.sub)
                Text('128 件 · 含 6 件百年古董花窗').fontSize(12).fontColor(VG.text).fontWeight(FontWeight.Medium)
              }
              .width('100%')
              .padding(12)
              .borderRadius(10)
              .backgroundColor(VG.bg)
              .justifyContent(FlexAlign.SpaceBetween)

              Row() {
                Text('早鸟票').fontSize(12).fontColor(VG.sub)
                Text('¥68(原价 ¥98)').fontSize(12).fontColor(VG_RUBY).fontWeight(FontWeight.Bold)
              }
              .width('100%')
              .padding(12)
              .borderRadius(10)
              .backgroundColor(VG.bg)
              .justifyContent(FlexAlign.SpaceBetween)
            }
            .width('100%')
            .margin({ top: 14 })

            Row({ space: 10 }) {
              Text('关闭')
                .fontSize(14)
                .fontColor(VG.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(20)
                .backgroundColor(VG.bg)
                .onClick(() => {
                  this.showExpo = false
                })
              Text('抢早鸟票')
                .fontSize(14)
                .fontColor('#FFFFFF')
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(20)
                .backgroundColor(VG_RUBY)
                .onClick(() => {
                  this.showExpo = false
                })
            }
            .width('100%')
            .margin({ top: 18 })
          }
          .width('82%')
          .constraintSize({ maxHeight: '75%' })
          .borderRadius(16)
          .backgroundColor(VG.card)
          .padding(20)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showClass) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('工坊课程')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor(VG.text)
            Text('零基础也能做出第一盏 Tiffany 灯')
              .fontSize(12)
              .fontColor(VG.sub)
              .margin({ top: 6 })

            Column({ space: 12 }) {
              Row({ space: 12 }) {
                Column() {
                  Text('Ⅰ').fontSize(16).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
                }
                .width(44)
                .height(44)
                .borderRadius(22)
                .backgroundColor(VG_AMBER)
                .justifyContent(FlexAlign.Center)
                .alignItems(HorizontalAlign.Center)

                Column() {
                  Text('体验课 1 节').fontSize(14).fontColor(VG.text).fontWeight(FontWeight.Medium)
                  Text('割刀 + 焊锡全流程感受').fontSize(11).fontColor(VG.sub).margin({ top: 2 })
                }
                .alignItems(HorizontalAlign.Start)
                .layoutWeight(1)

                Text('¥198').fontSize(13).fontColor(VG_AMBER).fontWeight(FontWeight.Bold)
              }
              .width('100%')

              Row({ space: 12 }) {
                Column() {
                  Text('Ⅱ').fontSize(16).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
                }
                .width(44)
                .height(44)
                .borderRadius(22)
                .backgroundColor(VG_COBALT)
                .justifyContent(FlexAlign.Center)
                .alignItems(HorizontalAlign.Center)

                Column() {
                  Text('系统课 8 节').fontSize(14).fontColor(VG.text).fontWeight(FontWeight.Medium)
                  Text('独立完成 35cm 挂屏作品').fontSize(11).fontColor(VG.sub).margin({ top: 2 })
                }
                .alignItems(HorizontalAlign.Start)
                .layoutWeight(1)

                Text('¥398').fontSize(13).fontColor(VG_COBALT).fontWeight(FontWeight.Bold)
              }
              .width('100%')

              Row({ space: 12 }) {
                Column() {
                  Text('Ⅲ').fontSize(16).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
                }
                .width(44)
                .height(44)
                .borderRadius(22)
                .backgroundColor(VG_RUBY)
                .justifyContent(FlexAlign.Center)
                .alignItems(HorizontalAlign.Center)

                Column() {
                  Text('匠人班 12 节').fontSize(14).fontColor(VG.text).fontWeight(FontWeight.Medium)
                  Text('蒂芙尼灯罩从图纸到点灯').fontSize(11).fontColor(VG.sub).margin({ top: 2 })
                }
                .alignItems(HorizontalAlign.Start)
                .layoutWeight(1)

                Text('¥1280').fontSize(13).fontColor(VG_RUBY).fontWeight(FontWeight.Bold)
              }
              .width('100%')
            }
            .width('100%')
            .margin({ top: 14 })

            Text('报名后 30 天内可约,材料全包')
              .fontSize(10)
              .fontColor(VG.sub)
              .margin({ top: 12 })

            Row({ space: 10 }) {
              Text('再想想')
                .fontSize(14)
                .fontColor(VG.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(20)
                .backgroundColor(VG.bg)
                .onClick(() => {
                  this.showClass = false
                })
              Text('去报名')
                .fontSize(14)
                .fontColor('#FFFFFF')
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(20)
                .backgroundColor(VG.primary)
                .onClick(() => {
                  this.showClass = false
                })
            }
            .width('100%')
            .margin({ top: 16 })
          }
          .width('82%')
          .constraintSize({ maxHeight: '70%' })
          .borderRadius(16)
          .backgroundColor(VG.card)
          .padding(20)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showComm) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('玻璃社区')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor(VG.text)
            Text('2.4 万名玻璃爱好者在这里交流')
              .fontSize(12)
              .fontColor(VG.sub)
              .margin({ top: 6 })

            Column({ space: 10 }) {
              Row({ space: 10 }) {
                Text('📷').fontSize(18)
                Column() {
                  Text('作品晒图').fontSize(13).fontColor(VG.text).fontWeight(FontWeight.Medium)
                  Text('今日 +86 张').fontSize(10).fontColor(VG.sub).margin({ top: 2 })
                }
                .alignItems(HorizontalAlign.Start)
                .layoutWeight(1)

                Text('›').fontSize(16).fontColor(VG.sub)
              }
              .width('100%')
              .padding(12)
              .borderRadius(10)
              .backgroundColor(VG.bg)
              .alignItems(VerticalAlign.Center)

              Row({ space: 10 }) {
                Text('💡').fontSize(18)
                Column() {
                  Text('技巧问答').fontSize(13).fontColor(VG.text).fontWeight(FontWeight.Medium)
                  Text('焊锡发亮的秘密').fontSize(10).fontColor(VG.sub).margin({ top: 2 })
                }
                .alignItems(HorizontalAlign.Start)
                .layoutWeight(1)

                Text('›').fontSize(16).fontColor(VG.sub)
              }
              .width('100%')
              .padding(12)
              .borderRadius(10)
              .backgroundColor(VG.bg)
              .alignItems(VerticalAlign.Center)

              Row({ space: 10 }) {
                Text('🏷').fontSize(18)
                Column() {
                  Text('玻璃转卖').fontSize(13).fontColor(VG.text).fontWeight(FontWeight.Medium)
                  Text('闲置玻璃板半价出').fontSize(10).fontColor(VG.sub).margin({ top: 2 })
                }
                .alignItems(HorizontalAlign.Start)
                .layoutWeight(1)

                Text('›').fontSize(16).fontColor(VG.sub)
              }
              .width('100%')
              .padding(12)
              .borderRadius(10)
              .backgroundColor(VG.bg)
              .alignItems(VerticalAlign.Center)
            }
            .width('100%')
            .margin({ top: 14 })

            Text('知道了')
              .fontSize(14)
              .fontColor('#FFFFFF')
              .width('100%')
              .textAlign(TextAlign.Center)
              .padding({ top: 10, bottom: 10 })
              .borderRadius(20)
              .backgroundColor(VG.primary)
              .margin({ top: 16 })
              .onClick(() => {
                this.showComm = false
              })
          }
          .width('78%')
          .constraintSize({ maxHeight: '60%' })
          .borderRadius(16)
          .backgroundColor(VG.card)
          .padding(20)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }
    }
    .width('100%')
    .height('100%')
  }
}

@Component
struct VgHomeTab {
  @State wave: number = 0
  @State timer: number = -1
  @State showDetail: boolean = false
  @State selName: string = ''
  onCustom: () => void = () => {}
  onExpo: () => void = () => {}
  onClass: () => void = () => {}
  onComm: () => void = () => {}

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

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

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

  build() {
    Stack() {
      Scroll() {
        Column() {
          Column() {
            Row({ space: 6 }) {
              Column()
                .width(36)
                .height(72)
                .backgroundColor('rgba(255,255,255,0.22)')
                .borderRadius(18)
                .translate({ x: vgRay(this.wave, 0) * 2.2 })
              Column()
                .width(24)
                .height(72)
                .backgroundColor('rgba(217,119,6,0.35)')
                .borderRadius(12)
                .translate({ x: vgRay(this.wave, 1) * 1.8 })
              Column()
                .width(16)
                .height(72)
                .backgroundColor('rgba(30,78,216,0.4)')
                .borderRadius(8)
                .translate({ x: vgRay(this.wave, 2) * 1.4 })
              Column()
                .width(10)
                .height(72)
                .backgroundColor('rgba(190,18,60,0.45)')
                .borderRadius(5)
                .translate({ x: vgRay(this.wave, 3) })
            }
            .width('100%')
            .height(72)
            .justifyContent(FlexAlign.Center)

            Text('让光穿过你家的窗')
              .fontSize(22)
              .fontWeight(FontWeight.Bold)
              .fontColor('#FFFFFF')
              .margin({ top: 14 })
            Text('每片玻璃都在工坊手裁手焊')
              .fontSize(12)
              .fontColor('rgba(255,255,255,0.75)')
              .margin({ top: 6 })
            Row({ space: 8 }) {
              Text('128 年工坊传承')
                .fontSize(10)
                .fontColor('#FFFFFF')
                .padding({ left: 10, right: 10, top: 5, bottom: 5 })
                .borderRadius(10)
                .backgroundColor('rgba(217,119,6,0.85)')
              Text('含古董玻璃')
                .fontSize(10)
                .fontColor('#FFFFFF')
                .padding({ left: 10, right: 10, top: 5, bottom: 5 })
                .borderRadius(10)
                .backgroundColor('rgba(190,18,60,0.85)')
            }
            .margin({ top: 12 })
          }
          .width('100%')
          .padding({ top: 24, bottom: 24, left: 16, right: 16 })
          .borderRadius(18)
          .backgroundColor(VG.primary)
          .margin({ left: 14, right: 14, top: 12 })

          Row({ space: 12 }) {
            Column() {
              Stack() {
                Column()
                  .width(64)
                  .height(64)
                  .borderRadius(32)
                  .backgroundColor(VG.bg)
                  .border({ width: 4, color: VG_COBALT })
                Column()
                  .width(4)
                  .height(24)
                  .backgroundColor(VG.text)
                  .borderRadius(2)
                  .rotate({ angle: (this.wave * 3) % 360 })
              }
              .width(64)
              .height(64)

              Text('拼块仪表')
                .fontSize(11)
                .fontColor(VG.sub)
                .margin({ top: 6 })
              Text('平均 9.6 块/件')
                .fontSize(12)
                .fontColor(VG.text)
                .fontWeight(FontWeight.Medium)
            }
            .layoutWeight(1)
            .padding({ top: 12, bottom: 12 })
            .borderRadius(14)
            .backgroundColor(VG.card)
            .alignItems(HorizontalAlign.Center)

            Column({ space: 8 }) {
              Row({ space: 6 }) {
                Column().width(12).height(12).borderRadius(3).backgroundColor(VG_RUBY)
                Text('宝石红').fontSize(10).fontColor(VG.sub)
              }
              Row({ space: 6 }) {
                Column().width(12).height(12).borderRadius(3).backgroundColor(VG_COBALT)
                Text('钴蓝').fontSize(10).fontColor(VG.sub)
              }
              Row({ space: 6 }) {
                Column().width(12).height(12).borderRadius(3).backgroundColor(VG_AMBER)
                Text('琥珀金').fontSize(10).fontColor(VG.sub)
              }
              Row({ space: 6 }) {
                Column().width(12).height(12).borderRadius(3).backgroundColor(VG_EMERALD)
                Text('翡翠绿').fontSize(10).fontColor(VG.sub)
              }
            }
            .layoutWeight(1)
            .padding({ top: 12, bottom: 12 })
            .borderRadius(14)
            .backgroundColor(VG.card)
            .justifyContent(FlexAlign.Center)
          }
          .width('100%')
          .padding({ left: 14, right: 14 })
          .margin({ top: 12 })

          Row({ space: 10 }) {
            Column() {
              Text('128')
                .fontSize(18)
                .fontWeight(FontWeight.Bold)
                .fontColor(VG.primary)
              Text('在售玻璃')
                .fontSize(10)
                .fontColor(VG.sub)
                .margin({ top: 2 })
            }
            .layoutWeight(1)
            .padding({ top: 12, bottom: 12 })
            .borderRadius(12)
            .backgroundColor(VG.card)
            .alignItems(HorizontalAlign.Center)

            Column() {
              Text('56')
                .fontSize(18)
                .fontWeight(FontWeight.Bold)
                .fontColor(VG_RUBY)
              Text('精品作品')
                .fontSize(10)
                .fontColor(VG.sub)
                .margin({ top: 2 })
            }
            .layoutWeight(1)
            .padding({ top: 12, bottom: 12 })
            .borderRadius(12)
            .backgroundColor(VG.card)
            .alignItems(HorizontalAlign.Center)

            Column() {
              Text('2.4w')
                .fontSize(18)
                .fontWeight(FontWeight.Bold)
                .fontColor(VG_AMBER)
              Text('社区成员')
                .fontSize(10)
                .fontColor(VG.sub)
                .margin({ top: 2 })
            }
            .layoutWeight(1)
            .padding({ top: 12, bottom: 12 })
            .borderRadius(12)
            .backgroundColor(VG.card)
            .alignItems(HorizontalAlign.Center)

            Column() {
              Text('12')
                .fontSize(18)
                .fontWeight(FontWeight.Bold)
                .fontColor(VG_EMERALD)
              Text('驻坊匠人')
                .fontSize(10)
                .fontColor(VG.sub)
                .margin({ top: 2 })
            }
            .layoutWeight(1)
            .padding({ top: 12, bottom: 12 })
            .borderRadius(12)
            .backgroundColor(VG.card)
            .alignItems(HorizontalAlign.Center)
          }
          .width('100%')
          .padding({ left: 14, right: 14 })
          .margin({ top: 12 })

          Text('工坊服务')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor(VG.text)
            .width('100%')
            .padding({ left: 16, right: 16 })
            .margin({ top: 16 })

          Row({ space: 10 }) {
            Column({ space: 6 }) {
              Column() {
                Text('✦').fontSize(20).fontColor('#FFFFFF')
              }
              .width(44)
              .height(44)
              .borderRadius(14)
              .backgroundColor(VG.primary)
              .justifyContent(FlexAlign.Center)
              .alignItems(HorizontalAlign.Center)

              Text('定制花窗')
                .fontSize(11)
                .fontColor(VG.text)
                .fontWeight(FontWeight.Medium)
            }
            .layoutWeight(1)
            .padding({ top: 12, bottom: 12 })
            .borderRadius(14)
            .backgroundColor(VG.card)
            .alignItems(HorizontalAlign.Center)
            .onClick(() => {
              this.onCustom()
            })

            Column({ space: 6 }) {
              Column() {
                Text('♢').fontSize(20).fontColor('#FFFFFF')
              }
              .width(44)
              .height(44)
              .borderRadius(14)
              .backgroundColor(VG_RUBY)
              .justifyContent(FlexAlign.Center)
              .alignItems(HorizontalAlign.Center)

              Text('光之巡展')
                .fontSize(11)
                .fontColor(VG.text)
                .fontWeight(FontWeight.Medium)
            }
            .layoutWeight(1)
            .padding({ top: 12, bottom: 12 })
            .borderRadius(14)
            .backgroundColor(VG.card)
            .alignItems(HorizontalAlign.Center)
            .onClick(() => {
              this.onExpo()
            })

            Column({ space: 6 }) {
              Column() {
                Text('✧').fontSize(20).fontColor('#FFFFFF')
              }
              .width(44)
              .height(44)
              .borderRadius(14)
              .backgroundColor(VG_AMBER)
              .justifyContent(FlexAlign.Center)
              .alignItems(HorizontalAlign.Center)

              Text('工坊课程')
                .fontSize(11)
                .fontColor(VG.text)
                .fontWeight(FontWeight.Medium)
            }
            .layoutWeight(1)
            .padding({ top: 12, bottom: 12 })
            .borderRadius(14)
            .backgroundColor(VG.card)
            .alignItems(HorizontalAlign.Center)
            .onClick(() => {
              this.onClass()
            })

            Column({ space: 6 }) {
              Column() {
                Text('◉').fontSize(20).fontColor('#FFFFFF')
              }
              .width(44)
              .height(44)
              .borderRadius(14)
              .backgroundColor(VG_EMERALD)
              .justifyContent(FlexAlign.Center)
              .alignItems(HorizontalAlign.Center)

              Text('玻璃社区')
                .fontSize(11)
                .fontColor(VG.text)
                .fontWeight(FontWeight.Medium)
            }
            .layoutWeight(1)
            .padding({ top: 12, bottom: 12 })
            .borderRadius(14)
            .backgroundColor(VG.card)
            .alignItems(HorizontalAlign.Center)
            .onClick(() => {
              this.onComm()
            })
          }
          .width('100%')
          .padding({ left: 14, right: 14 })
          .margin({ top: 10 })

          Text('玻璃热销榜')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor(VG.text)
            .width('100%')
            .padding({ left: 16, right: 16 })
            .margin({ top: 18 })

          Column({ space: 10 }) {
            ForEach(vgSoldRank(), (g: VgGlass, i: number) => {
              Row({ space: 12 }) {
                Text((i + 1).toString())
                  .fontSize(14)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(i < 3 ? '#FFFFFF' : VG.sub)
                  .width(26)
                  .height(26)
                  .textAlign(TextAlign.Center)
                  .borderRadius(13)
                  .backgroundColor(i === 0 ? VG_RUBY : (i === 1 ? VG_AMBER : (i === 2 ? VG_COBALT : VG.bg)))

                Column()
                  .width(44)
                  .height(44)
                  .borderRadius(10)
                  .backgroundColor(vgGlassColor(g.colorName))
                  .opacity(vgBlink(this.wave) === 0 ? 1 : 0.88)

                Column() {
                  Text(g.name)
                    .fontSize(13)
                    .fontWeight(FontWeight.Medium)
                    .fontColor(VG.text)
                    .maxLines(1)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })
                  Text(g.texture + '纹 · ' + g.thickness + 'mm · 已售 ' + g.sold)
                    .fontSize(10)
                    .fontColor(VG.sub)
                    .margin({ top: 3 })
                }
                .alignItems(HorizontalAlign.Start)
                .layoutWeight(1)

                Text('¥' + g.price)
                  .fontSize(14)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(VG_RUBY)
              }
              .width('100%')
              .padding(12)
              .borderRadius(12)
              .backgroundColor(VG.card)
              .alignItems(VerticalAlign.Center)
              .onClick(() => {
                this.selName = g.name
                this.showDetail = true
              })
            }, (g: VgGlass) => g.name)
          }
          .width('100%')
          .padding({ left: 14, right: 14 })
          .margin({ top: 10 })

          Text('驻坊匠人')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor(VG.text)
            .width('100%')
            .padding({ left: 16, right: 16 })
            .margin({ top: 18 })

          Scroll() {
            Row({ space: 12 }) {
              ForEach(VG_WORKS, (wk: VgWork, i: number) => {
                Column({ space: 6 }) {
                  Column() {
                    Text(wk.style)
                      .fontSize(16)
                      .fontWeight(FontWeight.Bold)
                      .fontColor('#FFFFFF')
                  }
                  .width(96)
                  .height(96)
                  .borderRadius(14)
                  .backgroundColor(vgStyleColor(wk.style))
                  .justifyContent(FlexAlign.Center)
                  .alignItems(HorizontalAlign.Center)
                  .translate({ y: vgFloatY(this.wave, i) })

                  Text(wk.name)
                    .fontSize(11)
                    .fontColor(VG.text)
                    .fontWeight(FontWeight.Medium)
                    .maxLines(1)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })
                  Text(wk.size + ' · ¥' + wk.price)
                    .fontSize(10)
                    .fontColor(VG.sub)
                }
                .width(100)
              }, (wk: VgWork) => wk.name)
            }
          }
          .scrollable(ScrollDirection.Horizontal)
          .width('100%')
          .margin({ top: 10, left: 14 })
          .scrollBar(BarState.Off)

          Column()
            .width('100%')
            .height(20)
        }
        .width('100%')
      }
      .scrollBar(BarState.Off)
      .width('100%')
      .height('100%')
      .align(Alignment.Top)
      .edgeEffect(EdgeEffect.Spring)

      if (this.showDetail) {
        Stack() {
          this.modalOverlay()
          Column() {
            Row({ space: 12 }) {
              Column()
                .width(56)
                .height(56)
                .borderRadius(12)
                .backgroundColor(vgGlassColor(vgGlassByName(this.selName).colorName))

              Column() {
                Text(vgGlassByName(this.selName).name)
                  .fontSize(16)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(VG.text)
                Text(vgGlassByName(this.selName).tag + ' · ' + vgGlassByName(this.selName).texture + '纹')
                  .fontSize(11)
                  .fontColor(VG.sub)
                  .margin({ top: 4 })
              }
              .alignItems(HorizontalAlign.Start)
              .layoutWeight(1)

              Text('¥' + vgGlassByName(this.selName).price + '/片')
                .fontSize(15)
                .fontWeight(FontWeight.Bold)
                .fontColor(VG_RUBY)
            }
            .width('100%')

            Column({ space: 8 }) {
              Text('透光率 ' + vgGlassByName(this.selName).trans + '%')
                .fontSize(11)
                .fontColor(VG.sub)
                .width('100%')
              Stack({ alignContent: Alignment.Start }) {
                Row()
                  .width('100%')
                  .height(8)
                  .borderRadius(4)
                  .backgroundColor(VG.bg)
                Row()
                  .width(vgTransW(vgGlassByName(this.selName).trans) + '%')
                  .height(8)
                  .borderRadius(4)
                  .backgroundColor(VG_AMBER)
              }
              .width('100%')

              Text('厚度 ' + vgGlassByName(this.selName).thickness + 'mm')
                .fontSize(11)
                .fontColor(VG.sub)
                .width('100%')
                .margin({ top: 6 })
              Stack({ alignContent: Alignment.Start }) {
                Row()
                  .width('100%')
                  .height(8)
                  .borderRadius(4)
                  .backgroundColor(VG.bg)
                Row()
                  .width(vgGlassByName(this.selName).thickness * 18 + '%')
                  .height(8)
                  .borderRadius(4)
                  .backgroundColor(VG_COBALT)
              }
              .width('100%')
            }
            .width('100%')
            .margin({ top: 16 })
            .padding(12)
            .borderRadius(12)
            .backgroundColor(VG.bg)

            Row({ space: 8 }) {
              Text('库存 ' + vgGlassByName(this.selName).stock + ' 片')
                .fontSize(11)
                .fontColor(VG.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 8, bottom: 8 })
                .borderRadius(10)
                .backgroundColor(VG.bg)
              Text('已售 ' + vgGlassByName(this.selName).sold)
                .fontSize(11)
                .fontColor(VG.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 8, bottom: 8 })
                .borderRadius(10)
                .backgroundColor(VG.bg)
            }
            .width('100%')
            .margin({ top: 10 })

            Row({ space: 10 }) {
              Text('关闭')
                .fontSize(14)
                .fontColor(VG.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(20)
                .backgroundColor(VG.bg)
                .onClick(() => {
                  this.showDetail = false
                })
              Text('加入定制单')
                .fontSize(14)
                .fontColor('#FFFFFF')
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(20)
                .backgroundColor(VG.primary)
                .onClick(() => {
                  this.showDetail = false
                })
            }
            .width('100%')
            .margin({ top: 16 })
          }
          .width('84%')
          .constraintSize({ maxHeight: '70%' })
          .borderRadius(16)
          .backgroundColor(VG.card)
          .padding(20)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }
    }
    .width('100%')
    .height('100%')
  }
}

@Component
struct VgGlassTab {
  @State wave: number = 0
  @State timer: number = -1
  @State filterKey: string = '全部'
  @State items: VgGlass[] = []
  @State showDetail: boolean = false
  @State showAdd: boolean = false
  @State showEdit: boolean = false
  @State showDel: boolean = false
  @State selName: string = ''
  @State inputName: string = ''
  @State inputColor: string = ''
  @State inputPrice: string = ''
  @State editPrice: string = ''

  aboutToAppear() {
    this.items = VG_GLASS.slice()
    this.timer = setInterval(() => {
      this.wave = this.wave + 1
    }, 60)
  }

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

  @Builder
  modalOverlay() {
    Column()
      .width('100%')
      .height('100%')
      .backgroundColor('rgba(36,30,62,0.62)')
      .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({ space: 8 }) {
            Text('玻璃仓库')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor(VG.text)
              .layoutWeight(1)
            Text('共 ' + vgFilterGlass(this.filterKey).length + ' 款')
              .fontSize(11)
              .fontColor(VG.sub)
          }
          .width('100%')
          .padding({ left: 16, right: 16 })
          .margin({ top: 12 })

          Scroll() {
            Row({ space: 8 }) {
              ForEach(['全部', '热销', '进口', '经典', '新品', '手工'], (k: string) => {
                Text(k)
                  .fontSize(12)
                  .fontColor(this.filterKey === k ? '#FFFFFF' : VG.sub)
                  .padding({ left: 16, right: 16, top: 7, bottom: 7 })
                  .borderRadius(16)
                  .backgroundColor(this.filterKey === k ? VG.primary : VG.card)
                  .onClick(() => {
                    this.filterKey = k
                  })
              }, (k: string) => k)
            }
          }
          .scrollable(ScrollDirection.Horizontal)
          .width('100%')
          .margin({ top: 10, left: 16 })
          .scrollBar(BarState.Off)

          Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
            ForEach(vgFilterGlass(this.filterKey), (g: VgGlass, i: number) => {
              Column() {
                Column() {
                  Row({ space: 4 }) {
                    Column()
                      .width(22)
                      .height(22)
                      .borderRadius(6)
                      .backgroundColor('#FFFFFF')
                      .opacity(0.5)
                    Column()
                      .width(14)
                      .height(14)
                      .borderRadius(4)
                      .backgroundColor('#FFFFFF')
                      .opacity(0.7)
                  }
                  .justifyContent(FlexAlign.Center)
                  .alignItems(VerticalAlign.Center)

                  Text(g.name)
                    .fontSize(12)
                    .fontWeight(FontWeight.Bold)
                    .fontColor('#FFFFFF')
                    .margin({ top: 6 })
                    .maxLines(1)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })
                }
                .width('100%')
                .height(86)
                .borderRadius(12)
                .justifyContent(FlexAlign.Center)
                .alignItems(HorizontalAlign.Center)
                .backgroundColor(vgGlassColor(g.colorName))
                .opacity(vgShine(this.wave) === 0 ? 1 : 0.92)

                Column() {
                  Text(g.tag + ' · ' + g.texture + '纹 · ' + g.thickness + 'mm')
                    .fontSize(10)
                    .fontColor(VG.sub)
                    .margin({ top: 8 })
                    .maxLines(1)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })
                  Row() {
                    Text('¥' + g.price)
                      .fontSize(14)
                      .fontWeight(FontWeight.Bold)
                      .fontColor(VG_RUBY)
                    Text('已售' + g.sold)
                      .fontSize(10)
                      .fontColor(VG.sub)
                      .margin({ left: 8 })
                  }
                  .width('100%')
                  .justifyContent(FlexAlign.SpaceBetween)
                  .alignItems(VerticalAlign.Center)
                  .margin({ top: 6 })

                  Row({ space: 6 }) {
                    Text('详情')
                      .fontSize(10)
                      .fontColor(VG.primary)
                      .padding({ left: 10, right: 10, top: 4, bottom: 4 })
                      .borderRadius(10)
                      .backgroundColor(VG.bg)
                      .onClick(() => {
                        this.selName = g.name
                        this.showDetail = true
                      })
                    Text('编辑')
                      .fontSize(10)
                      .fontColor(VG_AMBER)
                      .padding({ left: 10, right: 10, top: 4, bottom: 4 })
                      .borderRadius(10)
                      .backgroundColor(VG.bg)
                      .onClick(() => {
                        this.selName = g.name
                        this.editPrice = vgGlassByName(this.selName).price.toString()
                        this.showEdit = true
                      })
                    Text('删除')
                      .fontSize(10)
                      .fontColor(VG_RUBY)
                      .padding({ left: 10, right: 10, top: 4, bottom: 4 })
                      .borderRadius(10)
                      .backgroundColor(VG.bg)
                      .onClick(() => {
                        this.selName = g.name
                        this.showDel = true
                      })
                  }
                  .margin({ top: 8 })
                }
                .width('100%')
                .alignItems(HorizontalAlign.Start)
              }
              .width('48.5%')
              .padding(10)
              .borderRadius(14)
              .backgroundColor(VG.card)
              .margin({ top: 10 })
            }, (g: VgGlass) => g.name + this.wave)
          }
          .width('100%')
          .padding({ left: 14, right: 14 })
          .margin({ top: 4 })

          Text('✚ 上架新玻璃')
            .fontSize(13)
            .fontColor('#FFFFFF')
            .fontWeight(FontWeight.Medium)
            .width('100%')
            .textAlign(TextAlign.Center)
            .padding({ top: 12, bottom: 12 })
            .borderRadius(14)
            .backgroundColor(VG.primary)
            .margin({ left: 14, right: 14, top: 14 })
            .onClick(() => {
              this.showAdd = true
            })

          Text('透光率对比(%)')
            .fontSize(15)
            .fontWeight(FontWeight.Bold)
            .fontColor(VG.text)
            .width('100%')
            .padding({ left: 16, right: 16 })
            .margin({ top: 20 })

          Column({ space: 8 }) {
            ForEach(vgSoldRank(), (g: VgGlass) => {
              Column({ space: 5 }) {
                Row() {
                  Text(g.name)
                    .fontSize(11)
                    .fontColor(VG.text)
                    .layoutWeight(1)
                  Text(g.trans + '%')
                    .fontSize(11)
                    .fontColor(VG.sub)
                }
                .width('100%')

                Stack({ alignContent: Alignment.Start }) {
                  Row()
                    .width('100%')
                    .height(8)
                    .borderRadius(4)
                    .backgroundColor(VG.bg)
                  Row()
                    .width(vgTransW(g.trans) + '%')
                    .height(8)
                    .borderRadius(4)
                    .backgroundColor(vgGlassColor(g.colorName))
                }
                .width('100%')
              }
              .width('100%')
            }, (g: VgGlass) => g.name)
          }
          .width('100%')
          .padding(14)
          .borderRadius(14)
          .backgroundColor(VG.card)
          .margin({ top: 10, left: 14, right: 14 })

          Text('厚度分布(mm)')
            .fontSize(15)
            .fontWeight(FontWeight.Bold)
            .fontColor(VG.text)
            .width('100%')
            .padding({ left: 16, right: 16 })
            .margin({ top: 20 })

          Row({ space: 14 }) {
            ForEach(VG_GLASS, (g: VgGlass, i: number) => {
              Column({ space: 6 }) {
                Text(g.thickness + '')
                  .fontSize(10)
                  .fontColor(VG.sub)
                Column()
                  .width(16)
                  .height(vgThickH(g.thickness))
                  .borderRadius(6)
                  .backgroundColor(vgGlassColor(g.colorName))
              }
              .alignItems(HorizontalAlign.Center)
            }, (g: VgGlass) => g.name + g.thickness)
          }
          .width('100%')
          .justifyContent(FlexAlign.Center)
          .alignItems(VerticalAlign.Bottom)
          .padding({ top: 20, bottom: 10 })
          .borderRadius(14)
          .backgroundColor(VG.card)
          .margin({ top: 10, left: 14, right: 14 })

          Column()
            .width('100%')
            .height(20)
        }
        .width('100%')
      }
      .scrollBar(BarState.Off)
      .width('100%')
      .height('100%')
      .align(Alignment.Top)
      .edgeEffect(EdgeEffect.Spring)

      if (this.showDetail) {
        Stack() {
          this.modalOverlay()
          Column() {
            Row({ space: 12 }) {
              Column()
                .width(54)
                .height(54)
                .borderRadius(12)
                .backgroundColor(vgGlassColor(vgGlassByName(this.selName).colorName))

              Column() {
                Text(vgGlassByName(this.selName).name)
                  .fontSize(16)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(VG.text)
                Text(vgGlassByName(this.selName).colorName + ' · ' + vgGlassByName(this.selName).texture + '纹')
                  .fontSize(11)
                  .fontColor(VG.sub)
                  .margin({ top: 4 })
              }
              .alignItems(HorizontalAlign.Start)
              .layoutWeight(1)

              Text('¥' + vgGlassByName(this.selName).price)
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor(VG_RUBY)
            }
            .width('100%')

            Row({ space: 8 }) {
              Column() {
                Text(vgGlassByName(this.selName).thickness + 'mm')
                  .fontSize(15)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(VG.primary)
                Text('厚度')
                  .fontSize(10)
                  .fontColor(VG.sub)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .padding({ top: 10, bottom: 10 })
              .borderRadius(12)
              .backgroundColor(VG.bg)
              .alignItems(HorizontalAlign.Center)

              Column() {
                Text(vgGlassByName(this.selName).trans + '%')
                  .fontSize(15)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(VG_AMBER)
                Text('透光率')
                  .fontSize(10)
                  .fontColor(VG.sub)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .padding({ top: 10, bottom: 10 })
              .borderRadius(12)
              .backgroundColor(VG.bg)
              .alignItems(HorizontalAlign.Center)

              Column() {
                Text(vgGlassByName(this.selName).stock + '')
                  .fontSize(15)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(VG_EMERALD)
                Text('库存片数')
                  .fontSize(10)
                  .fontColor(VG.sub)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .padding({ top: 10, bottom: 10 })
              .borderRadius(12)
              .backgroundColor(VG.bg)
              .alignItems(HorizontalAlign.Center)
            }
            .width('100%')
            .margin({ top: 14 })

            Column({ space: 6 }) {
              Text('透光率进度')
                .fontSize(11)
                .fontColor(VG.sub)
                .width('100%')
              Stack({ alignContent: Alignment.Start }) {
                Row()
                  .width('100%')
                  .height(8)
                  .borderRadius(4)
                  .backgroundColor(VG.bg)
                Row()
                  .width(vgTransW(vgGlassByName(this.selName).trans) + '%')
                  .height(8)
                  .borderRadius(4)
                  .backgroundColor(VG_AMBER)
              }
              .width('100%')
            }
            .width('100%')
            .margin({ top: 12 })
            .padding(12)
            .borderRadius(12)
            .backgroundColor(VG.bg)

            Text('关闭')
              .fontSize(14)
              .fontColor('#FFFFFF')
              .width('100%')
              .textAlign(TextAlign.Center)
              .padding({ top: 10, bottom: 10 })
              .borderRadius(20)
              .backgroundColor(VG.primary)
              .margin({ top: 16 })
              .onClick(() => {
                this.showDetail = false
              })
          }
          .width('84%')
          .constraintSize({ maxHeight: '70%' })
          .borderRadius(16)
          .backgroundColor(VG.card)
          .padding(20)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showAdd) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('上架新玻璃')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor(VG.text)
            Text('录入玻璃仓库信息')
              .fontSize(12)
              .fontColor(VG.sub)
              .margin({ top: 6 })

            Column({ space: 12 }) {
              Column() {
                Text('玻璃名称').fontSize(12).fontColor(VG.text).width('100%')
                TextInput({ placeholder: '如:暮云灰玻璃' })
                  .fontSize(13)
                  .height(40)
                  .backgroundColor(VG.bg)
                  .borderRadius(10)
                  .margin({ top: 6 })
                  .onChange((v: string) => {
                    this.inputName = v
                  })
              }
              .width('100%')
              .alignItems(HorizontalAlign.Start)

              Column() {
                Text('色系').fontSize(12).fontColor(VG.text).width('100%')
                Row({ space: 8 }) {
                  TextInput({ placeholder: '如:钴蓝' })
                    .fontSize(13)
                    .height(40)
                    .backgroundColor(VG.bg)
                    .borderRadius(10)
                    .layoutWeight(1)
                    .margin({ top: 6 })
                    .onChange((v: string) => {
                      this.inputColor = v
                    })
                  Column()
                    .width(36)
                    .height(36)
                    .borderRadius(10)
                    .backgroundColor(vgGlassColor(this.inputColor))
                    .margin({ top: 6 })
                }
                .width('100%')
              }
              .width('100%')
              .alignItems(HorizontalAlign.Start)

              Column() {
                Text('单片价格(¥)').fontSize(12).fontColor(VG.text).width('100%')
                TextInput({ placeholder: '如:48' })
                  .fontSize(13)
                  .height(40)
                  .backgroundColor(VG.bg)
                  .borderRadius(10)
                  .margin({ top: 6 })
                  .onChange((v: string) => {
                    this.inputPrice = v
                  })
              }
              .width('100%')
              .alignItems(HorizontalAlign.Start)

              Row({ space: 10 }) {
                Text('取消')
                  .fontSize(14)
                  .fontColor(VG.sub)
                  .layoutWeight(1)
                  .textAlign(TextAlign.Center)
                  .padding({ top: 10, bottom: 10 })
                  .borderRadius(20)
                  .backgroundColor(VG.bg)
                  .onClick(() => {
                    this.showAdd = false
                  })
                Text('确认上架')
                  .fontSize(14)
                  .fontColor('#FFFFFF')
                  .layoutWeight(1)
                  .textAlign(TextAlign.Center)
                  .padding({ top: 10, bottom: 10 })
                  .borderRadius(20)
                  .backgroundColor(VG.primary)
                  .onClick(() => {
                    this.showAdd = false
                  })
              }
              .width('100%')
              .margin({ top: 8 })
            }
            .width('100%')
            .margin({ top: 16 })
          }
          .width('84%')
          .constraintSize({ maxHeight: '78%' })
          .borderRadius(16)
          .backgroundColor(VG.card)
          .padding(20)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showEdit) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('编辑玻璃信息')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor(VG.text)
            Row({ space: 10 }) {
              Column()
                .width(40)
                .height(40)
                .borderRadius(10)
                .backgroundColor(vgGlassColor(vgGlassByName(this.selName).colorName))
              Column() {
                Text(vgGlassByName(this.selName).name)
                  .fontSize(14)
                  .fontWeight(FontWeight.Medium)
                  .fontColor(VG.text)
                Text('当前价 ¥' + vgGlassByName(this.selName).price + ' / 库存 ' + vgGlassByName(this.selName).stock)
                  .fontSize(10)
                  .fontColor(VG.sub)
                  .margin({ top: 3 })
              }
              .alignItems(HorizontalAlign.Start)
              .layoutWeight(1)
            }
            .width('100%')
            .margin({ top: 14 })
            .alignItems(VerticalAlign.Center)

            Column() {
              Text('调整价格(¥)').fontSize(12).fontColor(VG.text).width('100%')
              TextInput({ text: this.editPrice })
                .fontSize(13)
                .height(40)
                .backgroundColor(VG.bg)
                .borderRadius(10)
                .margin({ top: 6 })
                .onChange((v: string) => {
                  this.editPrice = v
                })
            }
            .width('100%')
            .alignItems(HorizontalAlign.Start)
            .margin({ top: 12 })

            Row({ space: 8 }) {
              Text('补货 +20')
                .fontSize(12)
                .fontColor(VG.primary)
                .padding({ left: 14, right: 14, top: 8, bottom: 8 })
                .borderRadius(16)
                .backgroundColor(VG.bg)
              Text('促销 -¥5')
                .fontSize(12)
                .fontColor(VG_AMBER)
                .padding({ left: 14, right: 14, top: 8, bottom: 8 })
                .borderRadius(16)
                .backgroundColor(VG.bg)
            }
            .margin({ top: 12 })

            Row({ space: 10 }) {
              Text('取消')
                .fontSize(14)
                .fontColor(VG.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(20)
                .backgroundColor(VG.bg)
                .onClick(() => {
                  this.showEdit = false
                })
              Text('保存修改')
                .fontSize(14)
                .fontColor('#FFFFFF')
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(20)
                .backgroundColor(VG.primary)
                .onClick(() => {
                  this.showEdit = false
                })
            }
            .width('100%')
            .margin({ top: 18 })
          }
          .width('84%')
          .constraintSize({ maxHeight: '70%' })
          .borderRadius(16)
          .backgroundColor(VG.card)
          .padding(20)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showDel) {
        Stack() {
          this.modalOverlay()
          Column() {
            Column() {
              Text('⚠').fontSize(30).fontColor(VG_RUBY)
            }
            .width(56)
            .height(56)
            .borderRadius(28)
            .backgroundColor('rgba(190,18,60,0.1)')
            .justifyContent(FlexAlign.Center)
            .alignItems(HorizontalAlign.Center)

            Text('确认下架该玻璃?')
              .fontSize(16)
              .fontWeight(FontWeight.Bold)
              .fontColor(VG.text)
              .margin({ top: 12 })
            Text('「' + this.selName + '」将从仓库移除')
              .fontSize(12)
              .fontColor(VG.sub)
              .margin({ top: 6 })
            Text('已售出的订单不受影响')
              .fontSize(11)
              .fontColor(VG.sub)
              .margin({ top: 2 })

            Row({ space: 10 }) {
              Text('再想想')
                .fontSize(14)
                .fontColor(VG.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(20)
                .backgroundColor(VG.bg)
                .onClick(() => {
                  this.showDel = false
                })
              Text('确认下架')
                .fontSize(14)
                .fontColor('#FFFFFF')
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(20)
                .backgroundColor(VG_RUBY)
                .onClick(() => {
                  this.showDel = false
                })
            }
            .width('100%')
            .margin({ top: 18 })
          }
          .width('76%')
          .constraintSize({ maxHeight: '60%' })
          .borderRadius(16)
          .backgroundColor(VG.card)
          .padding(20)
          .alignItems(HorizontalAlign.Center)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }
    }
    .width('100%')
    .height('100%')
  }
}

@Component
struct VgWorkTab {
  @State wave: number = 0
  @State timer: number = -1
  @State showDetail: boolean = false
  @State showAdd: boolean = false
  @State showDel: boolean = false
  @State selName: string = ''
  @State inputName: string = ''
  @State inputStyle: string = '哥特'

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

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

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

  build() {
    Stack() {
      Scroll() {
        Column() {
          Row() {
            Text('匠人作品')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor(VG.text)
              .layoutWeight(1)
            Text('每件孤品')
              .fontSize(11)
              .fontColor(VG.sub)
          }
          .width('100%')
          .padding({ left: 16, right: 16 })
          .margin({ top: 12 })

          Scroll() {
            Row({ space: 12 }) {
              ForEach(VG_WORKS, (wk: VgWork, i: number) => {
                Column() {
                  Column() {
                    Text(wk.style)
                      .fontSize(13)
                      .fontColor('#FFFFFF')
                      .padding({ left: 10, right: 10, top: 4, bottom: 4 })
                      .borderRadius(10)
                      .backgroundColor('rgba(255,255,255,0.25)')
                    Row({ space: 3 }) {
                      Column().width(16).height(16).borderRadius(4).backgroundColor('rgba(255,255,255,0.35)').rotate({ angle: 45 })
                      Column().width(16).height(16).borderRadius(4).backgroundColor('rgba(255,255,255,0.55)').rotate({ angle: 45 })
                      Column().width(16).height(16).borderRadius(4).backgroundColor('rgba(255,255,255,0.75)').rotate({ angle: 45 })
                    }
                    .margin({ top: 10 })
                    .translate({ y: vgFloatY(this.wave, i) })

                    Text('¥' + wk.price)
                      .fontSize(16)
                      .fontWeight(FontWeight.Bold)
                      .fontColor('#FFFFFF')
                      .margin({ top: 10 })
                  }
                  .width(160)
                  .height(130)
                  .borderRadius(14)
                  .justifyContent(FlexAlign.Center)
                  .alignItems(HorizontalAlign.Center)
                  .backgroundColor(vgStyleColor(wk.style))

                  Column() {
                    Text(wk.name)
                      .fontSize(12)
                      .fontWeight(FontWeight.Medium)
                      .fontColor(VG.text)
                      .maxLines(1)
                      .textOverflow({ overflow: TextOverflow.Ellipsis })
                      .margin({ top: 8 })
                    Text(wk.size + ' · ' + wk.panels + ' 块 · 已售 ' + wk.sold)
                      .fontSize(10)
                      .fontColor(VG.sub)
                      .margin({ top: 3 })
                  }
                  .width(160)
                  .alignItems(HorizontalAlign.Start)
                }
                .width(160)
                .padding(10)
                .borderRadius(16)
                .backgroundColor(VG.card)
                .onClick(() => {
                  this.selName = wk.name
                  this.showDetail = true
                })
              }, (wk: VgWork) => wk.name)
            }
          }
          .scrollable(ScrollDirection.Horizontal)
          .width('100%')
          .margin({ top: 10, left: 14 })
          .scrollBar(BarState.Off)

          Text('✚ 发布新作品')
            .fontSize(13)
            .fontColor('#FFFFFF')
            .fontWeight(FontWeight.Medium)
            .width('100%')
            .textAlign(TextAlign.Center)
            .padding({ top: 12, bottom: 12 })
            .borderRadius(14)
            .backgroundColor(VG_RUBY)
            .margin({ left: 14, right: 14, top: 14 })
            .onClick(() => {
              this.showAdd = true
            })

          Text('风格分布')
            .fontSize(15)
            .fontWeight(FontWeight.Bold)
            .fontColor(VG.text)
            .width('100%')
            .padding({ left: 16, right: 16 })
            .margin({ top: 20 })

          Column({ space: 10 }) {
            ForEach(['哥特', '蒂芙尼', '现代', '新艺术', '拜占庭'], (s: string) => {
              Row({ space: 10 }) {
                Column()
                  .width(10)
                  .height(10)
                  .borderRadius(3)
                  .backgroundColor(vgStyleColor(s))
                Text(s)
                  .fontSize(12)
                  .fontColor(VG.text)
                  .width(64)
                Stack({ alignContent: Alignment.Start }) {
                  Row()
                    .width('100%')
                    .height(10)
                    .borderRadius(5)
                    .backgroundColor(VG.bg)
                  Row()
                    .width(vgStyleCount(s) * 30 + '%')
                    .height(10)
                    .borderRadius(5)
                    .backgroundColor(vgStyleColor(s))
                }
                .layoutWeight(1)
              }
              .width('100%')
              .alignItems(VerticalAlign.Center)
            }, (s: string) => s)
          }
          .width('100%')
          .padding(14)
          .borderRadius(14)
          .backgroundColor(VG.card)
          .margin({ top: 10, left: 14, right: 14 })

          Text('拼块数对比(块/件)')
            .fontSize(15)
            .fontWeight(FontWeight.Bold)
            .fontColor(VG.text)
            .width('100%')
            .padding({ left: 16, right: 16 })
            .margin({ top: 20 })

          Row({ space: 10 }) {
            ForEach(VG_WORKS, (wk: VgWork) => {
              Column({ space: 6 }) {
                Text(wk.panels + '')
                  .fontSize(10)
                  .fontColor(VG.sub)
                Column()
                  .width(18)
                  .height(vgPanelW(wk.panels))
                  .borderRadius(6)
                  .backgroundColor(vgStyleColor(wk.style))
                Text(wk.style)
                  .fontSize(8)
                  .fontColor(VG.sub)
                  .maxLines(1)
              }
              .alignItems(HorizontalAlign.Center)
            }, (wk: VgWork) => wk.name + wk.panels)
          }
          .width('100%')
          .justifyContent(FlexAlign.Center)
          .alignItems(VerticalAlign.Bottom)
          .padding({ top: 20, bottom: 10 })
          .borderRadius(14)
          .backgroundColor(VG.card)
          .margin({ top: 10, left: 14, right: 14 })

          Column()
            .width('100%')
            .height(20)
        }
        .width('100%')
      }
      .scrollBar(BarState.Off)
      .width('100%')
      .height('100%')
      .align(Alignment.Top)
      .edgeEffect(EdgeEffect.Spring)

      if (this.showDetail) {
        Stack() {
          this.modalOverlay()
          Column() {
            Column() {
              Text(vgWorkByName(this.selName).style)
                .fontSize(13)
                .fontColor('#FFFFFF')
                .padding({ left: 12, right: 12, top: 5, bottom: 5 })
                .borderRadius(12)
                .backgroundColor('rgba(255,255,255,0.3)')
              Text(vgWorkByName(this.selName).name)
                .fontSize(18)
                .fontWeight(FontWeight.Bold)
                .fontColor('#FFFFFF')
                .margin({ top: 8 })
              Text('¥' + vgWorkByName(this.selName).price + ' · ' + vgWorkByName(this.selName).size)
                .fontSize(12)
                .fontColor('rgba(255,255,255,0.85)')
                .margin({ top: 6 })
            }
            .width('100%')
            .padding({ top: 22, bottom: 22 })
            .borderRadius(14)
            .backgroundColor(vgStyleColor(vgWorkByName(this.selName).style))
            .alignItems(HorizontalAlign.Center)

            Row({ space: 8 }) {
              Column() {
                Text(vgWorkByName(this.selName).panels + '')
                  .fontSize(16)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(VG.primary)
                Text('玻璃拼块')
                  .fontSize(10)
                  .fontColor(VG.sub)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .padding({ top: 10, bottom: 10 })
              .borderRadius(12)
              .backgroundColor(VG.bg)
              .alignItems(HorizontalAlign.Center)

              Column() {
                Text(vgWorkByName(this.selName).colorCount + '')
                  .fontSize(16)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(VG_AMBER)
                Text('用色数')
                  .fontSize(10)
                  .fontColor(VG.sub)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .padding({ top: 10, bottom: 10 })
              .borderRadius(12)
              .backgroundColor(VG.bg)
              .alignItems(HorizontalAlign.Center)

              Column() {
                Text(vgWorkByName(this.selName).sold + '')
                  .fontSize(16)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(VG_EMERALD)
                Text('累计售出')
                  .fontSize(10)
                  .fontColor(VG.sub)
                  .margin({ top: 2 })
              }
              .layoutWeight(1)
              .padding({ top: 10, bottom: 10 })
              .borderRadius(12)
              .backgroundColor(VG.bg)
              .alignItems(HorizontalAlign.Center)
            }
            .width('100%')
            .margin({ top: 12 })

            Column({ space: 6 }) {
              Text('拼块规模')
                .fontSize(11)
                .fontColor(VG.sub)
                .width('100%')
              Stack({ alignContent: Alignment.Start }) {
                Row()
                  .width('100%')
                  .height(8)
                  .borderRadius(4)
                  .backgroundColor(VG.bg)
                Row()
                  .width(vgPanelW(vgWorkByName(this.selName).panels) + '%')
                  .height(8)
                  .borderRadius(4)
                  .backgroundColor(vgStyleColor(vgWorkByName(this.selName).style))
              }
              .width('100%')
            }
            .width('100%')
            .margin({ top: 12 })
            .padding(12)
            .borderRadius(12)
            .backgroundColor(VG.bg)

            Row({ space: 10 }) {
              Text('删除作品')
                .fontSize(13)
                .fontColor(VG_RUBY)
                .padding({ left: 14, right: 14, top: 10, bottom: 10 })
                .borderRadius(20)
                .backgroundColor('rgba(190,18,60,0.08)')
                .onClick(() => {
                  this.showDetail = false
                  this.showDel = true
                })
              Text('立即定制')
                .fontSize(14)
                .fontColor('#FFFFFF')
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(20)
                .backgroundColor(VG.primary)
                .onClick(() => {
                  this.showDetail = false
                })
            }
            .width('100%')
            .margin({ top: 16 })
          }
          .width('86%')
          .constraintSize({ maxHeight: '75%' })
          .borderRadius(16)
          .backgroundColor(VG.card)
          .padding(20)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showAdd) {
        Stack() {
          this.modalOverlay()
          Column() {
            Text('发布新作品')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor(VG.text)
            Text('登记你的花窗 / 灯罩作品')
              .fontSize(12)
              .fontColor(VG.sub)
              .margin({ top: 6 })

            Column({ space: 12 }) {
              Column() {
                Text('作品名称').fontSize(12).fontColor(VG.text).width('100%')
                TextInput({ placeholder: '如:黄昏落日横窗' })
                  .fontSize(13)
                  .height(40)
                  .backgroundColor(VG.bg)
                  .borderRadius(10)
                  .margin({ top: 6 })
                  .onChange((v: string) => {
                    this.inputName = v
                  })
              }
              .width('100%')
              .alignItems(HorizontalAlign.Start)

              Column() {
                Text('作品风格').fontSize(12).fontColor(VG.text).width('100%')
                Row({ space: 8 }) {
                  ForEach(['哥特', '蒂芙尼', '现代', '新艺术', '拜占庭'], (s: string) => {
                    Text(s)
                      .fontSize(12)
                      .fontColor(this.inputStyle === s ? '#FFFFFF' : VG.sub)
                      .padding({ left: 12, right: 12, top: 6, bottom: 6 })
                      .borderRadius(12)
                      .backgroundColor(this.inputStyle === s ? vgStyleColor(s) : VG.bg)
                      .onClick(() => {
                        this.inputStyle = s
                      })
                  }, (s: string) => s + this.inputStyle)
                }
                .margin({ top: 8 })
              }
              .width('100%')
              .alignItems(HorizontalAlign.Start)

              Row({ space: 10 }) {
                Text('取消')
                  .fontSize(14)
                  .fontColor(VG.sub)
                  .layoutWeight(1)
                  .textAlign(TextAlign.Center)
                  .padding({ top: 10, bottom: 10 })
                  .borderRadius(20)
                  .backgroundColor(VG.bg)
                  .onClick(() => {
                    this.showAdd = false
                  })
                Text('提交审核')
                  .fontSize(14)
                  .fontColor('#FFFFFF')
                  .layoutWeight(1)
                  .textAlign(TextAlign.Center)
                  .padding({ top: 10, bottom: 10 })
                  .borderRadius(20)
                  .backgroundColor(VG_RUBY)
                  .onClick(() => {
                    this.showAdd = false
                  })
              }
              .width('100%')
              .margin({ top: 8 })
            }
            .width('100%')
            .margin({ top: 16 })
          }
          .width('84%')
          .constraintSize({ maxHeight: '72%' })
          .borderRadius(16)
          .backgroundColor(VG.card)
          .padding(20)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }

      if (this.showDel) {
        Stack() {
          this.modalOverlay()
          Column() {
            Column() {
              Text('✕').fontSize(24).fontColor(VG_RUBY)
            }
            .width(52)
            .height(52)
            .borderRadius(26)
            .backgroundColor('rgba(190,18,60,0.1)')
            .justifyContent(FlexAlign.Center)
            .alignItems(HorizontalAlign.Center)

            Text('删除这件作品?')
              .fontSize(16)
              .fontWeight(FontWeight.Bold)
              .fontColor(VG.text)
              .margin({ top: 12 })
            Text('「' + this.selName + '」将不可恢复')
              .fontSize(12)
              .fontColor(VG.sub)
              .margin({ top: 6 })
            Text('已下单客户的制作不受影响')
              .fontSize(11)
              .fontColor(VG.sub)
              .margin({ top: 2 })

            Row({ space: 10 }) {
              Text('取消')
                .fontSize(14)
                .fontColor(VG.sub)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(20)
                .backgroundColor(VG.bg)
                .onClick(() => {
                  this.showDel = false
                })
              Text('删除')
                .fontSize(14)
                .fontColor('#FFFFFF')
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderRadius(20)
                .backgroundColor(VG_RUBY)
                .onClick(() => {
                  this.showDel = false
                })
            }
            .width('100%')
            .margin({ top: 18 })
          }
          .width('76%')
          .constraintSize({ maxHeight: '60%' })
          .borderRadius(16)
          .backgroundColor(VG.card)
          .padding(20)
          .alignItems(HorizontalAlign.Center)
          .zIndex(1000)
        }
        .width('100%')
        .height('100%')
      }
    }
    .width('100%')
    .height('100%')
  }
}

@Component
struct VgToolTab {
  @State wave: number = 0
  @State timer: number = -1
  @State showDetail: boolean = false
  @State showAdd: boolean = false
  @State showEdit: boolean = false
  @State selName: string = ''

}


总结

在这里插入图片描述

这款彩绘玻璃艺术工坊应用是四款应用中功能最丰富、技术最有特色的一款。它不仅拥有完整的电商功能(玻璃材料、成品作品、工具耗材),还创新性地引入了课程培训体系,形成了"材料+作品+工具+课程"的一站式服务闭环,为彩绘玻璃爱好者提供了全方位的学习和创作支持。

多色映射系统是应用最具特色的技术亮点。vgGlassColorvgStyleColor两个颜色映射函数,将业务语义(颜色名称、风格名称)与视觉表现(十六进制颜色值)优雅地解耦,既提升了代码的可维护性,又为数据可视化提供了丰富的色彩表现。彩色进度条、风格分布图等特色图表,都是基于这一技术实现的。

Logo

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

更多推荐