## 一、引言:从晨光中开启高效一天 在移动互联网与智能穿戴设备高度普及的今天,“习惯养成“已经从一个模糊的自我管理理念,演变为一个可量化、可追踪、可视化的数字化产品赛道。越来越多的人希望借助手
HarmonyOS 6.1.1街头涂鸦艺术装备喷漆马克笔贴纸电商系统技术实战——基于HarmonyOS API 24暗黑霓虹风UI设计全解析
街头涂鸦文化作为亚文化的重要组成部分,正从小众走向大众视野。从墙绘艺术到潮流周边,涂鸦创作者需要专业的工具装备来实现创意表达。本文将深入剖析一款以暗黑霓虹为视觉风格的街头涂鸦艺术装备应用,从架构设计、动画效果到交互体验,全方位展示HarmonyOS ArkTS在潮流文化领域的技术表现力。
该应用以"GRAFFITI ZONE"为品牌核心,围绕涂鸦创作生态构建了喷漆、马克笔、贴纸、防护装备、个人中心六大模块。采用纯黑背景搭配荧光绿与电粉紫的霓虹配色,营造出强烈的街头艺术氛围。技术上采用组件化分层架构与函数式数据处理,确保代码质量与运行性能。
引言

街头涂鸦艺术自上世纪60年代诞生于费城和纽约以来,已经从城市边缘的亚文化现象发展成为全球性的艺术潮流。在中国,随着年轻一代审美意识的觉醒和潮流文化的兴起,涂鸦艺术正在以更包容的姿态走进公众视野。涂鸦创作者们需要专业的喷漆、马克笔、贴纸等工具来释放创意,而一款专为涂鸦爱好者打造的装备选购应用,能够精准满足这一细分市场的需求。
本应用正是为涂鸦创作者量身打造的专业装备平台,它的视觉设计大胆而前卫——纯黑背景如同深夜的城市街头,荧光绿作为主色象征着喷漆罐喷射出的明亮色彩,电粉紫作为强调色增添了迷幻的霓虹感。整体风格强烈而统一,第一眼就能让用户感受到浓厚的街头文化气息。技术架构上,应用采用模块化组件设计,将喷漆、马克笔、贴纸、防护装备等品类分别封装为独立的Tab组件,每个组件内部实现完整的浏览、筛选、详情查看和数据管理功能。
在动画与交互设计方面,应用更是将涂鸦美学发挥到极致。喷漆扩散动画模拟颜料喷射的瞬间张力,滴落效果重现油漆流淌的视觉记忆,发光呼吸营造霓虹招牌的闪烁感,旋转光效增添动感活力。这些精心设计的微动画不仅提升了视觉体验,更成为品牌识别的重要元素。此外,应用还内置了创作社区功能,让涂鸦爱好者们能够交流作品、分享技巧,构建活跃的创作者社群。
暗黑霓虹主题系统

配色方案设计
应用的主题配色采用经典的暗黑霓虹风格,以纯黑为底,荧光绿为主色,电粉紫为强调色。这种高对比度的配色方案在黑暗背景下产生强烈的视觉冲击力,完美契合街头涂鸦文化的反叛精神与潮流特质。
interface GrTheme {
bg: string
primary: string
accent: string
card: string
text: string
sub: string
line: string
glow: string
dark: string
}
const GR: GrTheme = {
bg: '#0D0D0D',
primary: '#39FF14',
accent: '#FF2D95',
card: '#1A1A1A',
text: '#F0F0F0',
sub: '#8C8C8C',
line: '#2A2A2A',
glow: '#39FF14',
dark: '#080808'
}
暗黑霓虹风格的核心在于"黑得纯粹,亮得耀眼"。背景色使用接近纯黑的#0D0D0D,确保霓虹色的饱和度能够充分展现;主色选用经典的荧光绿#39FF14,这是涂鸦喷漆中最具代表性的颜色之一;强调色选用电粉紫#FF2D95,与荧光绿形成冷暖对比,增强视觉层次。这种配色方案虽然饱和度极高,但通过大面积黑色的平衡,整体视觉并不会显得过于刺眼。
主题系统中的glow字段与primary使用相同色值,这是因为在霓虹风格设计中,发光效果通常就是主色的半透明扩散。dark字段则使用了更深的#080808,用于头部导航栏和底部Tab栏,与稍浅的卡片背景形成层次区分。
数据模型与业务品类

应用的数据模型围绕涂鸦装备的核心品类展开,包括喷漆、马克笔、贴纸、防护装备四大类,每一类都有其独特的属性字段,真实反映了涂鸦装备的专业特性。
interface GrSpray {
name: string
brand: string
color: string
colorHex: string
pressure: number
volume: number
coverage: number
stock: number
tag: string
sold: number
}
interface GrMarker {
name: string
brand: string
tip: string
ink: number
waterproof: number
size: number
stock: number
sold: number
}
interface GrSticker {
name: string
size: string
material: string
count: number
stock: number
sold: number
}
interface GrGuard {
name: string
kind: string
material: string
level: number
stock: number
sold: number
}
喷漆数据是应用中最丰富的品类,包含了品牌、颜色名称、色值、压力、容量、覆盖面积、库存、标签和销量等完整属性。其中colorHex字段存储颜色的十六进制值,使得应用可以直接用真实颜色展示喷漆效果,而不仅仅是文字描述。马克笔数据则关注笔头类型、墨水量、防水性和尺寸等创作者关心的参数。防护装备数据包含防护种类、材质和防护等级,体现了对创作者健康安全的重视。
动画系统:涂鸦美学的动态表达

喷漆扩散与发光效果

应用最具特色的动画是喷漆扩散效果,它模拟了喷漆从喷嘴喷出后逐渐扩散的过程。色点的大小随时间周期性变化,配合透明度的呼吸效果,仿佛颜料正在空气中弥漫开来。
function grSprayR(wave: number, i: number): number {
let base: number = (wave * 2 + i * 37) % 80
return 0.8 + base / 200
}
function grGlow(wave: number, i: number): number {
let base: number = (wave + i * 13) % 100
return 0.4 + Math.abs(Math.sin(base / 15)) * 0.6
}
function grSpin(wave: number, i: number): number {
return (wave * 3 + i * 45) % 360
}
喷漆扩散动画的设计灵感来源于真实的喷漆过程。当按下喷漆罐的喷嘴时,颜料以一定的压力喷出,在空气中形成逐渐扩大的圆形雾状区域。
grSprayR函数通过模运算实现半径的周期性变化,模拟了连续喷射时的脉冲效果。配合grGlow函数实现的透明度变化,进一步增强了喷漆气雾的层次感和真实感。
grSprayR函数计算色点的缩放比例,取值范围在0.8到1.2之间,形成大小交替的脉动效果。grGlow函数基于正弦函数计算透明度,产生呼吸灯般的发光效果。grSpin函数则实现旋转动画,可用于装饰元素的旋转或喷漆罐的转动效果。这些函数都接受索引参数i,通过索引偏移实现同组元素的错峰动画,避免单调感。
滴落与浮动效果

滴落动画模拟了油漆在垂直表面上向下流淌的效果,是涂鸦视觉语言中的经典元素。浮动动画则让元素产生上下漂浮的动感,增强页面的活力。
function grDrip(wave: number, i: number): number {
let base: number = (wave * 2 + i * 29) % 100
return base * 0.25
}
function grFloatY(wave: number, i: number): number {
let base: number = (wave + i * 23) % 100
return Math.sin(base / 16) * 5
}
function grBlink(wave: number): number {
let base: number = wave % 100
return base < 50 ? 1 : 0.4
}
grDrip函数通过线性递增的位移值模拟油漆向下流淌的效果,结合模运算实现循环。grFloatY函数基于正弦函数实现平滑的上下浮动,振幅为5像素。grBlink函数实现了闪烁效果,通过模运算将时间分为亮和暗两个阶段,产生类似霓虹灯闪烁的视觉效果。这些动画函数虽然实现简单,但在涂鸦风格的UI中能够产生强烈的风格化视觉效果。
主应用架构与导航

根组件设计
应用的根组件采用Stack + Column的经典布局结构,黑色背景贯穿整个应用,为霓虹色元素提供了完美的展示舞台。头部导航栏、搜索框、内容区和底部Tab栏自上而下排列,结构清晰明了。
@Entry
@Component
struct GrApp {
@State currentTab: number = 0
@State wave: number = 0
private timer: number = -1
aboutToAppear() {
this.timer = setInterval(() => {
this.wave = (this.wave + 1) % 10000
}, 60)
}
aboutToDisappear() {
if (this.timer !== -1) {
clearInterval(this.timer)
}
}
build() {
Stack() {
Column() {
Row() {
Text('GRAFFITI')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
Text(' ZONE')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
Column().layoutWeight(1)
Text('search')
.fontSize(12)
.fontColor(GR.sub)
.margin({ right: 12 })
Text('cart')
.fontSize(12)
.fontColor(GR.sub)
}
.width('100%')
.height(48)
.padding({ left: 16, right: 16 })
.backgroundColor(GR.dark)
.alignItems(VerticalAlign.Center)
.border({ width: { bottom: 1 }, color: GR.line })
// ... 搜索栏、内容区、Tab栏
}
.width('100%')
.height('100%')
.backgroundColor(GR.bg)
}
}
}
品牌Logo的设计颇具巧思——"GRAFFITI"使用荧光绿色,"ZONE"使用白色,两个词拼接在一起形成鲜明的视觉对比,同时也呼应了喷漆双色喷涂的创作手法。根组件在全局层面维护了一个动画帧计数器wave,这个计数器可以传递给子组件使用,确保全局动画的同步性。
底部导航栏
底部Tab栏采用深色背景配合荧光色选中态,六个Tab分别对应首页、喷漆、马克笔、贴纸、防护和我的页面。图标使用emoji实现,简洁而富有表现力。
const GR_TABS: GrTab[] = [
{ name: '首页', icon: '🏠' },
{ name: '喷漆', icon: '🎨' },
{ name: '马克笔', icon: '✏️' },
{ name: '贴纸', icon: '📎' },
{ name: '防护', icon: '🦺' },
{ name: '我的', icon: '👤' }
]
Tab栏的选中态通过透明度和颜色变化来区分,当前选中的Tab使用荧光绿主色,未选中的Tab使用灰色次级色,透明度从1降低到0.4。这种设计简洁而有效,在深色背景上提供了清晰的导航指引。
功能流程图
喷漆Tab:色卡展示与销量排行
筛选与商品网格
喷漆Tab是应用的核心功能模块,它以横向滚动的标签筛选器 + 双列色卡网格 + 销量排行柱状图的组合布局,全面展示喷漆产品信息。筛选标签支持全部、速干、荧光、高压、夜光、金属等多个维度,帮助用户快速定位所需的喷漆类型。
@Component
struct GrSprayTab {
@State wave: number = 0
@State chip: number = 0
@State sprays: GrSpray[] = []
@State ranked: GrSpray[] = []
@State showDetail: boolean = false
@State showAdd: boolean = false
@State showEdit: boolean = false
@State showDel: boolean = false
@State selIdx: number = 0
private timer: number = -1
aboutToAppear() {
this.sprays = GR_SPRAYS.slice()
this.ranked = grSoldRank().slice(0, 6)
this.timer = setInterval(() => {
this.wave = (this.wave + 1) % 10000
}, 60)
}
// ...
}
组件在初始化时不仅加载了完整的喷漆数据,还预先计算了销量排名前6的商品数据,用于销量排行柱状图的展示。这种预计算策略避免了每次渲染时都进行排序操作,有助于提升性能。动画帧计数器的模值设置为10000,确保动画能够长时间运行而不会出现明显的跳变。
色卡商品卡片设计
喷漆商品的展示采用了色卡式设计,每个商品卡片顶部是一个圆形色点,直接展示喷漆的真实颜色。色点应用了喷漆扩散和发光动画,仿佛正在喷射的颜料,极具视觉冲击力。
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(this.sprays, (s: GrSpray, i: number) => {
Column() {
Column()
.width(48)
.height(48)
.borderRadius(24)
.backgroundColor(s.colorHex)
.scale({ x: grSprayR(this.wave, i), y: grSprayR(this.wave, i) })
.opacity(grGlow(this.wave, i))
.border({ width: 2, color: GR.dark })
Text(s.name)
.fontSize(11)
.fontColor(GR.text)
.margin({ top: 8 })
Text(s.brand + ' · ' + s.color)
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
Row() {
Text('覆盖')
.fontSize(8)
.fontColor(GR.sub)
.width(24)
Stack({ alignContent: Alignment.Start }) {
Row()
.width('100%')
.height(3)
.borderRadius(2)
.backgroundColor(GR.dark)
Row()
.width(grCoverageW(s.coverage))
.height(3)
.borderRadius(2)
.backgroundColor(GR.primary)
}
.layoutWeight(1)
.height(3)
}
.width('100%')
.margin({ top: 6 })
Row() {
Text('压力')
.fontSize(8)
.fontColor(GR.sub)
.width(24)
Stack({ alignContent: Alignment.Start }) {
Row()
.width('100%')
.height(3)
.borderRadius(2)
.backgroundColor(GR.dark)
Row()
.width(grPressureW(s.pressure))
.height(3)
.borderRadius(2)
.backgroundColor(GR.accent)
}
.layoutWeight(1)
.height(3)
}
.width('100%')
.margin({ top: 4 })
Row() {
Text('¥' + (s.volume / 10 + 29))
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
Column().layoutWeight(1)
Text('销' + s.sold)
.fontSize(9)
.fontColor(GR.sub)
}
.width('100%')
.margin({ top: 6 })
Row() {
Text('详情')
.fontSize(9)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 4, bottom: 4 })
.borderRadius(6)
.backgroundColor(GR.dark)
.onClick(() => {
this.selIdx = i
this.showDetail = true
})
Text('编辑')
.fontSize(9)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 4, bottom: 4 })
.borderRadius(6)
.backgroundColor(GR.dark)
.margin({ left: 4 })
.onClick(() => {
this.selIdx = i
this.showEdit = true
})
Text('删除')
.fontSize(9)
.fontColor(GR.accent)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 4, bottom: 4 })
.borderRadius(6)
.backgroundColor(GR.dark)
.margin({ left: 4 })
.onClick(() => {
this.selIdx = i
this.showDel = true
})
}
.width('100%')
.margin({ top: 6 })
}
.width('48.5%')
.padding(12)
.backgroundColor(GR.card)
.borderRadius(14)
.border({ width: 1, color: GR.line })
.margin({ top: 8 })
}, (s: GrSpray) => s.name)
}
.width('92%')
.margin({ top: 6 })
色卡式商品展示是喷漆类电商的最佳实践。相比于文字描述,真实的颜色展示能够让用户更直观地了解产品,大幅提升购物决策效率。在技术实现上,直接使用颜色的十六进制值作为背景色,配合黑色边框增强轮廓,即使在暗黑模式下也能准确呈现颜色效果。同时,喷漆扩散动画的加入让静态的色卡"活"了起来,大大增强了品牌的记忆点。
每个商品卡片中还包含了两条微型进度条,分别展示覆盖面积和喷漆压力两个关键参数。覆盖面积使用荧光绿(主色),压力使用电粉紫(强调色),通过颜色编码让用户一眼就能区分不同参数。这种将数据可视化融入商品卡片的设计,既节省了空间,又提升了信息密度。
销量排行柱状图
喷漆Tab底部展示了销量排行柱状图,通过柱形高度直观对比各款喷漆的销量差异。前三名使用荧光绿色突出显示,其余使用电粉紫色,清晰地划分了第一梯队和第二梯队。
Text('销量排行')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
.margin({ top: 14 })
Row() {
ForEach(this.ranked, (s: GrSpray, i: number) => {
Column() {
Column()
.width(18)
.height(Math.min(s.sold / 410 * 50, 50))
.backgroundColor(i < 3 ? GR.primary : GR.accent)
.borderRadius(4)
Text(s.sold + '')
.fontSize(8)
.fontColor(GR.sub)
.margin({ top: 4 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}, (s: GrSpray) => s.name)
}
.width('92%')
.height(70)
.alignItems(VerticalAlign.Bottom)
.padding({ top: 10, bottom: 4 })
.backgroundColor(GR.card)
.borderRadius(14)
柱状图的高度通过简单的比例计算得出,以销量最高的商品(410件)为基准,最大柱高为50像素。前三名使用主色荧光绿,其他使用强调色电粉紫,这种色彩编码方式让用户能够快速识别热销产品。整个图表区域采用卡片式设计,与上方的商品网格形成视觉上的统一。
首页设计:品牌展示与社区入口
品牌横幅与数据统计
首页顶部是品牌展示区域,大号的品牌名称配合一排动态色点,直观展示了应用的喷漆主题。色点应用了喷漆扩散和发光动画,如同从喷嘴中喷射出的彩色气雾,极具视觉张力。
Column() {
Row() {
Text('GRAFFITI')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
Text(' ZONE')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
Column().layoutWeight(1)
Text('HOT')
.fontSize(9)
.fontColor(GR.dark)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius(4)
.backgroundColor(GR.accent)
}
.width('100%')
Row() {
ForEach(GR_SPRAYS.slice(0, 6), (s: GrSpray, i: number) => {
Column()
.width(28)
.height(28)
.borderRadius(14)
.backgroundColor(s.colorHex)
.scale({ x: grSprayR(this.wave, i), y: grSprayR(this.wave, i) })
.opacity(grGlow(this.wave, i))
.margin({ left: i > 0 ? 6 : 0 })
}, (s: GrSpray) => s.name)
}
.margin({ top: 12 })
Text('1000+ 款色 | 30+ 品牌 | 全球直邮')
.fontSize(10)
.fontColor(GR.sub)
.margin({ top: 8 })
}
.width('92%')
.padding(16)
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.line })
.margin({ top: 8 })
.alignItems(HorizontalAlign.Start)
品牌横幅的右上角有一个"HOT"标签,使用电粉紫背景配黑色文字,非常醒目。横幅下方的六个彩色圆点取自销量前六的喷漆颜色,它们以不同的节奏脉动和发光,形成一道动态的色带,完美诠释了涂鸦文化的多彩与活力。
创作社区弹窗
首页的创作社区入口是应用的社交功能核心,点击后弹出社区加入窗口,展示社区成员数、作品数、本周新作数等关键数据,并提供自我介绍和创作类型选择的入社流程。
if (this.showCommunity) {
Stack() {
this.modalOverlay()
Column() {
Row() {
Text('💬 创作社区')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
.layoutWeight(1)
Text('×')
.fontSize(18)
.fontColor(GR.sub)
.onClick(() => {
this.showCommunity = false
})
}
.width('100%')
.padding({ bottom: 12 })
.border({ width: { bottom: 1 }, color: GR.line })
Row() {
Column() {
Text('2.8万')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
Text('成员')
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('1200+')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.accent)
Text('作品')
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('86')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
Text('本周新作')
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.padding({ top: 10, bottom: 10 })
.backgroundColor(GR.dark)
.borderRadius(12)
.margin({ top: 10 })
// ... 自我介绍、创作类型选择
}
.width('86%')
.constraintSize({ maxHeight: '80%' })
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.line })
.padding(16)
.zIndex(1000)
}
}
社区功能是提升用户粘性的重要手段。对于涂鸦这类创作型应用来说,社区不仅是用户交流作品的场所,更是品牌文化传播的重要阵地。通过设计简洁的入社流程(自我介绍 + 创作类型选择),可以有效降低用户的加入门槛,同时收集基本的用户画像数据,为后续的个性化推荐和社区运营奠定基础。
社区弹窗的数据统计区域采用三栏布局,分别展示成员数、作品数和本周新作数,使用荧光绿和电粉紫交替配色,数据突出而标签弱化,让用户一眼就能看到社区的活跃程度。整体设计延续了应用的暗黑霓虹风格,深色背景配合亮色数据,形成强烈的视觉对比。
技术点对比分析
| 功能模块 | 技术方案 | 设计亮点 | 开发效率 |
|---|---|---|---|
| 主题系统 | 接口约束 + 常量对象 | 暗黑霓虹配色,色值丰富层次分明 | 高,配置即用 |
| 色卡展示 | 背景色 + 扩散缩放动画 | 真实颜色预览,喷漆动效增强记忆点 | 中,需调试动画参数 |
| 参数可视化 | 微型进度条 + 颜色编码 | 高密度信息展示,双参数对比直观 | 高,基础组件组合 |
| 销量排行 | 动态柱高 + Top色彩区分 | 前三名高亮,快速识别热销产品 | 高,简单比例计算 |
| 商品筛选 | 索引映射 + 纯函数过滤 | 多标签维度,筛选逻辑清晰 | 高,数组操作易实现 |
| 弹窗系统 | Stack叠加 + 状态控制 | 半透明遮罩,多弹窗复用遮罩Builder | 高,@Builder复用 |
| 社区功能 | 数据统计 + 表单收集 | 三栏数据展示,入社流程简洁 | 中,需处理用户输入 |
| 全局动画 | 单定时器 + 多函数组合 | 动画同步性好,组合效果丰富 | 中,需精心设计函数 |
安装DevEco Studio程序

选择目标安装目录:

设置环境变量,但是需要重启一下:

新建一个空白模板:

设置API为24的模板项目:
初始化项目,自动下载相关依赖:

完整代码:
// GRAFFITI ZONE 街头涂鸦艺术装备
// GR前缀 暗黑×荧光绿×电粉紫 涂鸦街头风
interface GrTheme {
bg: string
primary: string
accent: string
card: string
text: string
sub: string
line: string
glow: string
dark: string
}
const GR: GrTheme = {
bg: '#0D0D0D',
primary: '#39FF14',
accent: '#FF2D95',
card: '#1A1A1A',
text: '#F0F0F0',
sub: '#8C8C8C',
line: '#2A2A2A',
glow: '#39FF14',
dark: '#080808'
}
interface GrSpray {
name: string
brand: string
color: string
colorHex: string
pressure: number
volume: number
coverage: number
stock: number
tag: string
sold: number
}
interface GrMarker {
name: string
brand: string
tip: string
ink: number
waterproof: number
size: number
stock: number
sold: number
}
interface GrSticker {
name: string
size: string
material: string
count: number
stock: number
sold: number
}
interface GrGuard {
name: string
kind: string
material: string
level: number
stock: number
sold: number
}
interface GrOrder {
id: string
item: string
date: string
amount: number
status: string
}
interface GrCollect {
name: string
brand: string
price: number
date: string
}
interface GrMonth {
month: string
amount: number
}
interface GrTab {
name: string
icon: string
}
const GR_TABS: GrTab[] = [
{ name: '首页', icon: '🏠' },
{ name: '喷漆', icon: '🎨' },
{ name: '马克笔', icon: '✏️' },
{ name: '贴纸', icon: '📎' },
{ name: '防护', icon: '🦺' },
{ name: '我的', icon: '👤' }
]
const GR_SPRAYS: GrSpray[] = [
{ name: 'Hardcore 600ml', brand: 'MONTANA', color: '纯白', colorHex: '#F0F0F0', pressure: 8, volume: 600, coverage: 12, stock: 45, tag: '速干', sold: 320 },
{ name: 'MTN94 400ml', brand: 'MONTANA', color: '荧光绿', colorHex: '#39FF14', pressure: 6, volume: 400, coverage: 10, stock: 38, tag: '低压', sold: 280 },
{ name: 'Krylon Max', brand: 'KRYLON', color: '荧光粉', colorHex: '#FF2D95', pressure: 9, volume: 400, coverage: 9, stock: 22, tag: '荧光', sold: 195 },
{ name: 'Black 500ml', brand: 'MONTANA', color: '深黑', colorHex: '#0A0A0A', pressure: 10, volume: 500, coverage: 11, stock: 50, tag: '高压', sold: 410 },
{ name: 'Flame Orange', brand: 'FLAME', color: '焦橙', colorHex: '#FF6B1A', pressure: 7, volume: 400, coverage: 8, stock: 30, tag: '暖色', sold: 165 },
{ name: 'Loop Glow', brand: 'LOOP', color: '电青', colorHex: '#00F5D4', pressure: 5, volume: 400, coverage: 7, stock: 18, tag: '夜光', sold: 120 },
{ name: 'Mega 600ml', brand: 'MONTANA', color: '金黄', colorHex: '#FFD700', pressure: 8, volume: 600, coverage: 12, stock: 35, tag: '大容量', sold: 240 },
{ name: 'Hardcore2 400ml', brand: 'MONTANA', color: '紫罗兰', colorHex: '#8B5CF6', pressure: 7, volume: 400, coverage: 9, stock: 28, tag: '纯色', sold: 155 },
{ name: 'Kobra 300ml', brand: 'KOBRA', color: '湖蓝', colorHex: '#1B9AAA', pressure: 6, volume: 300, coverage: 6, stock: 42, tag: '精细', sold: 130 },
{ name: 'Banner 500ml', brand: 'BANNER', color: '银灰', colorHex: '#C0C0C0', pressure: 9, volume: 500, coverage: 10, stock: 15, tag: '金属', sold: 89 }
]
const GR_MARKERS: GrMarker[] = [
{ name: 'Sketch 0.3', brand: 'COPIC', tip: '软头', ink: 2.8, waterproof: 1, size: 3, stock: 60, sold: 450 },
{ name: 'One4All 1mm', brand: 'MOLOTOW', tip: '圆头', ink: 4, waterproof: 1, size: 1, stock: 45, sold: 380 },
{ name: 'PC-1MR 0.7', brand: 'POSCA', tip: '针头', ink: 1.5, waterproof: 0, size: 0.7, stock: 55, sold: 520 },
{ name: 'PC-5M 1.8', brand: 'POSCA', tip: '圆头', ink: 2.5, waterproof: 0, size: 1.8, stock: 70, sold: 610 },
{ name: 'Microperm 0.4', brand: 'SAKURA', tip: '针头', ink: 1.2, waterproof: 1, size: 0.4, stock: 40, sold: 290 },
{ name: 'Marker 2mm', brand: 'MONTANA', tip: '方头', ink: 5, waterproof: 1, size: 2, stock: 35, sold: 210 },
{ name: 'K-42 1mm', brand: 'KRINK', tip: '圆头', ink: 7, waterproof: 1, size: 1, stock: 25, sold: 180 },
{ name: 'Ciao 0.5', brand: 'COPIC', tip: '软头', ink: 2, waterproof: 0, size: 0.5, stock: 50, sold: 340 }
]
const GR_STICKERS: GrSticker[] = [
{ name: '街头涂鸦贴', size: '10x15cm', material: 'PVC', count: 50, stock: 80, sold: 200 },
{ name: '荧光字母贴', size: '5x5cm', material: 'PVC', count: 100, stock: 120, sold: 350 },
{ name: '喷漆罐造型', size: '8x12cm', material: '全息', count: 30, stock: 45, sold: 180 },
{ name: '复古标签贴', size: '4x6cm', material: '纸质', count: 200, stock: 150, sold: 280 },
{ name: '荧光色块贴', size: '3x3cm', material: 'PVC', count: 150, stock: 100, sold: 310 },
{ name: '涂鸦人物贴', size: '15x20cm', material: '全息', count: 20, stock: 30, sold: 120 },
{ name: '城市天际线', size: '20x30cm', material: 'PVC', count: 15, stock: 25, sold: 85 },
{ name: '迷你印章贴', size: '2x2cm', material: 'PVC', count: 300, stock: 200, sold: 420 }
]
const GR_GUARDS: GrGuard[] = [
{ name: '防毒面具Pro', kind: '呼吸防护', material: '硅胶活性炭', level: 5, stock: 35, sold: 180 },
{ name: '护目镜防雾', kind: '眼部防护', material: 'PC镜片', level: 3, stock: 50, sold: 220 },
{ name: '手套防滑', kind: '手部防护', material: '丁腈橡胶', level: 2, stock: 80, sold: 350 },
{ name: '防尘罩衣', kind: '全身防护', material: '无纺布', level: 2, stock: 40, sold: 120 },
{ name: '耳塞降噪', kind: '听力防护', material: '泡棉', level: 3, stock: 100, sold: 280 },
{ name: '安全鞋头', kind: '脚部防护', material: '钢头', level: 4, stock: 25, sold: 95 }
]
const GR_ORDERS: GrOrder[] = [
{ id: 'GR250801', item: 'Montana Hardcore 600ml', date: '08-01', amount: 89, status: '已签收' },
{ id: 'GR250728', item: 'Copic Sketch 0.3', date: '07-28', amount: 45, status: '已发货' },
{ id: 'GR250725', item: '防毒面具Pro', date: '07-25', amount: 298, status: '已签收' },
{ id: 'GR250720', item: '荧光字母贴x100', date: '07-20', amount: 35, status: '已签收' },
{ id: 'GR250715', item: 'MTN94 荧光绿', date: '07-15', amount: 78, status: '已签收' },
{ id: 'GR250710', item: 'Posca PC-5M 1.8', date: '07-10', amount: 28, status: '已签收' }
]
const GR_COLLECTS: GrCollect[] = [
{ name: 'Black 深黑', brand: 'MONTANA', price: 89, date: '08-01' },
{ name: 'Max 荧光粉', brand: 'KRYLON', price: 128, date: '07-28' },
{ name: 'Glow 电青', brand: 'LOOP', price: 158, date: '07-20' },
{ name: 'Sketch 0.3', brand: 'COPIC', price: 45, date: '07-15' },
{ name: 'K-42 1mm', brand: 'KRINK', price: 198, date: '07-10' },
{ name: 'Banner 银灰', brand: 'BANNER', price: 168, date: '07-05' }
]
const GR_MONTHS: GrMonth[] = [
{ month: '3月', amount: 320 },
{ month: '4月', amount: 450 },
{ month: '5月', amount: 280 },
{ month: '6月', amount: 520 },
{ month: '7月', amount: 380 },
{ month: '8月', amount: 610 }
]
function grSprayR(wave: number, i: number): number {
let base: number = (wave * 2 + i * 37) % 80
return 0.8 + base / 200
}
function grGlow(wave: number, i: number): number {
let base: number = (wave + i * 13) % 100
return 0.4 + Math.abs(Math.sin(base / 15)) * 0.6
}
function grSpin(wave: number, i: number): number {
return (wave * 3 + i * 45) % 360
}
function grDrip(wave: number, i: number): number {
let base: number = (wave * 2 + i * 29) % 100
return base * 0.25
}
function grFloatY(wave: number, i: number): number {
let base: number = (wave + i * 23) % 100
return Math.sin(base / 16) * 5
}
function grBlink(wave: number): number {
let base: number = wave % 100
return base < 50 ? 1 : 0.4
}
function grFilterSprays(idx: number): GrSpray[] {
let r: GrSpray[] = []
let tags: string[] = ['', '速干', '荧光', '高压', '夜光', '金属']
let tag: string = idx < tags.length ? tags[idx] : ''
if (idx === 0) {
r = GR_SPRAYS.slice()
} else {
for (let i = 0; i < GR_SPRAYS.length; i++) {
if (GR_SPRAYS[i].tag === tag) {
r.push(GR_SPRAYS[i])
}
}
}
if (r.length === 0) {
r = GR_SPRAYS.slice()
}
return r
}
function grSoldRank(): GrSpray[] {
let r: GrSpray[] = GR_SPRAYS.slice()
r.sort((a: GrSpray, b: GrSpray) => b.sold - a.sold)
return r
}
function grCoverageW(c: number): string {
let pct: number = Math.min(c / 15 * 100, 100)
return pct + '%'
}
function grPressureW(p: number): string {
return (p * 10) + '%'
}
function grInkW(ink: number): string {
let pct: number = Math.min(ink / 8 * 100, 100)
return pct + '%'
}
function grStickerCountW(c: number): string {
let pct: number = Math.min(c / 300 * 100, 100)
return pct + '%'
}
function grGuardLevelW(l: number): string {
return (l * 20) + '%'
}
function grMonthH(i: number): number {
let amounts: number[] = [320, 450, 280, 520, 380, 610]
let max: number = 610
return amounts[i] / max * 60
}
function grTopN(i: number): string {
let labels: string[] = ['No.1', 'No.2', 'No.3', 'No.4', 'No.5', 'No.6', 'No.7', 'No.8', 'No.9', 'No.10']
return i < labels.length ? labels[i] : ''
}
@Entry
@Component
struct GrApp {
@State currentTab: number = 0
@State wave: number = 0
private timer: number = -1
aboutToAppear() {
this.timer = setInterval(() => {
this.wave = (this.wave + 1) % 10000
}, 60)
}
aboutToDisappear() {
if (this.timer !== -1) {
clearInterval(this.timer)
}
}
build() {
Stack() {
Column() {
Row() {
Text('GRAFFITI')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
Text(' ZONE')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
Column().layoutWeight(1)
Text('search')
.fontSize(12)
.fontColor(GR.sub)
.margin({ right: 12 })
Text('cart')
.fontSize(12)
.fontColor(GR.sub)
}
.width('100%')
.height(48)
.padding({ left: 16, right: 16 })
.backgroundColor(GR.dark)
.alignItems(VerticalAlign.Center)
.border({ width: { bottom: 1 }, color: GR.line })
Row() {
Text('search')
.fontSize(11)
.fontColor(GR.sub)
.margin({ right: 8 })
Text('搜索喷漆 / 马克笔 / 贴纸...')
.fontSize(12)
.fontColor(GR.sub)
.layoutWeight(1)
}
.width('92%')
.height(34)
.margin({ top: 6, bottom: 6 })
.padding({ left: 12, right: 12 })
.backgroundColor(GR.card)
.borderRadius(17)
.border({ width: 1, color: GR.line })
.alignItems(VerticalAlign.Center)
Column() {
if (this.currentTab === 0) {
GrHomeTab()
} else if (this.currentTab === 1) {
GrSprayTab()
} else if (this.currentTab === 2) {
GrMarkerTab()
} else if (this.currentTab === 3) {
GrStickerTab()
} else if (this.currentTab === 4) {
GrGuardTab()
} else {
GrMineTab()
}
}
.layoutWeight(1)
Row() {
ForEach(GR_TABS, (t: GrTab, i: number) => {
Column() {
Text(t.icon)
.fontSize(18)
.opacity(this.currentTab === i ? 1 : 0.4)
Text(t.name)
.fontSize(9)
.fontColor(this.currentTab === i ? GR.primary : GR.sub)
.margin({ top: 2 })
}
.layoutWeight(1)
.onClick(() => {
this.currentTab = i
})
}, (t: GrTab) => t.name)
}
.width('100%')
.height(54)
.backgroundColor(GR.dark)
.border({ width: { top: 1 }, color: GR.line })
.alignItems(VerticalAlign.Center)
}
.width('100%')
.height('100%')
.backgroundColor(GR.bg)
}
}
}
@Component
struct GrHomeTab {
@State wave: number = 0
@State showEvent: boolean = false
@State showClass: boolean = false
@State showLimited: boolean = false
@State showCommunity: boolean = false
@State ranked: GrSpray[] = []
private timer: number = -1
aboutToAppear() {
this.ranked = grSoldRank().slice(0, 5)
this.timer = setInterval(() => {
this.wave = (this.wave + 1) % 10000
}, 60)
}
aboutToDisappear() {
if (this.timer !== -1) {
clearInterval(this.timer)
}
}
@Builder modalOverlay() {
Column()
.width('100%')
.height('100%')
.backgroundColor('rgba(0,0,0,0.72)')
.position({ x: 0, y: 0 })
.zIndex(999)
.onClick(() => {
this.showEvent = false
this.showClass = false
this.showLimited = false
this.showCommunity = false
})
}
build() {
Stack() {
Scroll() {
Column() {
Column() {
Row() {
Text('GRAFFITI')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
Text(' ZONE')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
Column().layoutWeight(1)
Text('HOT')
.fontSize(9)
.fontColor(GR.dark)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius(4)
.backgroundColor(GR.accent)
}
.width('100%')
Row() {
ForEach(GR_SPRAYS.slice(0, 6), (s: GrSpray, i: number) => {
Column()
.width(28)
.height(28)
.borderRadius(14)
.backgroundColor(s.colorHex)
.scale({ x: grSprayR(this.wave, i), y: grSprayR(this.wave, i) })
.opacity(grGlow(this.wave, i))
.margin({ left: i > 0 ? 6 : 0 })
}, (s: GrSpray) => s.name)
}
.margin({ top: 12 })
Text('1000+ 款色 | 30+ 品牌 | 全球直邮')
.fontSize(10)
.fontColor(GR.sub)
.margin({ top: 8 })
}
.width('92%')
.padding(16)
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.line })
.margin({ top: 8 })
.alignItems(HorizontalAlign.Start)
Row() {
Column() {
Text('30+')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
Text('品牌')
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('1000+')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.accent)
Text('款色')
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('5')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
Text('活动')
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('2.8万')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.accent)
Text('会员')
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}
.width('92%')
.padding({ top: 12, bottom: 12 })
.backgroundColor(GR.card)
.borderRadius(14)
.border({ width: 1, color: GR.line })
.margin({ top: 8 })
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
Column() {
Text('🎨')
.fontSize(24)
Text('涂鸦活动')
.fontSize(11)
.fontColor(GR.text)
.margin({ top: 6 })
Text('5场进行中')
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
}
.width('48.5%')
.padding(14)
.backgroundColor('rgba(57,255,20,0.08)')
.borderRadius(14)
.border({ width: 1, color: GR.line })
.alignItems(HorizontalAlign.Center)
.onClick(() => {
this.showEvent = true
})
Column() {
Text('📚')
.fontSize(24)
Text('喷漆课堂')
.fontSize(11)
.fontColor(GR.text)
.margin({ top: 6 })
Text('8门课程')
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
}
.width('48.5%')
.padding(14)
.backgroundColor('rgba(255,45,149,0.08)')
.borderRadius(14)
.border({ width: 1, color: GR.line })
.alignItems(HorizontalAlign.Center)
.onClick(() => {
this.showClass = true
})
Column() {
Text('⭐')
.fontSize(24)
Text('联名限定')
.fontSize(11)
.fontColor(GR.text)
.margin({ top: 6 })
Text('3款预售中')
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
}
.width('48.5%')
.padding(14)
.backgroundColor('rgba(57,255,20,0.08)')
.borderRadius(14)
.border({ width: 1, color: GR.line })
.alignItems(HorizontalAlign.Center)
.margin({ top: 8 })
.onClick(() => {
this.showLimited = true
})
Column() {
Text('💬')
.fontSize(24)
Text('创作社区')
.fontSize(11)
.fontColor(GR.text)
.margin({ top: 6 })
Text('1200+ 作品')
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
}
.width('48.5%')
.padding(14)
.backgroundColor('rgba(255,45,149,0.08)')
.borderRadius(14)
.border({ width: 1, color: GR.line })
.alignItems(HorizontalAlign.Center)
.margin({ top: 8 })
.onClick(() => {
this.showCommunity = true
})
}
.width('92%')
.margin({ top: 10 })
Row() {
Text('热销榜单')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
Column().layoutWeight(1)
Text('查看全部')
.fontSize(11)
.fontColor(GR.sub)
}
.width('92%')
.margin({ top: 14 })
Column() {
ForEach(this.ranked, (s: GrSpray, i: number) => {
Row() {
Text(grTopN(i))
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(i < 3 ? GR.primary : GR.sub)
.width(36)
Column()
.width(24)
.height(24)
.borderRadius(12)
.backgroundColor(s.colorHex)
.margin({ left: 4, right: 8 })
Column() {
Text(s.name)
.fontSize(12)
.fontColor(GR.text)
Text(s.brand + ' · ' + s.color)
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Text(s.sold + '件')
.fontSize(11)
.fontColor(GR.primary)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.padding({ top: 10, bottom: 10 })
.border({ width: { bottom: 1 }, color: GR.line })
}, (s: GrSpray) => s.name)
}
.width('92%')
.padding({ left: 14, right: 14 })
.backgroundColor(GR.card)
.borderRadius(14)
.border({ width: 1, color: GR.line })
.margin({ top: 6 })
Row() {
Text('联名限定')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
Column().layoutWeight(1)
Text('更多 >')
.fontSize(11)
.fontColor(GR.sub)
}
.width('92%')
.margin({ top: 14 })
Scroll() {
Row({ space: 10 }) {
ForEach(GR_COLLECTS, (c: GrCollect, i: number) => {
Column() {
Column()
.width('100%')
.height(70)
.backgroundColor(i % 2 === 0 ? GR.dark : GR.line)
.borderRadius(10)
.justifyContent(FlexAlign.Center)
Text(c.name)
.fontSize(11)
.fontColor(GR.text)
.margin({ top: 6 })
Text(c.brand)
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
Text('¥' + c.price)
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
.margin({ top: 4 })
}
.width(120)
.padding(10)
.backgroundColor(GR.card)
.borderRadius(12)
.border({ width: 1, color: GR.line })
.onClick(() => {
this.showLimited = true
})
}, (c: GrCollect) => c.name)
}
.padding({ left: 16, right: 16 })
}
.scrollable(ScrollDirection.Horizontal)
.width('100%')
.margin({ top: 6, bottom: 12 })
}
}
.width('100%')
.height('100%')
if (this.showEvent) {
Stack() {
this.modalOverlay()
Column() {
Row() {
Text('涂鸦活动报名')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
.layoutWeight(1)
Text('×')
.fontSize(18)
.fontColor(GR.sub)
.onClick(() => {
this.showEvent = false
})
}
.width('100%')
.padding({ bottom: 12 })
.border({ width: { bottom: 1 }, color: GR.line })
Text('街头涂鸦大赛 2025')
.fontSize(14)
.fontColor(GR.primary)
.margin({ top: 12 })
Text('时间:08月20日 14:00-17:00')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 6 })
Text('地点:798艺术区 B3 厂房')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 4 })
Text('报名费:¥50 / 限50人')
.fontSize(11)
.fontColor(GR.accent)
.margin({ top: 4 })
Column() {
Text('参赛须知')
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
Text('· 自备喷漆与防护装备')
.fontSize(10)
.fontColor(GR.sub)
.margin({ top: 6 })
Text('· 创作时间 3 小时')
.fontSize(10)
.fontColor(GR.sub)
.margin({ top: 3 })
Text('· 评委打分 + 现场投票')
.fontSize(10)
.fontColor(GR.sub)
.margin({ top: 3 })
Text('· 一等奖 ¥3000 喷漆礼包')
.fontSize(10)
.fontColor(GR.accent)
.margin({ top: 3 })
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.margin({ top: 12 })
Row({ space: 10 }) {
Text('取消')
.fontSize(13)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.dark)
.onClick(() => {
this.showEvent = false
})
Text('立即报名')
.fontSize(13)
.fontColor(Color.Black)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.primary)
.onClick(() => {
this.showEvent = false
})
}
.width('100%')
.margin({ top: 16 })
}
.width('86%')
.constraintSize({ maxHeight: '80%' })
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.line })
.padding(16)
.zIndex(1000)
}
.width('100%')
.height('100%')
}
if (this.showClass) {
Stack() {
this.modalOverlay()
Column() {
Row() {
Column()
.width(4)
.height(16)
.borderRadius(2)
.backgroundColor(GR.primary)
Text('喷漆课堂')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
.margin({ left: 8 })
Column().layoutWeight(1)
Text('×')
.fontSize(18)
.fontColor(GR.sub)
.onClick(() => {
this.showClass = false
})
}
.width('100%')
.padding({ bottom: 12 })
.border({ width: { bottom: 1 }, color: GR.line })
Column() {
Row() {
Text('基础入门')
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
Column().layoutWeight(1)
Text('¥99')
.fontSize(12)
.fontColor(GR.accent)
}
.width('100%')
Text('2课时 · 喷漆控制与线条')
.fontSize(10)
.fontColor(GR.sub)
.margin({ top: 4 })
}
.width('100%')
.padding({ top: 10, bottom: 10 })
.border({ width: { bottom: 1 }, color: GR.line })
.margin({ top: 8 })
Column() {
Row() {
Text('进阶叠色')
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(GR.accent)
Column().layoutWeight(1)
Text('¥199')
.fontSize(12)
.fontColor(GR.accent)
}
.width('100%')
Text('3课时 · 渐变与叠色技法')
.fontSize(10)
.fontColor(GR.sub)
.margin({ top: 4 })
}
.width('100%')
.padding({ top: 10, bottom: 10 })
.border({ width: { bottom: 1 }, color: GR.line })
Column() {
Row() {
Text('大师创作')
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
Column().layoutWeight(1)
Text('¥399')
.fontSize(12)
.fontColor(GR.accent)
}
.width('100%')
Text('5课时 · 完整作品从草图到上墙')
.fontSize(10)
.fontColor(GR.sub)
.margin({ top: 4 })
}
.width('100%')
.padding({ top: 10, bottom: 10 })
Row({ space: 10 }) {
Text('以后再说')
.fontSize(13)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.dark)
.onClick(() => {
this.showClass = false
})
Text('预约课程')
.fontSize(13)
.fontColor(GR.dark)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.primary)
.onClick(() => {
this.showClass = false
})
}
.width('100%')
.margin({ top: 14 })
}
.width('86%')
.constraintSize({ maxHeight: '80%' })
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.line })
.padding(16)
.zIndex(1000)
}
.width('100%')
.height('100%')
}
if (this.showLimited) {
Stack() {
this.modalOverlay()
Column() {
Row() {
Text('联名限定预购')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.accent)
.layoutWeight(1)
Text('×')
.fontSize(18)
.fontColor(GR.sub)
.onClick(() => {
this.showLimited = false
})
}
.width('100%')
.padding({ bottom: 12 })
.border({ width: { bottom: 1 }, color: GR.line })
Row() {
Column()
.width(60)
.height(60)
.borderRadius(12)
.backgroundColor(GR.dark)
.border({ width: 1, color: GR.line })
Column() {
Text('MTN × GRAFFITI')
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
Text('联名限定套装')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 4 })
Text('限量 500 套')
.fontSize(10)
.fontColor(GR.accent)
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 12 })
.layoutWeight(1)
}
.width('100%')
.margin({ top: 12 })
Text('配色选择')
.fontSize(12)
.fontColor(GR.text)
.margin({ top: 14 })
Row({ space: 10 }) {
Column()
.width(28)
.height(28)
.borderRadius(14)
.backgroundColor('#39FF14')
.border({ width: 2, color: GR.primary })
Column()
.width(28)
.height(28)
.borderRadius(14)
.backgroundColor('#FF2D95')
.border({ width: 1, color: GR.line })
Column()
.width(28)
.height(28)
.borderRadius(14)
.backgroundColor('#00F5D4')
.border({ width: 1, color: GR.line })
Column()
.width(28)
.height(28)
.borderRadius(14)
.backgroundColor('#FFD700')
.border({ width: 1, color: GR.line })
}
.margin({ top: 8 })
Row() {
Text('预购价')
.fontSize(12)
.fontColor(GR.sub)
Column().layoutWeight(1)
Text('¥468')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(GR.accent)
}
.width('100%')
.margin({ top: 14 })
Row({ space: 10 }) {
Text('取消')
.fontSize(13)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.dark)
.onClick(() => {
this.showLimited = false
})
Text('预购')
.fontSize(13)
.fontColor(Color.Black)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.primary)
.onClick(() => {
this.showLimited = false
})
}
.width('100%')
.margin({ top: 16 })
}
.width('86%')
.constraintSize({ maxHeight: '80%' })
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.line })
.padding(16)
.zIndex(1000)
}
.width('100%')
.height('100%')
}
if (this.showCommunity) {
Stack() {
this.modalOverlay()
Column() {
Row() {
Text('💬 创作社区')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
.layoutWeight(1)
Text('×')
.fontSize(18)
.fontColor(GR.sub)
.onClick(() => {
this.showCommunity = false
})
}
.width('100%')
.padding({ bottom: 12 })
.border({ width: { bottom: 1 }, color: GR.line })
Row() {
Column() {
Text('2.8万')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
Text('成员')
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('1200+')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.accent)
Text('作品')
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('86')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
Text('本周新作')
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.padding({ top: 10, bottom: 10 })
.backgroundColor(GR.dark)
.borderRadius(12)
.margin({ top: 10 })
Text('自我介绍')
.fontSize(12)
.fontColor(GR.text)
.margin({ top: 12 })
TextInput({ placeholder: '一句话介绍你的创作风格...' })
.height(36)
.fontSize(11)
.fontColor(GR.text)
.backgroundColor(GR.dark)
.borderRadius(8)
.border({ width: 1, color: GR.line })
.margin({ top: 6 })
Text('创作类型')
.fontSize(12)
.fontColor(GR.text)
.margin({ top: 12 })
Row({ space: 8 }) {
Text('墙绘')
.fontSize(11)
.fontColor(GR.dark)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(14)
.backgroundColor(GR.primary)
Text('布面')
.fontSize(11)
.fontColor(GR.sub)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(14)
.backgroundColor(GR.dark)
Text('涂鸦')
.fontSize(11)
.fontColor(GR.sub)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(14)
.backgroundColor(GR.dark)
}
.margin({ top: 6 })
Row({ space: 10 }) {
Text('取消')
.fontSize(13)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.dark)
.onClick(() => {
this.showCommunity = false
})
Text('加入社区')
.fontSize(13)
.fontColor(GR.dark)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.primary)
.onClick(() => {
this.showCommunity = false
})
}
.width('100%')
.margin({ top: 16 })
}
.width('86%')
.constraintSize({ maxHeight: '80%' })
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.line })
.padding(16)
.zIndex(1000)
}
.width('100%')
.height('100%')
}
}
.width('100%')
.height('100%')
}
}
@Component
struct GrSprayTab {
@State wave: number = 0
@State chip: number = 0
@State sprays: GrSpray[] = []
@State ranked: GrSpray[] = []
@State showDetail: boolean = false
@State showAdd: boolean = false
@State showEdit: boolean = false
@State showDel: boolean = false
@State selIdx: number = 0
private timer: number = -1
aboutToAppear() {
this.sprays = GR_SPRAYS.slice()
this.ranked = grSoldRank().slice(0, 6)
this.timer = setInterval(() => {
this.wave = (this.wave + 1) % 10000
}, 60)
}
aboutToDisappear() {
if (this.timer !== -1) {
clearInterval(this.timer)
}
}
@Builder modalOverlay() {
Column()
.width('100%')
.height('100%')
.backgroundColor('rgba(0,0,0,0.72)')
.position({ x: 0, y: 0 })
.zIndex(999)
.onClick(() => {
this.showDetail = false
this.showAdd = false
this.showEdit = false
this.showDel = false
})
}
build() {
Stack() {
Scroll() {
Column() {
Row() {
Text('喷漆色卡')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
Column().layoutWeight(1)
Text('+' + '新增')
.fontSize(11)
.fontColor(GR.primary)
.onClick(() => {
this.showAdd = true
})
}
.width('92%')
.margin({ top: 8 })
Scroll() {
Row({ space: 8 }) {
ForEach(['全部', '速干', '荧光', '高压', '夜光', '金属'], (tag: string, i: number) => {
Text(tag)
.fontSize(11)
.fontColor(this.chip === i ? GR.dark : GR.sub)
.padding({ left: 14, right: 14, top: 6, bottom: 6 })
.borderRadius(14)
.backgroundColor(this.chip === i ? GR.primary : GR.dark)
.border({ width: 1, color: GR.line })
.onClick(() => {
this.chip = i
this.sprays = grFilterSprays(i)
})
}, (tag: string) => tag)
}
.padding({ left: 16, right: 16 })
}
.scrollable(ScrollDirection.Horizontal)
.width('100%')
.margin({ top: 6 })
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(this.sprays, (s: GrSpray, i: number) => {
Column() {
Column()
.width(48)
.height(48)
.borderRadius(24)
.backgroundColor(s.colorHex)
.scale({ x: grSprayR(this.wave, i), y: grSprayR(this.wave, i) })
.opacity(grGlow(this.wave, i))
.border({ width: 2, color: GR.dark })
Text(s.name)
.fontSize(11)
.fontColor(GR.text)
.margin({ top: 8 })
Text(s.brand + ' · ' + s.color)
.fontSize(9)
.fontColor(GR.sub)
.margin({ top: 2 })
Row() {
Text('覆盖')
.fontSize(8)
.fontColor(GR.sub)
.width(24)
Stack({ alignContent: Alignment.Start }) {
Row()
.width('100%')
.height(3)
.borderRadius(2)
.backgroundColor(GR.dark)
Row()
.width(grCoverageW(s.coverage))
.height(3)
.borderRadius(2)
.backgroundColor(GR.primary)
}
.layoutWeight(1)
.height(3)
}
.width('100%')
.margin({ top: 6 })
Row() {
Text('压力')
.fontSize(8)
.fontColor(GR.sub)
.width(24)
Stack({ alignContent: Alignment.Start }) {
Row()
.width('100%')
.height(3)
.borderRadius(2)
.backgroundColor(GR.dark)
Row()
.width(grPressureW(s.pressure))
.height(3)
.borderRadius(2)
.backgroundColor(GR.accent)
}
.layoutWeight(1)
.height(3)
}
.width('100%')
.margin({ top: 4 })
Row() {
Text('¥' + (s.volume / 10 + 29))
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
Column().layoutWeight(1)
Text('销' + s.sold)
.fontSize(9)
.fontColor(GR.sub)
}
.width('100%')
.margin({ top: 6 })
Row() {
Text('详情')
.fontSize(9)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 4, bottom: 4 })
.borderRadius(6)
.backgroundColor(GR.dark)
.onClick(() => {
this.selIdx = i
this.showDetail = true
})
Text('编辑')
.fontSize(9)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 4, bottom: 4 })
.borderRadius(6)
.backgroundColor(GR.dark)
.margin({ left: 4 })
.onClick(() => {
this.selIdx = i
this.showEdit = true
})
Text('删除')
.fontSize(9)
.fontColor(GR.accent)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 4, bottom: 4 })
.borderRadius(6)
.backgroundColor(GR.dark)
.margin({ left: 4 })
.onClick(() => {
this.selIdx = i
this.showDel = true
})
}
.width('100%')
.margin({ top: 6 })
}
.width('48.5%')
.padding(12)
.backgroundColor(GR.card)
.borderRadius(14)
.border({ width: 1, color: GR.line })
.margin({ top: 8 })
}, (s: GrSpray) => s.name)
}
.width('92%')
.margin({ top: 6 })
Text('销量排行')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
.margin({ top: 14 })
Row() {
ForEach(this.ranked, (s: GrSpray, i: number) => {
Column() {
Column()
.width(18)
.height(Math.min(s.sold / 410 * 50, 50))
.backgroundColor(i < 3 ? GR.primary : GR.accent)
.borderRadius(4)
Text(s.sold + '')
.fontSize(8)
.fontColor(GR.sub)
.margin({ top: 4 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}, (s: GrSpray) => s.name)
}
.width('92%')
.height(70)
.alignItems(VerticalAlign.Bottom)
.padding({ top: 10, bottom: 4 })
.backgroundColor(GR.card)
.borderRadius(14)
.border({ width: 1, color: GR.line })
.margin({ top: 6, bottom: 12 })
}
}
.width('100%')
.height('100%')
if (this.showDetail) {
Stack() {
this.modalOverlay()
Column() {
Row() {
Text('喷漆详情')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
.layoutWeight(1)
Text('×')
.fontSize(18)
.fontColor(GR.sub)
.onClick(() => {
this.showDetail = false
})
}
.width('100%')
.padding({ bottom: 12 })
.border({ width: { bottom: 1 }, color: GR.line })
Row() {
Column()
.width(56)
.height(56)
.borderRadius(28)
.backgroundColor(this.sprays[this.selIdx].colorHex)
.border({ width: 2, color: GR.dark })
.scale({ x: grSprayR(this.wave, 0), y: grSprayR(this.wave, 0) })
Column() {
Text(this.sprays[this.selIdx].name)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
Text(this.sprays[this.selIdx].brand + ' · ' + this.sprays[this.selIdx].color)
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 4 })
Text('¥' + (this.sprays[this.selIdx].volume / 10 + 29))
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 14 })
.layoutWeight(1)
}
.width('100%')
.margin({ top: 12 })
Column() {
Text('覆盖力')
.fontSize(11)
.fontColor(GR.sub)
Stack({ alignContent: Alignment.Start }) {
Row()
.width('100%')
.height(6)
.borderRadius(3)
.backgroundColor(GR.dark)
Row()
.width(grCoverageW(this.sprays[this.selIdx].coverage))
.height(6)
.borderRadius(3)
.backgroundColor(GR.primary)
}
.width('100%')
.height(6)
.margin({ top: 4 })
}
.width('100%')
.margin({ top: 14 })
Column() {
Text('喷射压力')
.fontSize(11)
.fontColor(GR.sub)
Stack({ alignContent: Alignment.Start }) {
Row()
.width('100%')
.height(6)
.borderRadius(3)
.backgroundColor(GR.dark)
Row()
.width(grPressureW(this.sprays[this.selIdx].pressure))
.height(6)
.borderRadius(3)
.backgroundColor(GR.accent)
}
.width('100%')
.height(6)
.margin({ top: 4 })
}
.width('100%')
.margin({ top: 10 })
Row() {
Column() {
Text('容量')
.fontSize(9)
.fontColor(GR.sub)
Text(this.sprays[this.selIdx].volume + 'ml')
.fontSize(13)
.fontColor(GR.text)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('库存')
.fontSize(9)
.fontColor(GR.sub)
Text(this.sprays[this.selIdx].stock + '瓶')
.fontSize(13)
.fontColor(GR.text)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('已售')
.fontSize(9)
.fontColor(GR.sub)
Text(this.sprays[this.selIdx].sold + '件')
.fontSize(13)
.fontColor(GR.primary)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.padding({ top: 10, bottom: 10 })
.backgroundColor(GR.dark)
.borderRadius(10)
.margin({ top: 12 })
Text('标签:' + this.sprays[this.selIdx].tag)
.fontSize(10)
.fontColor(GR.accent)
.margin({ top: 10 })
Row({ space: 10 }) {
Text('关闭')
.fontSize(13)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.dark)
.onClick(() => {
this.showDetail = false
})
Text('加入收藏')
.fontSize(13)
.fontColor(Color.Black)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.primary)
.onClick(() => {
this.showDetail = false
})
}
.width('100%')
.margin({ top: 16 })
}
.width('86%')
.constraintSize({ maxHeight: '80%' })
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.line })
.padding(16)
.zIndex(1000)
}
.width('100%')
.height('100%')
}
if (this.showAdd) {
Stack() {
this.modalOverlay()
Column() {
Row() {
Text('新增喷漆')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
.layoutWeight(1)
Text('×')
.fontSize(18)
.fontColor(GR.sub)
.onClick(() => {
this.showAdd = false
})
}
.width('100%')
.padding({ bottom: 12 })
.border({ width: { bottom: 1 }, color: GR.line })
Text('喷漆名称')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
TextInput({ placeholder: '如:Hardcore 600ml' })
.height(36)
.fontSize(11)
.fontColor(GR.text)
.backgroundColor(GR.dark)
.borderRadius(8)
.border({ width: 1, color: GR.line })
.margin({ top: 4 })
Text('品牌')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
TextInput({ placeholder: '如:MONTANA' })
.height(36)
.fontSize(11)
.fontColor(GR.text)
.backgroundColor(GR.dark)
.borderRadius(8)
.border({ width: 1, color: GR.line })
.margin({ top: 4 })
Text('选择颜色')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
Row({ space: 10 }) {
Column()
.width(28)
.height(28)
.borderRadius(14)
.backgroundColor('#39FF14')
.border({ width: 2, color: GR.primary })
Column()
.width(28)
.height(28)
.borderRadius(14)
.backgroundColor('#FF2D95')
.border({ width: 1, color: GR.line })
Column()
.width(28)
.height(28)
.borderRadius(14)
.backgroundColor('#00F5D4')
.border({ width: 1, color: GR.line })
Column()
.width(28)
.height(28)
.borderRadius(14)
.backgroundColor('#FFD700')
.border({ width: 1, color: GR.line })
Column()
.width(28)
.height(28)
.borderRadius(14)
.backgroundColor('#8B5CF6')
.border({ width: 1, color: GR.line })
Column()
.width(28)
.height(28)
.borderRadius(14)
.backgroundColor('#F0F0F0')
.border({ width: 1, color: GR.line })
}
.margin({ top: 6 })
Text('容量(ml)')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
TextInput({ placeholder: '如:400' })
.height(36)
.fontSize(11)
.fontColor(GR.text)
.backgroundColor(GR.dark)
.borderRadius(8)
.border({ width: 1, color: GR.line })
.margin({ top: 4 })
Row({ space: 10 }) {
Text('取消')
.fontSize(13)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.dark)
.onClick(() => {
this.showAdd = false
})
Text('确认新增')
.fontSize(13)
.fontColor(Color.Black)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.primary)
.onClick(() => {
this.showAdd = false
})
}
.width('100%')
.margin({ top: 16 })
}
.width('86%')
.constraintSize({ maxHeight: '80%' })
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.line })
.padding(16)
.zIndex(1000)
}
.width('100%')
.height('100%')
}
if (this.showEdit) {
Stack() {
this.modalOverlay()
Column() {
Row() {
Text('编辑喷漆')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.accent)
.layoutWeight(1)
Text('×')
.fontSize(18)
.fontColor(GR.sub)
.onClick(() => {
this.showEdit = false
})
}
.width('100%')
.padding({ bottom: 12 })
.border({ width: { bottom: 1 }, color: GR.line })
Text('当前:' + this.sprays[this.selIdx].name)
.fontSize(12)
.fontColor(GR.sub)
.margin({ top: 10 })
Text('修改名称')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
TextInput({ placeholder: this.sprays[this.selIdx].name })
.height(36)
.fontSize(11)
.fontColor(GR.text)
.backgroundColor(GR.dark)
.borderRadius(8)
.border({ width: 1, color: GR.line })
.margin({ top: 4 })
Text('修改品牌')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
TextInput({ placeholder: this.sprays[this.selIdx].brand })
.height(36)
.fontSize(11)
.fontColor(GR.text)
.backgroundColor(GR.dark)
.borderRadius(8)
.border({ width: 1, color: GR.line })
.margin({ top: 4 })
Text('当前色值')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
Row() {
Column()
.width(28)
.height(28)
.borderRadius(14)
.backgroundColor(this.sprays[this.selIdx].colorHex)
.border({ width: 2, color: GR.primary })
Text(this.sprays[this.selIdx].colorHex)
.fontSize(11)
.fontColor(GR.sub)
.margin({ left: 10 })
}
.margin({ top: 6 })
Text('修改标签')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
TextInput({ placeholder: this.sprays[this.selIdx].tag })
.height(36)
.fontSize(11)
.fontColor(GR.text)
.backgroundColor(GR.dark)
.borderRadius(8)
.border({ width: 1, color: GR.line })
.margin({ top: 4 })
Row({ space: 10 }) {
Text('取消')
.fontSize(13)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.dark)
.onClick(() => {
this.showEdit = false
})
Text('保存修改')
.fontSize(13)
.fontColor(GR.dark)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.accent)
.onClick(() => {
this.showEdit = false
})
}
.width('100%')
.margin({ top: 16 })
}
.width('86%')
.constraintSize({ maxHeight: '80%' })
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.line })
.padding(16)
.zIndex(1000)
}
.width('100%')
.height('100%')
}
if (this.showDel) {
Stack() {
this.modalOverlay()
Column() {
Text('确认删除')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.accent)
.textAlign(TextAlign.Center)
.width('100%')
Column()
.width(48)
.height(48)
.borderRadius(24)
.backgroundColor(this.sprays[this.selIdx].colorHex)
.border({ width: 2, color: GR.dark })
.margin({ top: 16 })
Text(this.sprays[this.selIdx].name)
.fontSize(13)
.fontColor(GR.text)
.margin({ top: 10 })
Text('删除后不可恢复,确定要删除这款喷漆吗?')
.fontSize(11)
.fontColor(GR.sub)
.textAlign(TextAlign.Center)
.width('100%')
.margin({ top: 8 })
Row({ space: 10 }) {
Text('取消')
.fontSize(13)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.dark)
.onClick(() => {
this.showDel = false
})
Text('确认删除')
.fontSize(13)
.fontColor(Color.White)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.accent)
.onClick(() => {
this.showDel = false
})
}
.width('100%')
.margin({ top: 20 })
}
.width('80%')
.padding(20)
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.accent })
.zIndex(1000)
}
.width('100%')
.height('100%')
}
}
.width('100%')
.height('100%')
}
}
@Component
struct GrMarkerTab {
@State wave: number = 0
@State showDetail: boolean = false
@State showAdd: boolean = false
@State showEdit: boolean = false
@State selIdx: number = 0
@State tipChip: number = 0
private timer: number = -1
aboutToAppear() {
this.timer = setInterval(() => {
this.wave = (this.wave + 1) % 10000
}, 60)
}
aboutToDisappear() {
if (this.timer !== -1) {
clearInterval(this.timer)
}
}
@Builder modalOverlay() {
Column()
.width('100%')
.height('100%')
.backgroundColor('rgba(0,0,0,0.72)')
.position({ x: 0, y: 0 })
.zIndex(999)
.onClick(() => {
this.showDetail = false
this.showAdd = false
this.showEdit = false
})
}
build() {
Stack() {
Scroll() {
Column() {
Row() {
Text('马克笔')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
Column().layoutWeight(1)
Text('+ 新增')
.fontSize(11)
.fontColor(GR.primary)
.onClick(() => {
this.showAdd = true
})
}
.width('92%')
.margin({ top: 8 })
ForEach(GR_MARKERS, (m: GrMarker, i: number) => {
Column() {
Row() {
Text(m.brand)
.fontSize(11)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
Column().layoutWeight(1)
Text(m.tip)
.fontSize(9)
.fontColor(GR.dark)
.padding({ left: 8, right: 8, top: 3, bottom: 3 })
.borderRadius(10)
.backgroundColor(GR.primary)
}
.width('100%')
Text(m.name)
.fontSize(13)
.fontColor(GR.text)
.margin({ top: 4 })
Row() {
Text('墨水')
.fontSize(8)
.fontColor(GR.sub)
.width(24)
Stack({ alignContent: Alignment.Start }) {
Row()
.width('100%')
.height(4)
.borderRadius(2)
.backgroundColor(GR.dark)
Row()
.width(grInkW(m.ink))
.height(4)
.borderRadius(2)
.backgroundColor(GR.accent)
}
.layoutWeight(1)
.height(4)
}
.width('100%')
.margin({ top: 8 })
Row() {
Text(m.waterproof === 1 ? '防水 ✓' : '非防水')
.fontSize(10)
.fontColor(m.waterproof === 1 ? GR.primary : GR.sub)
Column().layoutWeight(1)
Text(m.size + 'mm')
.fontSize(10)
.fontColor(GR.sub)
Text(' ¥' + (m.ink * 15 + 15))
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
.margin({ left: 8 })
}
.width('100%')
.margin({ top: 6 })
Row() {
Text('详情')
.fontSize(9)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 4, bottom: 4 })
.borderRadius(6)
.backgroundColor(GR.dark)
.onClick(() => {
this.selIdx = i
this.showDetail = true
})
Text('编辑')
.fontSize(9)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 4, bottom: 4 })
.borderRadius(6)
.backgroundColor(GR.dark)
.margin({ left: 4 })
.onClick(() => {
this.selIdx = i
this.showEdit = true
})
}
.width('100%')
.margin({ top: 8 })
}
.width('92%')
.padding(14)
.backgroundColor(GR.card)
.borderRadius(14)
.border({ width: 1, color: GR.line })
.margin({ top: 8 })
.alignItems(HorizontalAlign.Start)
}, (m: GrMarker) => m.name)
Text('笔尖尺寸对比')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
.margin({ top: 14 })
Column() {
ForEach(GR_MARKERS, (m: GrMarker, i: number) => {
Row() {
Text(m.size + 'mm')
.fontSize(9)
.fontColor(GR.sub)
.width(36)
Stack({ alignContent: Alignment.Start }) {
Row()
.width('100%')
.height(6)
.borderRadius(3)
.backgroundColor(GR.dark)
Row()
.width(m.size * 18 + '%')
.height(6)
.borderRadius(3)
.backgroundColor(i % 2 === 0 ? GR.primary : GR.accent)
}
.layoutWeight(1)
.height(6)
Text(m.brand)
.fontSize(8)
.fontColor(GR.sub)
.width(60)
.margin({ left: 8 })
}
.width('100%')
.margin({ top: 5 })
}, (m: GrMarker) => m.name)
}
.width('92%')
.padding(14)
.backgroundColor(GR.card)
.borderRadius(14)
.border({ width: 1, color: GR.line })
.margin({ top: 6, bottom: 12 })
}
}
.width('100%')
.height('100%')
if (this.showDetail) {
Stack() {
this.modalOverlay()
Column() {
Row() {
Text('马克笔详情')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
.layoutWeight(1)
Text('×')
.fontSize(18)
.fontColor(GR.sub)
.onClick(() => {
this.showDetail = false
})
}
.width('100%')
.padding({ bottom: 12 })
.border({ width: { bottom: 1 }, color: GR.line })
Row() {
Text(GR_MARKERS[this.selIdx].brand)
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
Column().layoutWeight(1)
Text(GR_MARKERS[this.selIdx].tip)
.fontSize(10)
.fontColor(GR.dark)
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
.borderRadius(12)
.backgroundColor(GR.primary)
}
.width('100%')
.margin({ top: 12 })
Text(GR_MARKERS[this.selIdx].name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
.margin({ top: 6 })
Column() {
Text('墨水容量')
.fontSize(11)
.fontColor(GR.sub)
Stack({ alignContent: Alignment.Start }) {
Row()
.width('100%')
.height(6)
.borderRadius(3)
.backgroundColor(GR.dark)
Row()
.width(grInkW(GR_MARKERS[this.selIdx].ink))
.height(6)
.borderRadius(3)
.backgroundColor(GR.accent)
}
.width('100%')
.height(6)
.margin({ top: 4 })
Text(GR_MARKERS[this.selIdx].ink + 'ml')
.fontSize(10)
.fontColor(GR.accent)
.margin({ top: 4 })
}
.width('100%')
.margin({ top: 12 })
Row() {
Column() {
Text('笔尖')
.fontSize(9)
.fontColor(GR.sub)
Text(GR_MARKERS[this.selIdx].size + 'mm')
.fontSize(13)
.fontColor(GR.text)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('防水')
.fontSize(9)
.fontColor(GR.sub)
Text(GR_MARKERS[this.selIdx].waterproof === 1 ? '是' : '否')
.fontSize(13)
.fontColor(GR_MARKERS[this.selIdx].waterproof === 1 ? GR.primary : GR.sub)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('库存')
.fontSize(9)
.fontColor(GR.sub)
Text(GR_MARKERS[this.selIdx].stock + '支')
.fontSize(13)
.fontColor(GR.text)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('已售')
.fontSize(9)
.fontColor(GR.sub)
Text(GR_MARKERS[this.selIdx].sold + '支')
.fontSize(13)
.fontColor(GR.primary)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.padding({ top: 10, bottom: 10 })
.backgroundColor(GR.dark)
.borderRadius(10)
.margin({ top: 12 })
Text('¥' + (GR_MARKERS[this.selIdx].ink * 15 + 15))
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
.margin({ top: 12 })
Row({ space: 10 }) {
Text('关闭')
.fontSize(13)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.dark)
.onClick(() => {
this.showDetail = false
})
Text('加入购物车')
.fontSize(13)
.fontColor(Color.Black)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.primary)
.onClick(() => {
this.showDetail = false
})
}
.width('100%')
.margin({ top: 16 })
}
.width('86%')
.constraintSize({ maxHeight: '80%' })
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.line })
.padding(16)
.zIndex(1000)
}
.width('100%')
.height('100%')
}
if (this.showAdd) {
Stack() {
this.modalOverlay()
Column() {
Row() {
Text('新增马克笔')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
.layoutWeight(1)
Text('×')
.fontSize(18)
.fontColor(GR.sub)
.onClick(() => {
this.showAdd = false
})
}
.width('100%')
.padding({ bottom: 12 })
.border({ width: { bottom: 1 }, color: GR.line })
Text('名称')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
TextInput({ placeholder: '如:Sketch 0.3' })
.height(36)
.fontSize(11)
.fontColor(GR.text)
.backgroundColor(GR.dark)
.borderRadius(8)
.border({ width: 1, color: GR.line })
.margin({ top: 4 })
Text('品牌')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
TextInput({ placeholder: '如:COPIC' })
.height(36)
.fontSize(11)
.fontColor(GR.text)
.backgroundColor(GR.dark)
.borderRadius(8)
.border({ width: 1, color: GR.line })
.margin({ top: 4 })
Text('笔尖类型')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
Row({ space: 8 }) {
ForEach(['软头', '圆头', '针头', '方头'], (t: string, i: number) => {
Text(t)
.fontSize(10)
.fontColor(this.tipChip === i ? GR.dark : GR.sub)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(12)
.backgroundColor(this.tipChip === i ? GR.primary : GR.dark)
.border({ width: 1, color: GR.line })
.onClick(() => {
this.tipChip = i
})
}, (t: string) => t)
}
.margin({ top: 6 })
Text('墨水容量(ml)')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
TextInput({ placeholder: '如:2.8' })
.height(36)
.fontSize(11)
.fontColor(GR.text)
.backgroundColor(GR.dark)
.borderRadius(8)
.border({ width: 1, color: GR.line })
.margin({ top: 4 })
Row({ space: 10 }) {
Text('取消')
.fontSize(13)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.dark)
.onClick(() => {
this.showAdd = false
})
Text('确认新增')
.fontSize(13)
.fontColor(Color.Black)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.primary)
.onClick(() => {
this.showAdd = false
})
}
.width('100%')
.margin({ top: 16 })
}
.width('86%')
.constraintSize({ maxHeight: '80%' })
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.line })
.padding(16)
.zIndex(1000)
}
.width('100%')
.height('100%')
}
if (this.showEdit) {
Stack() {
this.modalOverlay()
Column() {
Row() {
Text('编辑马克笔')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.accent)
.layoutWeight(1)
Text('×')
.fontSize(18)
.fontColor(GR.sub)
.onClick(() => {
this.showEdit = false
})
}
.width('100%')
.padding({ bottom: 12 })
.border({ width: { bottom: 1 }, color: GR.line })
Text('当前:' + GR_MARKERS[this.selIdx].name)
.fontSize(12)
.fontColor(GR.sub)
.margin({ top: 10 })
Text('修改名称')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
TextInput({ placeholder: GR_MARKERS[this.selIdx].name })
.height(36)
.fontSize(11)
.fontColor(GR.text)
.backgroundColor(GR.dark)
.borderRadius(8)
.border({ width: 1, color: GR.line })
.margin({ top: 4 })
Text('修改品牌')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
TextInput({ placeholder: GR_MARKERS[this.selIdx].brand })
.height(36)
.fontSize(11)
.fontColor(GR.text)
.backgroundColor(GR.dark)
.borderRadius(8)
.border({ width: 1, color: GR.line })
.margin({ top: 4 })
Text('修改笔尖尺寸(mm)')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
TextInput({ placeholder: GR_MARKERS[this.selIdx].size + '' })
.height(36)
.fontSize(11)
.fontColor(GR.text)
.backgroundColor(GR.dark)
.borderRadius(8)
.border({ width: 1, color: GR.line })
.margin({ top: 4 })
Row({ space: 10 }) {
Text('取消')
.fontSize(13)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.dark)
.onClick(() => {
this.showEdit = false
})
Text('保存')
.fontSize(13)
.fontColor(GR.dark)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.accent)
.onClick(() => {
this.showEdit = false
})
}
.width('100%')
.margin({ top: 16 })
}
.width('86%')
.constraintSize({ maxHeight: '80%' })
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.line })
.padding(16)
.zIndex(1000)
}
.width('100%')
.height('100%')
}
}
.width('100%')
.height('100%')
}
}
@Component
struct GrStickerTab {
@State wave: number = 0
@State showDetail: boolean = false
@State showAdd: boolean = false
@State showDel: boolean = false
@State selIdx: number = 0
private timer: number = -1
aboutToAppear() {
this.timer = setInterval(() => {
this.wave = (this.wave + 1) % 10000
}, 60)
}
aboutToDisappear() {
if (this.timer !== -1) {
clearInterval(this.timer)
}
}
@Builder modalOverlay() {
Column()
.width('100%')
.height('100%')
.backgroundColor('rgba(0,0,0,0.72)')
.position({ x: 0, y: 0 })
.zIndex(999)
.onClick(() => {
this.showDetail = false
this.showAdd = false
this.showDel = false
})
}
build() {
Stack() {
Scroll() {
Column() {
Row() {
Text('贴纸')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
Column().layoutWeight(1)
Text('+ 新增')
.fontSize(11)
.fontColor(GR.primary)
.onClick(() => {
this.showAdd = true
})
}
.width('92%')
.margin({ top: 8 })
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(GR_STICKERS, (s: GrSticker, i: number) => {
Column() {
Column()
.width(36)
.height(36)
.borderRadius(8)
.backgroundColor(i % 3 === 0 ? GR.dark : GR.line)
.scale({ x: grSprayR(this.wave, i), y: grSprayR(this.wave, i) })
.opacity(grGlow(this.wave, i))
Text(s.name)
.fontSize(10)
.fontColor(GR.text)
.margin({ top: 6 })
Text(s.material)
.fontSize(8)
.fontColor(GR.sub)
.margin({ top: 2 })
Text(s.count + '枚')
.fontSize(9)
.fontColor(GR.accent)
.margin({ top: 2 })
Stack({ alignContent: Alignment.Start }) {
Row()
.width('100%')
.height(3)
.borderRadius(2)
.backgroundColor(GR.dark)
Row()
.width(grStickerCountW(s.count))
.height(3)
.borderRadius(2)
.backgroundColor(GR.primary)
}
.width('100%')
.height(3)
.margin({ top: 4 })
Text('¥' + (s.count / 10 + 5))
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
.margin({ top: 4 })
Text('详情')
.fontSize(9)
.fontColor(GR.sub)
.width('100%')
.textAlign(TextAlign.Center)
.padding({ top: 4, bottom: 4 })
.borderRadius(6)
.backgroundColor(GR.dark)
.margin({ top: 4 })
.onClick(() => {
this.selIdx = i
this.showDetail = true
})
}
.width('31%')
.padding(10)
.backgroundColor(GR.card)
.borderRadius(12)
.border({ width: 1, color: GR.line })
.margin({ top: 8 })
.alignItems(HorizontalAlign.Center)
}, (s: GrSticker) => s.name)
}
.width('92%')
.margin({ top: 6 })
Text('材质分布')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
.margin({ top: 14 })
Column() {
Row() {
Text('PVC')
.fontSize(9)
.fontColor(GR.sub)
.width(36)
Stack({ alignContent: Alignment.Start }) {
Row()
.width('100%')
.height(6)
.borderRadius(3)
.backgroundColor(GR.dark)
Row()
.width('62%')
.height(6)
.borderRadius(3)
.backgroundColor(GR.primary)
}
.layoutWeight(1)
.height(6)
Text('5款')
.fontSize(8)
.fontColor(GR.sub)
.width(28)
.margin({ left: 6 })
}
.width('100%')
.margin({ top: 6 })
Row() {
Text('全息')
.fontSize(9)
.fontColor(GR.sub)
.width(36)
Stack({ alignContent: Alignment.Start }) {
Row()
.width('100%')
.height(6)
.borderRadius(3)
.backgroundColor(GR.dark)
Row()
.width('25%')
.height(6)
.borderRadius(3)
.backgroundColor(GR.accent)
}
.layoutWeight(1)
.height(6)
Text('2款')
.fontSize(8)
.fontColor(GR.sub)
.width(28)
.margin({ left: 6 })
}
.width('100%')
.margin({ top: 5 })
Row() {
Text('纸质')
.fontSize(9)
.fontColor(GR.sub)
.width(36)
Stack({ alignContent: Alignment.Start }) {
Row()
.width('100%')
.height(6)
.borderRadius(3)
.backgroundColor(GR.dark)
Row()
.width('13%')
.height(6)
.borderRadius(3)
.backgroundColor(GR.primary)
}
.layoutWeight(1)
.height(6)
Text('1款')
.fontSize(8)
.fontColor(GR.sub)
.width(28)
.margin({ left: 6 })
}
.width('100%')
.margin({ top: 5 })
}
.width('92%')
.padding(14)
.backgroundColor(GR.card)
.borderRadius(14)
.border({ width: 1, color: GR.line })
.margin({ top: 6, bottom: 12 })
}
}
.width('100%')
.height('100%')
if (this.showDetail) {
Stack() {
this.modalOverlay()
Column() {
Row() {
Text('贴纸详情')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
.layoutWeight(1)
Text('×')
.fontSize(18)
.fontColor(GR.sub)
.onClick(() => {
this.showDetail = false
})
}
.width('100%')
.padding({ bottom: 12 })
.border({ width: { bottom: 1 }, color: GR.line })
Text(GR_STICKERS[this.selIdx].name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.text)
.margin({ top: 12 })
Row() {
Column() {
Text('尺寸')
.fontSize(9)
.fontColor(GR.sub)
Text(GR_STICKERS[this.selIdx].size)
.fontSize(12)
.fontColor(GR.text)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('材质')
.fontSize(9)
.fontColor(GR.sub)
Text(GR_STICKERS[this.selIdx].material)
.fontSize(12)
.fontColor(GR.primary)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('数量')
.fontSize(9)
.fontColor(GR.sub)
Text(GR_STICKERS[this.selIdx].count + '枚')
.fontSize(12)
.fontColor(GR.accent)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.padding({ top: 10, bottom: 10 })
.backgroundColor(GR.dark)
.borderRadius(10)
.margin({ top: 12 })
Row() {
Text('库存')
.fontSize(11)
.fontColor(GR.sub)
Column().layoutWeight(1)
Text(GR_STICKERS[this.selIdx].stock + ' 件')
.fontSize(13)
.fontColor(GR.text)
}
.width('100%')
.margin({ top: 10 })
Row() {
Text('已售')
.fontSize(11)
.fontColor(GR.sub)
Column().layoutWeight(1)
Text(GR_STICKERS[this.selIdx].sold + ' 件')
.fontSize(13)
.fontColor(GR.primary)
}
.width('100%')
.margin({ top: 6 })
Text('¥' + (GR_STICKERS[this.selIdx].count / 10 + 5))
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
.margin({ top: 12 })
Row({ space: 10 }) {
Text('关闭')
.fontSize(13)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.dark)
.onClick(() => {
this.showDetail = false
})
Text('删除')
.fontSize(13)
.fontColor(Color.White)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.accent)
.onClick(() => {
this.showDetail = false
this.showDel = true
})
}
.width('100%')
.margin({ top: 16 })
}
.width('86%')
.constraintSize({ maxHeight: '80%' })
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.line })
.padding(16)
.zIndex(1000)
}
.width('100%')
.height('100%')
}
if (this.showAdd) {
Stack() {
this.modalOverlay()
Column() {
Row() {
Text('新增贴纸')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.primary)
.layoutWeight(1)
Text('×')
.fontSize(18)
.fontColor(GR.sub)
.onClick(() => {
this.showAdd = false
})
}
.width('100%')
.padding({ bottom: 12 })
.border({ width: { bottom: 1 }, color: GR.line })
Text('名称')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
TextInput({ placeholder: '如:荧光字母贴' })
.height(36)
.fontSize(11)
.fontColor(GR.text)
.backgroundColor(GR.dark)
.borderRadius(8)
.border({ width: 1, color: GR.line })
.margin({ top: 4 })
Text('尺寸')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
TextInput({ placeholder: '如:5x5cm' })
.height(36)
.fontSize(11)
.fontColor(GR.text)
.backgroundColor(GR.dark)
.borderRadius(8)
.border({ width: 1, color: GR.line })
.margin({ top: 4 })
Text('材质')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
Row({ space: 8 }) {
ForEach(['PVC', '全息', '纸质'], (mat: string, i: number) => {
Text(mat)
.fontSize(10)
.fontColor(this.selIdx === i ? GR.dark : GR.sub)
.padding({ left: 14, right: 14, top: 6, bottom: 6 })
.borderRadius(12)
.backgroundColor(this.selIdx === i ? GR.primary : GR.dark)
.border({ width: 1, color: GR.line })
.onClick(() => {
this.selIdx = i
})
}, (mat: string) => mat)
}
.margin({ top: 6 })
Text('数量(枚)')
.fontSize(11)
.fontColor(GR.sub)
.margin({ top: 10 })
TextInput({ placeholder: '如:100' })
.height(36)
.fontSize(11)
.fontColor(GR.text)
.backgroundColor(GR.dark)
.borderRadius(8)
.border({ width: 1, color: GR.line })
.margin({ top: 4 })
Row({ space: 10 }) {
Text('取消')
.fontSize(13)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.dark)
.onClick(() => {
this.showAdd = false
})
Text('确认新增')
.fontSize(13)
.fontColor(Color.Black)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.primary)
.onClick(() => {
this.showAdd = false
})
}
.width('100%')
.margin({ top: 16 })
}
.width('86%')
.constraintSize({ maxHeight: '80%' })
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.line })
.padding(16)
.zIndex(1000)
}
.width('100%')
.height('100%')
}
if (this.showDel) {
Stack() {
this.modalOverlay()
Column() {
Text('确认删除')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(GR.accent)
.textAlign(TextAlign.Center)
.width('100%')
Text(GR_STICKERS[this.selIdx].name)
.fontSize(13)
.fontColor(GR.text)
.margin({ top: 14 })
.textAlign(TextAlign.Center)
.width('100%')
Text('删除后不可恢复')
.fontSize(11)
.fontColor(GR.sub)
.textAlign(TextAlign.Center)
.width('100%')
.margin({ top: 8 })
Row({ space: 10 }) {
Text('取消')
.fontSize(13)
.fontColor(GR.sub)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.dark)
.onClick(() => {
this.showDel = false
})
Text('确认删除')
.fontSize(13)
.fontColor(Color.White)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.borderRadius(10)
.backgroundColor(GR.accent)
.onClick(() => {
this.showDel = false
})
}
.width('100%')
.margin({ top: 20 })
}
.width('80%')
.padding(20)
.backgroundColor(GR.card)
.borderRadius(16)
.border({ width: 1, color: GR.accent })
.zIndex(1000)
}
.width('100%')
.height('100%')
}
}
.width('100%')
.height('100%')
}
}
.
}
总结

本文深入解析了一款基于HarmonyOS ArkTS构建的街头涂鸦艺术装备应用,从暗黑霓虹主题系统、涂鸦风格动画效果、色卡式商品展示到创作社区设计,全方位展示了声明式UI在潮流文化领域的创新应用。该应用以强烈的视觉风格和丰富的动效设计,成功塑造了专业而前卫的涂鸦装备品牌形象。
从技术实现的角度来看,该应用有几个特别值得借鉴的设计思路。首先是主题系统的色彩运用,通过高饱和度的霓虹色与纯黑背景的对比,创造出强烈的视觉冲击力,同时借助多层次的深色(背景、卡片、头部)确保了界面的深度感。其次是动画系统的设计,喷漆扩散、发光呼吸、滴落、闪烁等动画效果不仅增强了视觉吸引力,更成为品牌风格的重要组成部分,让应用在同类产品中脱颖而出。最后是数据可视化的巧妙运用,将覆盖面积、压力等参数以微型进度条的形式融入商品卡片,既提升了信息密度,又保持了界面的整洁。
更多推荐



所有评论(0)