## 一、鸿蒙生态与 ArkTS 声明式 UI 范式的前沿探索 HarmonyOS 6.1.1 作为华为全场景分布式操作系统的最新演进版本,其应用开发框架在跨设备协同、声明式 UI 渲染引擎以及
基于HarmonyOS API 24的ArkTS高空跳伞装备馆伞包头盔选购与基地预约系统深度解析——HarmonyOS 6.1.1天空蓝调UI设计实战指南
高空跳伞作为一项极致刺激的极限运动,吸引着全球无数勇敢者挑战自我。专业的跳伞装备是保障安全、提升体验的核心要素。本文将深入剖析一款以天空蓝为主色调的高空跳伞装备馆应用,从架构设计、数据模型、预约系统到安全展示,全方位呈现HarmonyOS ArkTS声明式UI在专业运动装备领域的应用实践。
该应用以"SKYDIVE PRO"为品牌标识,围绕高空跳伞装备生态构建了首页、伞包、头盔、高度表、伞服、个人中心六大功能模块。采用天空蓝为主色、安全橙为强调色、云白为背景的清爽配色方案,传递出专业、安全、可信赖的品牌形象。技术上采用组件化架构与函数式数据处理,结合丰富的天空元素动画,打造沉浸式的跳伞装备选购体验。
引言

高空跳伞(Skydiving)被誉为"勇敢者的运动",从数千米高空纵身跃下,在自由落体中感受风的速度,在伞花绽放时俯瞰大地的壮丽,这种极致的体验让无数人心驰神往。随着国内跳伞基地的不断增多和跳伞文化的逐步普及,越来越多的人开始尝试这项运动,对专业跳伞装备的需求也日益增长。
本应用正是为跳伞爱好者和专业从业者打造的一站式装备服务平台。它不仅仅是一个装备电商,更是一个集装备选购、基地预约、培训课程、装备知识于一体的综合性服务应用。在视觉设计上,应用采用清爽的天空蓝配色体系,浅灰色背景如同高空的薄雾,深蓝色主色调象征专业与可靠,安全橙色则传递出安全警示与活力。整体风格简洁明快,符合航空运动的科技感与专业感。技术架构上,应用采用模块化的Tab组件设计,每个模块独立维护状态和数据,通过纯函数工具集实现数据过滤、统计和可视化计算。
在交互与功能设计方面,应用充分考虑了跳伞运动的专业特性。伞包页面提供了按类型筛选、面积对比图、格子数分布等专业数据可视化,帮助用户深入了解装备参数;头盔页面以热销榜的形式展示产品,配合安全等级条直观呈现防护能力;首页更是集成了基地预约、装备选配课堂、联名限定预购等特色功能,为用户提供全方位的跳伞服务支持。云朵飘动、自由落体、开伞动画等天空元素的融入,让整个应用充满了高空的自由感。
天空主题系统与数据架构

配色体系设计
应用的主题配色采用了航空专业风格的设计语言,以多层次的蓝色构建主色系,用醒目的安全橙作为强调色,白色和浅灰色构成背景层。这种配色方案既保持了运动应用的活力感,又通过蓝色传递出专业可靠的品牌调性。
interface SjTheme {
bg: string
primary: string
accent: string
card: string
text: string
sub: string
line: string
glow: string
dark: string
}
const SJ: SjTheme = {
bg: '#E8F0F5',
primary: '#0066CC',
accent: '#FF6B1A',
card: '#FFFFFF',
text: '#1A2330',
sub: '#7A8896',
line: '#D8E2EA',
glow: '#4DA8FF',
dark: '#002B5C'
}
安全橙色在航空领域有着特殊的含义——它是国际通用的安全警示色,也是降落伞和救生设备的标准配色。将安全橙用作强调色,不仅在视觉上形成了冷暖对比,更在品牌语义上与跳伞运动的安全属性形成了呼应。这种"功能色"的运用是专业类应用设计中的高级技巧,能够在潜移默化中强化用户的品牌认知。
主题系统中的dark字段使用了深藏蓝色#002B5C,比主色更深更沉,常用于品牌Logo和重要标题,增强视觉分量感。glow字段使用了亮蓝色#4DA8FF,用于发光效果和渐变过渡,模拟天空中光线穿透云层的视觉感受。整个配色体系从浅到深层次分明,为界面设计提供了丰富的色彩选择。
专业数据模型

应用的数据模型围绕跳伞装备的四大核心品类展开:伞包、头盔、高度表和伞服。每个品类的数据接口都经过精心设计,涵盖了该品类最关键的专业参数,体现了应用的专业性。
interface SjCanopy {
name: string
brand: string
type: string
area: number
cells: number
weight: number
deploy: number
stock: number
sold: number
tag: string
}
interface SjHelmet {
name: string
brand: string
type: string
material: string
visor: string
weight: number
safety: number
stock: number
sold: number
}
interface SjAlti {
name: string
brand: string
type: string
accuracy: number
battery: number
gps: number
waterproof: string
stock: number
sold: number
}
interface SjSuit {
name: string
brand: string
material: string
tempMin: number
tempMax: number
weight: number
stock: number
sold: number
}
伞包数据是应用中最复杂的品类,包含了品牌、类型、面积、格子数、重量、开伞速度等关键参数。其中面积(Area)是伞包最重要的参数之一,直接影响降落速度和载荷;格子数(Cells)关系到伞翼的结构和性能;开伞速度(Deploy)则是衡量伞包响应速度的重要指标。头盔数据特别关注安全等级(Safety),用5分制来量化防护能力,这是跳伞头盔选购的核心指标。高度表数据则包含了精度、续航、GPS功能、防水等级等专业参数。
天空元素动画系统

自由落体与开伞动画
应用的动画设计围绕天空主题展开,自由落体动画模拟物体从高空坠落的轨迹,开伞动画则模拟降落伞打开时的逐渐展开效果。这些动画不仅增强了页面的动感,更在语义上与跳伞运动形成了深度关联。
function sjFall(wave: number, i: number): number {
let base: number = (wave + i * 30) % 200
return base - 100
}
function sjOpen(wave: number, i: number): number {
let phase: number = (wave + i * 40) % 100
return 0.3 + phase / 100 * 0.7
}
function sjNeedle(wave: number): number {
return Math.sin(wave / 30) * 60
}
function sjCloud(wave: number, i: number): number {
return ((wave * 2 + i * 50) % 400) - 100
}
开伞动画的设计颇具巧思。它通过缩放比例从0.3逐渐增加到1.0,模拟了降落伞从紧收到完全展开的过程。在实际UI中,将这个函数应用于伞包图标或装饰元素上,能够产生"伞花绽放"的视觉效果,非常贴合跳伞主题。这种主题化的动画设计比通用的淡入淡出更能打动用户,是提升品牌辨识度的有效手段。
sjFall函数模拟自由落体运动,返回值在-100到100之间周期性变化,可用于实现物体上下坠落的动画效果。sjOpen函数模拟开伞过程,缩放比例从0.3线性增长到1.0,形成逐渐展开的视觉效果。sjNeedle函数基于正弦函数计算指针摆动,适用于高度表等仪表类组件的动画。sjCloud函数实现云朵的水平飘移动画,让背景元素呈现缓慢流动的天空景象。
浮动与闪烁效果

除了主题化的特殊动画,应用还包含了通用的浮动和闪烁动画,用于增强界面元素的活力和层次感。
function sjFloatY(wave: number, i: number): number {
return Math.sin(wave / 40 + i) * 8
}
function sjBlink(wave: number, i: number): number {
let v: number = (wave + i * 50) % 200
return v < 100 ? 1.0 : 0.4
}
sjFloatY函数基于正弦函数实现平滑的上下浮动,振幅为8像素,比其他应用的浮动幅度更大,更符合高空自由飘动的感觉。sjBlink函数实现闪烁效果,通过模运算将时间周期分为亮(1.0透明度)和暗(0.4透明度)两个阶段,配合索引偏移实现错峰闪烁。这些动画函数虽然实现简单,但在天空主题的UI中能够产生良好的氛围效果。
主应用架构与首页设计

根组件结构
应用根组件采用Stack + Column的布局结构,白色卡片式头部搭配浅蓝灰色背景,整体视觉清爽明快。根组件中维护了四个首页弹窗的显示状态,分别对应基地预约、装备课堂、联名预购和社区功能。
@Entry
@Component
struct SjApp {
@State tab: string = 'home'
@State showHomeBase: boolean = false
@State showHomeCourse: boolean = false
@State showHomePre: boolean = false
@State showHomeCommunity: boolean = false
@Builder modalOverlay() {
Column() {
Column().width('100%').height('100%').backgroundColor('rgba(0,43,92,0.65)')
}
.width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
.onClick(() => {
this.showHomeBase = false
this.showHomeCourse = false
this.showHomePre = false
this.showHomeCommunity = false
})
}
build() {
Stack() {
Column() {
// 头部品牌栏
Row() {
Column() {
Text('SKYDIVE').fontSize(22).fontWeight(900).fontColor(SJ.dark)
Text('PRO EQUIPMENT').fontSize(10).fontColor(SJ.sub).letterSpacing(2)
}
.alignItems(HorizontalAlign.Start)
Column().layoutWeight(1)
Row() {
Text('4200m').fontSize(12).fontColor(SJ.sub)
Column().width(2).height(12).backgroundColor(SJ.line).margin({ left: 8, right: 8 })
Text('FREE FALL').fontSize(10).fontColor(SJ.accent).fontWeight(700)
}
}
.width('100%').height(56).padding({ left: 16, right: 16 }).backgroundColor(SJ.card)
// ... 搜索栏、内容区、Tab栏
}
.width('100%').height('100%').backgroundColor(SJ.bg)
// ... 首页弹窗
}
}
}
根组件中弹窗管理的设计体现了良好的架构思维。四个首页弹窗共用一个遮罩层Builder,通过@Builder装饰器实现代码复用。遮罩层的点击事件统一处理所有弹窗的关闭操作,避免了在每个弹窗中重复编写关闭逻辑。这种"集中管理、复用代码"的设计模式,在弹窗数量较多时尤为重要,能够有效减少冗余代码,提升可维护性。
品牌头部的设计颇具航空特色,右侧展示了"4200m"和"FREE FALL"两个信息,前者是标准跳伞高度,后者是自由落体的英文标识,用分隔线隔开形成类似仪表盘的视觉效果。这些细节设计在潜移默化中强化了跳伞主题,提升了品牌的专业感。
Tab导航与组件通信

底部Tab栏采用字符串键值而非数字索引来标识当前选中的Tab,这种设计比索引方式更具可读性和可维护性,尤其是在Tab数量较多或顺序可能调整的场景下优势更明显。
const SJ_TABS: SjTab[] = [
{ key: 'home', label: '首页', icon: $r('app.media.layered_image') },
{ key: 'canopy', label: '伞包', icon: $r('app.media.layered_image') },
{ key: 'helmet', label: '头盔', icon: $r('app.media.layered_image') },
{ key: 'alti', label: '高度表', icon: $r('app.media.layered_image') },
{ key: 'suit', label: '伞服', icon: $r('app.media.layered_image') },
{ key: 'mine', label: '我的', icon: $r('app.media.layered_image') }
]
首页Tab与父组件之间通过回调函数进行通信,父组件将弹窗控制函数以参数形式传递给首页组件,首页组件在用户触发相应操作时调用这些回调。这种"自上而下传递控制函数"的模式是ArkTS组件通信的常用方式,它保持了状态的单一数据源(状态在父组件维护),同时允许子组件触发状态变化。
应用功能流程图
伞包Tab:专业数据可视化
双列网格与筛选系统
伞包Tab采用了横向筛选标签 + 双列商品网格 + 数据可视化图表的复合布局。筛选标签支持方型、十字、椭圆三种伞包类型,帮助用户快速定位所需品类。商品卡片采用渐变背景配合开伞动画,视觉效果精美。
@Component
struct SjCanopyTab {
@State wave: number = 0
@State list: SjCanopy[] = []
@State filterTag: string = '全部'
private timer: number = -1
@State showDetail: boolean = false
@State showAdd: boolean = false
@State showEdit: boolean = false
@State showDelete: boolean = false
@State selName: string = ''
aboutToAppear() {
this.list = sjFilterCanopies('全部')
this.timer = setInterval(() => {
this.wave += 1
}, 60)
}
aboutToDisappear() {
if (this.timer !== -1) {
clearInterval(this.timer)
}
}
// ... build方法
}
组件使用filterTag字符串状态来管理筛选条件,比使用数字索引更具语义化。selName状态用于记录当前选中的伞包名称,通过名称而非索引来定位数据,这种方式在数据排序或筛选后仍然能够正确找到目标项,比索引方式更加健壮。
商品卡片设计
伞包商品卡片采用渐变背景设计,从亮蓝色到深蓝色的135度渐变模拟了天空从近到远的视觉效果。卡片中央的白色小圆点应用了开伞动画,仿佛正在绽放的伞花,设计巧妙而富有寓意。
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(this.list, (c: SjCanopy, i: number) => {
Column() {
Stack() {
Column().width('100%').height(80).borderRadius(12)
.linearGradient({ angle: 135, colors: [[SJ.glow, 0], [SJ.primary, 1]] })
Text(c.type).fontSize(11).fontColor('#FFFFFF').fontWeight(700)
Column().width(20).height(20).borderRadius(10).backgroundColor('rgba(255,255,255,0.3)')
.scale({ x: sjOpen(this.wave, i), y: sjOpen(this.wave, i) })
}
.width('100%').height(80)
Text(c.name).fontSize(13).fontWeight(700).fontColor(SJ.text).margin({ top: 8 })
Text(c.brand).fontSize(10).fontColor(SJ.sub).margin({ top: 2 })
Row() {
Text(c.tag).fontSize(10).fontColor(SJ.accent).fontWeight(600)
Column().layoutWeight(1)
Text('库存 ' + c.stock.toString()).fontSize(10).fontColor(SJ.sub)
}
.width('100%').margin({ top: 4 })
Row() {
Text('已售 ').fontSize(10).fontColor(SJ.sub)
Text(c.sold.toString()).fontSize(12).fontColor(SJ.accent).fontWeight(700)
Column().layoutWeight(1)
Text('查看 >').fontSize(10).fontColor(SJ.primary)
}
.width('100%').margin({ top: 4 })
}
.width('48.5%')
.padding(10)
.backgroundColor(SJ.card)
.borderRadius(12)
.margin({ bottom: 8 })
.onClick(() => {
this.selName = c.name
this.showDetail = true
})
}, (c: SjCanopy) => c.name)
}
.width('100%').padding({ left: 12, right: 12 })
渐变背景在商品卡片设计中的运用是一个值得关注的技巧。相比于纯色背景,渐变背景能够提供更丰富的视觉层次和更强的品牌辨识度。在这个案例中,亮蓝到深蓝的渐变不仅美观,更在语义上呼应了"天空"和"高度"的主题,让用户在浏览商品时能够持续感受到品牌氛围。这种"功能性美学"的设计思路,是优秀UI与普通UI的重要区别。
商品卡片的信息布局层次分明:顶部是最醒目的视觉区(渐变背景 + 类型标签 + 开伞动画),中间是产品名称和品牌,底部是标签、库存和销量信息。安全橙色的标签和销量数据有效引导了用户视线,帮助用户快速抓住关键信息。
面积对比图与格子数柱状图
伞包Tab的另一个亮点是两组数据可视化图表:面积对比图和格子数分布柱状图。面积对比图采用横向条形图的形式,直观展示各款伞包的面积差异;格子数柱状图则用垂直柱形展示不同伞包的格子数分布。
// 面积对比图
Column() {
Text('面积对比 (sq.ft)').fontSize(13).fontWeight(700).fontColor(SJ.dark).margin({ bottom: 12 })
ForEach(this.list.slice(0, 5), (c: SjCanopy, i: number) => {
Row() {
Text(c.name.substring(0, 8)).fontSize(10).fontColor(SJ.sub).width(60)
Stack({ alignContent: Alignment.Start }) {
Column().width('100%').height(10).borderRadius(5).backgroundColor(SJ.bg)
Column().width(sjAreaW(c.area).toString() + '%').height(10).borderRadius(5).backgroundColor(SJ.primary)
}
.layoutWeight(1)
Text(c.area.toString()).fontSize(10).fontColor(SJ.text).fontWeight(600).width(32)
}
.width('100%').margin({ bottom: 6 })
}, (c: SjCanopy) => c.name)
}
.width('100%').padding(16).backgroundColor(SJ.card).margin({ top: 8 })
// 格子数柱状图
Column() {
Text('格子数分布').fontSize(13).fontWeight(700).fontColor(SJ.dark).margin({ bottom: 12 })
Row({ space: 4 }) {
ForEach(this.list.slice(0, 6), (c: SjCanopy, i: number) => {
Column() {
Column().width(24).height(sjCellsBarW(c.cells)).borderRadius(4).backgroundColor(SJ.accent)
Text(c.cells.toString()).fontSize(9).fontColor(SJ.sub).margin({ top: 4 })
}
.alignItems(HorizontalAlign.Center)
}, (c: SjCanopy) => c.name)
}
.width('100%').justifyContent(FlexAlign.SpaceAround)
}
.width('100%').padding(16).backgroundColor(SJ.card).margin({ top: 8, bottom: 8 })
这两组图表分别从不同维度展示了伞包的关键参数。面积对比图使用主色(蓝色),格子数柱状图使用强调色(橙色),通过颜色编码帮助用户快速区分不同的数据维度。图表都采用白色卡片背景,与浅灰色的页面背景形成对比,保持了视觉上的层次感。这些专业的数据可视化不仅提升了应用的专业形象,更能帮助用户做出更明智的购买决策。
详情弹窗与CRUD操作
伞包详情弹窗展示了更完整的产品参数,包括面积、格子数、重量、开伞速度、库存、销量等。弹窗底部提供了编辑和删除操作入口,配合新增功能形成了完整的数据管理闭环。
if (this.showDetail) {
Stack() {
this.modalOverlay()
Column() {
Text('伞包详情').fontSize(18).fontWeight(700).fontColor(SJ.dark).margin({ top: 20 })
Text(sjCanopyByName(this.selName).name).fontSize(16).fontWeight(600).fontColor(SJ.text).margin({ top: 8 })
Text(sjCanopyByName(this.selName).brand + ' · ' + sjCanopyByName(this.selName).type + ' · ' + sjCanopyByName(this.selName).tag)
.fontSize(12).fontColor(SJ.sub).margin({ top: 4 })
Column() {
Text('面积').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Stack({ alignContent: Alignment.Start }) {
Column().width('100%').height(8).borderRadius(4).backgroundColor(SJ.bg)
Column().width(sjAreaW(sjCanopyByName(this.selName).area).toString() + '%').height(8).borderRadius(4).backgroundColor(SJ.primary)
}
Text(sjCanopyByName(this.selName).area.toString() + ' sq.ft').fontSize(12).fontColor(SJ.text).fontWeight(600).margin({ top: 4 })
}
.width('100%').margin({ top: 16 })
// ... 格子数、重量、开伞、库存、已售
Row() {
Text().width(28).height(28).borderRadius(14).backgroundColor(SJ.sub).onClick(() => { this.showDetail = false })
Column().layoutWeight(1)
Text('编辑').fontSize(12).fontColor(SJ.card).padding({ left: 14, right: 14, top: 8, bottom: 8 }).borderRadius(16).backgroundColor(SJ.accent)
.onClick(() => { this.showDetail = false; this.showEdit = true })
Text('删除').fontSize(12).fontColor(SJ.card).padding({ left: 14, right: 14, top: 8, bottom: 8 }).borderRadius(16).backgroundColor('#CC3333')
.margin({ left: 8 }).onClick(() => { this.showDetail = false; this.showDelete = true })
}
.width('100%').margin({ top: 16 })
}
.width('86%').backgroundColor(SJ.card).borderRadius(16).padding(20)
.constraintSize({ maxHeight: '80%' }).zIndex(1000)
}
}
详情弹窗的关闭按钮设计成一个灰色圆形按钮,位于弹窗的左下角,这种设计在视觉上更轻量,也更符合移动端的操作习惯。编辑和删除按钮则位于右下角,使用不同的颜色区分操作类型:编辑用橙色(强调色),删除用红色(警示色)。这种"左关闭、右操作"的布局模式符合用户的认知习惯,能够有效减少误操作。
详情弹窗中使用了sjCanopyByName辅助函数根据名称查找伞包数据,这种方式确保了即使列表数据发生变化(如筛选、排序),详情弹窗仍然能够正确显示对应的数据。删除操作会跳转到专门的确认弹窗,避免误删,体现了对数据安全的重视。
头盔Tab:安全等级可视化
热销榜与安全等级条
头盔Tab采用排行榜式的列表布局,从第1名到第8名依次排列。每个头盔项左侧是排名数字和头盔图片,中间是名称、品牌、安全等级条和面罩信息,右侧是销量数据。这种布局突出了头盔的排名和安全属性,契合用户选购头盔时最关注安全性能的心理。
Column() {
Text('头盔热销榜').fontSize(14).fontWeight(700).fontColor(SJ.dark).margin({ bottom: 12 })
ForEach(this.list, (h: SjHelmet, i: number) => {
Row() {
Text(sjTopN(i)).fontSize(20).fontWeight(900).fontColor(i < 3 ? SJ.accent : SJ.sub).width(28)
Column() {
Column().width(56).height(56).borderRadius(12).backgroundColor(SJ.bg)
.border({ width: 1, color: SJ.line })
Text(h.type).fontSize(10).fontColor(SJ.primary).fontWeight(600).margin({ top: 2 })
}
.width(56).alignItems(HorizontalAlign.Center)
Column() {
Text(h.name).fontSize(14).fontWeight(700).fontColor(SJ.text)
Text(h.brand + ' · ' + h.material).fontSize(10).fontColor(SJ.sub).margin({ top: 2 })
// 安全等级条
Row() {
ForEach([1, 2, 3, 4, 5], (n: number) => {
Column().width(16).height(4).borderRadius(2)
.backgroundColor(n <= h.safety ? SJ.accent : SJ.line)
.margin({ right: 2 })
}, (n: number) => n.toString())
}
.margin({ top: 4 })
Row() {
Text(h.visor).fontSize(10).fontColor(SJ.primary).padding({ left: 6, right: 6, top: 2, bottom: 2 }).borderRadius(4).backgroundColor(SJ.bg)
Column().layoutWeight(1)
Text(h.weight.toString() + 'kg').fontSize(10).fontColor(SJ.sub)
}
.width('100%').margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start).layoutWeight(1).padding({ left: 12 })
Column() {
Text('已售').fontSize(10).fontColor(SJ.sub)
Text(h.sold.toString()).fontSize(14).fontWeight(700).fontColor(SJ.accent)
}
.alignItems(HorizontalAlign.End)
}
.width('100%').padding(12).backgroundColor(SJ.card).borderRadius(12).margin({ bottom: 8 })
.onClick(() => {
this.selName = h.name
this.showDetail = true
})
}, (h: SjHelmet) => h.name)
}
.width('100%').padding(16).backgroundColor(SJ.bg)
安全等级条的设计是头盔页面的一大亮点。相比于用数字或星级来表示安全等级,用横向排列的矩形条来展示更具视觉冲击力,也更符合仪表盘式的数据展示习惯。5个小矩形条中,被点亮的数量代表安全等级的高低,橙色的点亮部分在灰色背景上非常醒目。这种设计既直观又富有设计感,是数据可视化的优秀范例。
安全等级条通过循环生成5个小矩形,根据头盔的安全等级值决定哪些矩形被点亮(橙色),哪些保持灰色背景。前三名的排名数字使用安全橙色突出显示,强化了热销产品的视觉权重。整个列表区域使用浅灰色背景,与白色的卡片形成对比,增强了页面的深度感。
技术实现对比表格
| 技术维度 | 实现方案 | 优势 | 性能表现 |
|---|---|---|---|
| 主题系统 | 9字段主题接口 + 天空蓝配色 | 层次分明,专业感强 | 优秀,纯常量引用 |
| 数据定位 | 名称键值 + 查找函数 | 健壮性好,不受排序筛选影响 | 良好,线性查找适用于小数据 |
| 渐变背景 | linearGradient + 135度角 | 视觉层次丰富,主题呼应强 | 优秀,GPU加速渲染 |
| 开伞动画 | 缩放函数 + 半透明白圆 | 寓意巧妙,品牌记忆点强 | 优秀,变换属性高效 |
| 面积对比图 | Stack叠加 + 动态宽度百分比 | 实现简单,数据对比直观 | 优秀,基础布局组合 |
| 安全等级条 | ForEach + 条件背景色 | 可视化程度高,辨识度强 | 优秀,固定数量渲染 |
| 基地预约 | 多步选择 + 选项卡样式 | 流程清晰,操作引导明确 | 良好,状态管理较多 |
| 弹窗系统 | @Builder复用 + Stack叠加 | 代码复用度高,层级清晰 | 良好,多弹窗时注意内存 |
安装DevEco Studio程序

选择目标安装目录:

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

新建一个空白模板:

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

完整代码:
// SJ 前缀 | 天空蓝×安全橙×云白 | 浅色系 | 6 tab
// ============ 接口定义 ============
interface SjTheme {
bg: string
primary: string
accent: string
card: string
text: string
sub: string
line: string
glow: string
dark: string
}
interface SjTab {
key: string
label: string
icon: Resource
}
interface SjCanopy {
name: string
brand: string
type: string
area: number
cells: number
weight: number
deploy: number
stock: number
sold: number
tag: string
}
interface SjHelmet {
name: string
brand: string
type: string
material: string
visor: string
weight: number
safety: number
stock: number
sold: number
}
interface SjAlti {
name: string
brand: string
type: string
accuracy: number
battery: number
gps: number
waterproof: string
stock: number
sold: number
}
interface SjSuit {
name: string
brand: string
material: string
tempMin: number
tempMax: number
weight: number
stock: number
sold: number
}
interface SjOrder {
id: string
item: string
date: string
status: string
amount: number
}
interface SjCollect {
name: string
brand: string
price: number
tag: string
}
interface SjMonth {
month: string
amount: number
}
// ============ 主题常量 ============
const SJ: SjTheme = {
bg: '#E8F0F5',
primary: '#0066CC',
accent: '#FF6B1A',
card: '#FFFFFF',
text: '#1A2330',
sub: '#7A8896',
line: '#D8E2EA',
glow: '#4DA8FF',
dark: '#002B5C'
}
const SJ_TABS: SjTab[] = [
{ key: 'home', label: '首页', icon: $r('app.media.layered_image') },
{ key: 'canopy', label: '伞包', icon: $r('app.media.layered_image') },
{ key: 'helmet', label: '头盔', icon: $r('app.media.layered_image') },
{ key: 'alti', label: '高度表', icon: $r('app.media.layered_image') },
{ key: 'suit', label: '伞服', icon: $r('app.media.layered_image') },
{ key: 'mine', label: '我的', icon: $r('app.media.layered_image') }
]
// ============ 数据:伞包 10 条 ============
const SJ_CANOPIES: SjCanopy[] = [
{ name: 'Sabre3-170', brand: 'PD Performance', type: '方型', area: 170, cells: 9, weight: 2.8, deploy: 3000, stock: 12, sold: 348, tag: '高性能' },
{ name: 'Sigma-135', brand: 'Vector', type: '方型', area: 135, cells: 9, weight: 2.2, deploy: 2700, stock: 8, sold: 412, tag: '竞赛级' },
{ name: 'Stiletto-120', brand: 'PD Performance', type: '十字', area: 120, cells: 9, weight: 2.0, deploy: 2500, stock: 6, sold: 521, tag: '高速' },
{ name: 'Pulse-150', brand: 'Aerodyne', type: '椭圆', area: 150, cells: 7, weight: 2.5, deploy: 2800, stock: 15, sold: 298, tag: '稳定' },
{ name: 'Katana-120', brand: 'PD Performance', type: '十字', area: 120, cells: 9, weight: 2.1, deploy: 2600, stock: 4, sold: 678, tag: '高端' },
{ name: 'Pilot-210', brand: 'Aerodyne', type: '方型', area: 210, cells: 7, weight: 3.2, deploy: 3200, stock: 20, sold: 234, tag: '初学' },
{ name: 'Saffire3-169', brand: 'NZ Aerosports', type: '椭圆', area: 169, cells: 9, weight: 2.6, deploy: 2900, stock: 10, sold: 389, tag: '全能' },
{ name: 'Velo-84', brand: 'PD Performance', type: '十字', area: 84, cells: 9, weight: 1.5, deploy: 2200, stock: 3, sold: 712, tag: '极限' },
{ name: 'Optima-178', brand: 'NZ Aerosports', type: '方型', area: 178, cells: 9, weight: 2.9, deploy: 3100, stock: 9, sold: 301, tag: '教学' },
{ name: 'Hornet-190', brand: 'Aerodyne', type: '椭圆', area: 190, cells: 7, weight: 3.0, deploy: 3300, stock: 14, sold: 256, tag: '安全' }
]
// ============ 数据:头盔 8 条 ============
const SJ_HELMETS: SjHelmet[] = [
{ name: 'G3 Carbon', brand: 'Cookie', type: '全盔', material: '碳纤维', visor: '电变镜', weight: 0.65, safety: 5, stock: 10, sold: 245 },
{ name: 'GK.1 Pro', brand: 'Cookie', type: '全盔', material: '玻璃钢', visor: '透明镜', weight: 0.82, safety: 5, stock: 8, sold: 312 },
{ name: 'FlatTop-CC', brand: 'BoneHead', type: '半盔', material: '碳纤维', visor: '无', weight: 0.48, safety: 4, stock: 15, sold: 198 },
{ name: 'Mamba-X', brand: 'Tonfly', type: '全盔', material: '碳纤维', visor: '电变镜', weight: 0.72, safety: 5, stock: 6, sold: 423 },
{ name: 'Force2', brand: 'BoneHead', type: '半盔', material: '玻璃钢', visor: '无', weight: 0.55, safety: 4, stock: 12, sold: 167 },
{ name: 'Intruder', brand: 'TonFly', type: '全盔', material: '凯夫拉', visor: '彩色镜', weight: 0.78, safety: 5, stock: 7, sold: 289 },
{ name: 'Halo-3', brand: 'Cookie', type: '全盔', material: '碳纤维', visor: '电变镜', weight: 0.68, safety: 5, stock: 9, sold: 356 },
{ name: 'Phantom', brand: 'BoneHead', type: '半盔', material: '聚碳酸酯', visor: '无', weight: 0.42, safety: 3, stock: 18, sold: 134 }
]
// ============ 数据:高度表 8 条 ============
const SJ_ALTIS: SjAlti[] = [
{ name: 'Atlas-S', brand: 'Digital', type: '腕戴式', accuracy: 1, battery: 80, gps: 5, waterproof: '50m', stock: 14, sold: 312 },
{ name: 'Skytrax-Pro', brand: 'FlyTech', type: '腕戴式', accuracy: 1, battery: 120, gps: 5, waterproof: '100m', stock: 8, sold: 445 },
{ name: 'AltiTrack', brand: 'Digital', type: '手持式', accuracy: 2, battery: 60, gps: 3, waterproof: '30m', stock: 16, sold: 234 },
{ name: 'N3-Baro', brand: 'FlyTech', type: '腕戴式', accuracy: 1, battery: 100, gps: 5, waterproof: '50m', stock: 10, sold: 389 },
{ name: 'Optima-A2', brand: 'Aerodyne', type: '听觉式', accuracy: 3, battery: 40, gps: 0, waterproof: '10m', stock: 20, sold: 178 },
{ name: 'ProTrack-3', brand: 'FlyTech', type: '腕戴式', accuracy: 1, battery: 150, gps: 5, waterproof: '100m', stock: 6, sold: 512 },
{ name: 'SkyWatch', brand: 'Digital', type: '手持式', accuracy: 2, battery: 72, gps: 4, waterproof: '50m', stock: 12, sold: 267 },
{ name: 'Alti-Vario', brand: 'Aerodyne', type: '腕戴式', accuracy: 1, battery: 90, gps: 4, waterproof: '30m', stock: 15, sold: 298 }
]
// ============ 数据:伞服 6 条 ============
const SJ_SUITS: SjSuit[] = [
{ name: 'Ghost-Suit', brand: 'TonySuits', material: '尼龙涂层', tempMin: -10, tempMax: 15, weight: 1.8, stock: 8, sold: 234 },
{ name: 'Stealth-2', brand: 'Squirrel', material: '聚酯纤维', tempMin: -5, tempMax: 20, weight: 1.5, stock: 12, sold: 312 },
{ name: 'Aura-Pro', brand: 'TonySuits', material: '三层复合', tempMin: -15, tempMax: 10, weight: 2.1, stock: 6, sold: 289 },
{ name: 'Freak-3', brand: 'Squirrel', material: '尼龙涂层', tempMin: -8, tempMax: 18, weight: 1.6, stock: 10, sold: 345 },
{ name: 'Commando', brand: 'TonySuits', material: '聚氨酯', tempMin: -20, tempMax: 5, weight: 2.4, stock: 4, sold: 178 },
{ name: 'Strix-Lite', brand: 'Squirrel', material: '聚酯纤维', tempMin: 0, tempMax: 25, weight: 1.2, stock: 14, sold: 423 }
]
// ============ 数据:订单 6 条 ============
const SJ_ORDERS: SjOrder[] = [
{ id: 'SJ20260801', item: 'Sigma-135 伞包', date: '08-01', status: '已签收', amount: 26800 },
{ id: 'SJ20260728', item: 'G3 Carbon 头盔', date: '07-28', status: '已签收', amount: 8900 },
{ id: 'SJ20260720', item: 'Skytrax-Pro 高度表', date: '07-20', status: '运输中', amount: 4200 },
{ id: 'SJ20260715', item: 'Stealth-2 伞服', date: '07-15', status: '已签收', amount: 6800 },
{ id: 'SJ20260710', item: 'Katana-120 伞包', date: '07-10', status: '已签收', amount: 32000 },
{ id: 'SJ20260701', item: 'N3-Baro 高度表', date: '07-01', status: '已退换', amount: 3800 }
]
// ============ 数据:收藏 6 条 ============
const SJ_COLLECTS: SjCollect[] = [
{ name: 'Velo-84', brand: 'PD Performance', price: 38000, tag: '极限高速' },
{ name: 'Mamba-X', brand: 'TonFly', price: 12800, tag: '竞赛级' },
{ name: 'ProTrack-3', brand: 'FlyTech', price: 5600, tag: '旗舰' },
{ name: 'Aura-Pro', brand: 'TonySuits', price: 9200, tag: '保暖' },
{ name: 'GK.1 Pro', brand: 'Cookie', price: 9800, tag: '全盔' },
{ name: 'Commando', brand: 'TonySuits', price: 11500, tag: '极寒' }
]
// ============ 数据:月度消费 6 条 ============
const SJ_MONTHS: SjMonth[] = [
{ month: '03月', amount: 26800 },
{ month: '04月', amount: 18900 },
{ month: '05月', amount: 35200 },
{ month: '06月', amount: 22400 },
{ month: '07月', amount: 41800 },
{ month: '08月', amount: 30600 }
]
// ============ 全局纯函数 ============
function sjFall(wave: number, i: number): number {
let base: number = (wave + i * 30) % 200
return base - 100
}
function sjOpen(wave: number, i: number): number {
let phase: number = (wave + i * 40) % 100
return 0.3 + phase / 100 * 0.7
}
function sjNeedle(wave: number): number {
return Math.sin(wave / 30) * 60
}
function sjCloud(wave: number, i: number): number {
return ((wave * 2 + i * 50) % 400) - 100
}
function sjFloatY(wave: number, i: number): number {
return Math.sin(wave / 40 + i) * 8
}
function sjBlink(wave: number, i: number): number {
let v: number = (wave + i * 50) % 200
return v < 100 ? 1.0 : 0.4
}
function sjFilterCanopies(tag: string): SjCanopy[] {
let r: SjCanopy[] = []
for (let i = 0; i < SJ_CANOPIES.length; i++) {
if (tag === '全部' || SJ_CANOPIES[i].tag === tag) {
r.push(SJ_CANOPIES[i])
}
}
return r
}
function sjFilterHelmets(type: string): SjHelmet[] {
let r: SjHelmet[] = []
for (let i = 0; i < SJ_HELMETS.length; i++) {
if (type === '全部' || SJ_HELMETS[i].type === type) {
r.push(SJ_HELMETS[i])
}
}
return r
}
function sjFilterAltis(type: string): SjAlti[] {
let r: SjAlti[] = []
for (let i = 0; i < SJ_ALTIS.length; i++) {
if (type === '全部' || SJ_ALTIS[i].type === type) {
r.push(SJ_ALTIS[i])
}
}
return r
}
function sjSoldRank(): SjCanopy[] {
let r: SjCanopy[] = SJ_CANOPIES.slice()
r.sort((a: SjCanopy, b: SjCanopy) => b.sold - a.sold)
return r.slice(0, 5)
}
function sjAreaW(area: number): number {
return Math.min(area / 210 * 100, 100)
}
function sjCellsBarW(cells: number): number {
return cells / 9 * 100
}
function sjGpsW(gps: number): number {
return gps / 5 * 100
}
function sjBatteryW(battery: number): number {
return Math.min(battery / 150 * 100, 100)
}
function sjTempW(suit: SjSuit): number {
let range: number = suit.tempMax - suit.tempMin
return Math.min(range / 45 * 100, 100)
}
function sjMonthH(amount: number): number {
let max: number = 41800
return Math.max(amount / max * 120, 8)
}
function sjTopN(i: number): string {
return ['1', '2', '3', '4', '5'][i] || ''
}
function sjStatusColor(status: string): string {
if (status === '已签收') {
return SJ.primary
}
if (status === '运输中') {
return SJ.accent
}
if (status === '已退换') {
return '#999999'
}
return SJ.sub
}
function sjCanopyByName(name: string): SjCanopy {
let idx: number = 0
for (let i = 0; i < SJ_CANOPIES.length; i++) {
if (SJ_CANOPIES[i].name === name) {
idx = i
}
}
return SJ_CANOPIES[idx]
}
// ============ 主组件 ============
@Entry
@Component
struct SjApp {
@State tab: string = 'home'
@State showHomeBase: boolean = false
@State showHomeCourse: boolean = false
@State showHomePre: boolean = false
@State showHomeCommunity: boolean = false
@Builder modalOverlay() {
Column() {
Column().width('100%').height('100%').backgroundColor('rgba(0,43,92,0.65)')
}
.width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
.onClick(() => {
this.showHomeBase = false
this.showHomeCourse = false
this.showHomePre = false
this.showHomeCommunity = false
})
}
build() {
Stack() {
Column() {
// 头部品牌栏
Row() {
Column() {
Text('SKYDIVE').fontSize(22).fontWeight(900).fontColor(SJ.dark)
Text('PRO EQUIPMENT').fontSize(10).fontColor(SJ.sub).letterSpacing(2)
}
.alignItems(HorizontalAlign.Start)
Column().layoutWeight(1)
Row() {
Text('4200m').fontSize(12).fontColor(SJ.sub)
Column().width(2).height(12).backgroundColor(SJ.line).margin({ left: 8, right: 8 })
Text('FREE FALL').fontSize(10).fontColor(SJ.accent).fontWeight(700)
}
}
.width('100%').height(56).padding({ left: 16, right: 16 }).backgroundColor(SJ.card)
// 搜索条
Row() {
Row() {
Text('🔍').fontSize(14).fontColor(SJ.sub).margin({ left: 12, right: 8 })
Text('搜索伞包 / 头盔 / 高度表...').fontSize(13).fontColor(SJ.sub)
Column().layoutWeight(1)
Text().width(28).height(28).borderRadius(14).backgroundColor(SJ.primary).onClick(() => {})
}
.width('100%').height(40).backgroundColor(SJ.bg).borderRadius(20).padding({ left: 4, right: 4 })
}
.width('100%').padding({ left: 12, right: 12, top: 8, bottom: 8 })
// tab 内容区
Column() {
if (this.tab === 'home') {
SjHomeTab({
onBase: () => { this.showHomeBase = true },
onCourse: () => { this.showHomeCourse = true },
onPre: () => { this.showHomePre = true },
onCommunity: () => { this.showHomeCommunity = true }
})
} else if (this.tab === 'canopy') {
SjCanopyTab()
} else if (this.tab === 'helmet') {
SjHelmetTab()
} else if (this.tab === 'alti') {
SjAltiTab()
} else if (this.tab === 'suit') {
SjSuitTab()
} else {
SjMineTab()
}
}
.layoutWeight(1)
// 底部 tab 栏
Row() {
ForEach(SJ_TABS, (t: SjTab) => {
Column() {
Text(t.icon).width(20).height(20).fontColor(this.tab === t.key ? SJ.primary : SJ.sub)
Text(t.label).fontSize(10).fontColor(this.tab === t.key ? SJ.primary : SJ.sub).fontWeight(this.tab === t.key ? 700 : 400)
}
.layoutWeight(1)
.padding({ top: 6, bottom: 6 })
.onClick(() => {
this.tab = t.key
})
}, (t: SjTab) => t.key)
}
.width('100%').height(52).backgroundColor(SJ.card)
.border({ width: { top: 1 }, color: SJ.line })
}
.width('100%').height('100%').backgroundColor(SJ.bg)
// 首页弹框
if (this.showHomeBase) {
Stack() {
this.modalOverlay()
Column() {
Text('跳伞基地预约').fontSize(18).fontWeight(700).fontColor(SJ.dark).margin({ top: 20 })
Text('选择您的跳伞目的地').fontSize(12).fontColor(SJ.sub).margin({ top: 4 })
Column() {
Text('基地:').fontSize(13).fontColor(SJ.text).fontWeight(600).margin({ bottom: 8 })
Row() {
Text('罗布泊').fontSize(12).fontColor(SJ.card).padding(8).borderRadius(8).backgroundColor(SJ.primary)
Text('安吉').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
Text('荆门').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
Text('阳江').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
}
.width('100%')
}
.width('100%').margin({ top: 16 })
Column() {
Text('高度:').fontSize(13).fontColor(SJ.text).fontWeight(600).margin({ bottom: 8 })
Row() {
Text('4000m').fontSize(12).fontColor(SJ.card).padding(8).borderRadius(8).backgroundColor(SJ.accent)
Text('3000m').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
Text('2500m').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
}
.width('100%')
}
.width('100%').margin({ top: 12 })
Column() {
Text('日期:').fontSize(13).fontColor(SJ.text).fontWeight(600).margin({ bottom: 8 })
Row() {
Text('08-25').fontSize(12).fontColor(SJ.card).padding(8).borderRadius(8).backgroundColor(SJ.primary)
Text('08-26').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
Text('08-27').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
}
.width('100%')
}
.width('100%').margin({ top: 12 })
Row() {
Text('预约费:').fontSize(13).fontColor(SJ.text).fontWeight(600)
Text('¥880').fontSize(18).fontWeight(800).fontColor(SJ.accent)
Column().layoutWeight(1)
Text('立即预约').fontSize(13).fontColor(SJ.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(SJ.primary)
}
.width('100%').margin({ top: 20 })
}
.width('86%').backgroundColor(SJ.card).borderRadius(16).padding(20)
.constraintSize({ maxHeight: '80%' }).zIndex(1000)
}
.width('100%').height('100%')
}
if (this.showHomeCourse) {
Stack() {
this.modalOverlay()
Column() {
Text('装备选配课堂').fontSize(18).fontWeight(700).fontColor(SJ.dark).margin({ top: 20 })
Text('专业教练为您讲解装备知识').fontSize(12).fontColor(SJ.sub).margin({ top: 4 })
Column() {
Text('课程一:伞包选配入门').fontSize(13).fontColor(SJ.text).fontWeight(600).margin({ bottom: 4 })
Text('讲师:张教练 | 2小时 | ¥380').fontSize(11).fontColor(SJ.sub)
}
.width('100%').padding(12).backgroundColor(SJ.bg).borderRadius(12).margin({ top: 16 })
Column() {
Text('课程二:高速度降落技巧').fontSize(13).fontColor(SJ.text).fontWeight(600).margin({ bottom: 4 })
Text('讲师:王教练 | 3小时 | ¥580').fontSize(11).fontColor(SJ.sub)
}
.width('100%').padding(12).backgroundColor(SJ.bg).borderRadius(12).margin({ top: 8 })
Column() {
Text('课程三:装备维护保养').fontSize(13).fontColor(SJ.text).fontWeight(600).margin({ bottom: 4 })
Text('讲师:李教练 | 1.5小时 | ¥280').fontSize(11).fontColor(SJ.sub)
}
.width('100%').padding(12).backgroundColor(SJ.bg).borderRadius(12).margin({ top: 8 })
Row() {
Text().width(28).height(28).borderRadius(14).backgroundColor(SJ.accent).onClick(() => { this.showHomeCourse = false })
Column().layoutWeight(1)
Text('预约全部').fontSize(13).fontColor(SJ.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(SJ.primary)
}
.width('100%').margin({ top: 20 })
}
.width('86%').backgroundColor(SJ.card).borderRadius(16).padding(20)
.constraintSize({ maxHeight: '80%' }).zIndex(1000)
}
.width('100%').height('100%')
}
if (this.showHomePre) {
Stack() {
this.modalOverlay()
Column() {
Text('联名限定预购').fontSize(18).fontWeight(700).fontColor(SJ.dark).margin({ top: 20 })
Text('PD × SKYDIVE 联名款').fontSize(12).fontColor(SJ.sub).margin({ top: 4 })
Row() {
ForEach(['#0066CC', '#FF6B1A', '#4DA8FF', '#002B5C'], (c: string) => {
Column().width(36).height(36).borderRadius(18).backgroundColor(c).margin({ left: 6, right: 6 })
}, (c: string, i: number) => `${c}_${i}`)
}
.margin({ top: 16 })
Column() {
Text('配色选择').fontSize(12).fontColor(SJ.sub).margin({ bottom: 8 })
Row() {
Text('天空蓝').fontSize(12).fontColor(SJ.card).padding(8).borderRadius(8).backgroundColor(SJ.primary)
Text('安全橙').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
Text('荧光蓝').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
Text('深蓝').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
}
}
.width('100%').margin({ top: 16 })
Row() {
Text('预购价:').fontSize(13).fontColor(SJ.text).fontWeight(600)
Text('¥36,800').fontSize(20).fontWeight(800).fontColor(SJ.accent)
Column().layoutWeight(1)
Text('付定金').fontSize(13).fontColor(SJ.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(SJ.accent)
}
.width('100%').margin({ top: 20 })
}
.width('86%').backgroundColor(SJ.card).borderRadius(16).padding(20)
.constraintSize({ maxHeight: '80%' }).zIndex(1000)
}
.width('100%').height('100%')
}
if (this.showHomeCommunity) {
Stack() {
this.modalOverlay()
Column() {
Text('跳伞社区').fontSize(18).fontWeight(700).fontColor(SJ.dark).margin({ top: 20 })
Text('与全球跳伞爱好者交流').fontSize(12).fontColor(SJ.sub).margin({ top: 4 })
Column() {
Text('"今天在罗布泊4000米自由落体,太刺激了!"').fontSize(13).fontColor(SJ.text).margin({ bottom: 4 })
Text('@SkyDiver_张 | 2小时前').fontSize(11).fontColor(SJ.sub)
}
.width('100%').padding(12).backgroundColor(SJ.bg).borderRadius(12).margin({ top: 16 })
Column() {
Text('"新入手Katana-120,降落操控丝滑,强烈推荐"').fontSize(13).fontColor(SJ.text).margin({ bottom: 4 })
Text('@FreeFall_李 | 5小时前').fontSize(11).fontColor(SJ.sub)
}
.width('100%').padding(12).backgroundColor(SJ.bg).borderRadius(12).margin({ top: 8 })
Column() {
Text('"ProTrack-3数据记录非常精准,复盘很方便"').fontSize(13).fontColor(SJ.text).margin({ bottom: 4 })
Text('@Altitude_王 | 1天前').fontSize(11).fontColor(SJ.sub)
}
.width('100%').padding(12).backgroundColor(SJ.bg).borderRadius(12).margin({ top: 8 })
Row() {
Text().width(28).height(28).borderRadius(14).backgroundColor(SJ.sub).onClick(() => { this.showHomeCommunity = false })
Column().layoutWeight(1)
Text('加入社区').fontSize(13).fontColor(SJ.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(SJ.primary)
}
.width('100%').margin({ top: 20 })
}
.width('86%').backgroundColor(SJ.card).borderRadius(16).padding(20)
.constraintSize({ maxHeight: '80%' }).zIndex(1000)
}
.width('100%').height('100%')
}
}
.width('100%').height('100%')
}
}
// ============ Tab 1: 首页 ============
@Component
struct SjHomeTab {
@State wave: number = 0
@State canopyList: SjCanopy[] = []
@State rankList: SjCanopy[] = []
private timer: number = -1
@State showDetail: boolean = false
@State selName: string = ''
onBase: () => void = () => {}
onCourse: () => void = () => {}
onPre: () => void = () => {}
onCommunity: () => void = () => {}
aboutToAppear() {
this.canopyList = SJ_CANOPIES.slice(0, 4)
this.rankList = sjSoldRank()
this.timer = setInterval(() => {
this.wave += 1
}, 60)
}
aboutToDisappear() {
if (this.timer !== -1) {
clearInterval(this.timer)
}
}
@Builder modalOverlay() {
Column() {
Column().width('100%').height('100%').backgroundColor('rgba(0,43,92,0.65)')
}
.width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
.onClick(() => {
this.showDetail = false
})
}
build() {
Stack() {
Scroll() {
Column() {
// 下坠动画 banner
Stack() {
Column().width('100%').height(120).linearGradient({ angle: 135, colors: [[SJ.primary, 0], [SJ.glow, 1]] })
Column() {
Text('FREE FALL').fontSize(28).fontWeight(900).fontColor('#FFFFFF').letterSpacing(3)
Text('4200m 高空跳伞装备甄选').fontSize(12).fontColor('rgba(255,255,255,0.8)').margin({ top: 4 })
}
.alignItems(HorizontalAlign.Center)
.margin({ top: 20 })
// 下坠效果圆点
Row() {
ForEach([0, 1, 2, 3, 4, 5], (i: number) => {
Column().width(6).height(6).borderRadius(3).backgroundColor('rgba(255,255,255,0.7)')
.position({ x: 60 + i * 55, y: sjFall(this.wave, i) + 100 })
}, (i: number) => i.toString())
}
.width('100%').height(120)
}
.width('100%').height(120)
// 高度仪表盘
Row() {
Stack() {
Column().width(100).height(100).borderRadius(50).border({ width: 4, color: SJ.line })
Column().width(80).height(80).borderRadius(40).backgroundColor(SJ.card)
Column().width(4).height(40).backgroundColor(SJ.accent).borderRadius(2)
.position({ x: 48, y: 10 })
.rotate({ angle: sjNeedle(this.wave), centerX: '50%', centerY: '100%' })
Text('ALT').fontSize(10).fontColor(SJ.sub).position({ x: 36, y: 62 })
Text((4200 + Math.floor(sjNeedle(this.wave) * 30)).toString() + 'm')
.fontSize(14).fontWeight(700).fontColor(SJ.dark).position({ x: 24, y: 74 })
}
.width(110).height(110)
Column() {
Text('当前高度').fontSize(11).fontColor(SJ.sub)
Text('4200m').fontSize(20).fontWeight(800).fontColor(SJ.primary).margin({ top: 4 })
Text('自由落体时间 60s').fontSize(11).fontColor(SJ.sub).margin({ top: 4 })
Text('开伞高度 1000m').fontSize(11).fontColor(SJ.sub).margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start).layoutWeight(1).padding({ left: 12 })
}
.width('100%').padding(16).backgroundColor(SJ.card).margin({ top: 8 })
// 4 格统计卡
Row() {
Column() {
Text('238').fontSize(22).fontWeight(800).fontColor(SJ.primary)
Text('伞包').fontSize(11).fontColor(SJ.sub).margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center).padding({ top: 12, bottom: 12 })
Column().width(1).height(36).backgroundColor(SJ.line)
Column() {
Text('156').fontSize(22).fontWeight(800).fontColor(SJ.accent)
Text('头盔').fontSize(11).fontColor(SJ.sub).margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center).padding({ top: 12, bottom: 12 })
Column().width(1).height(36).backgroundColor(SJ.line)
Column() {
Text('89').fontSize(22).fontWeight(800).fontColor(SJ.primary)
Text('高度表').fontSize(11).fontColor(SJ.sub).margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center).padding({ top: 12, bottom: 12 })
Column().width(1).height(36).backgroundColor(SJ.line)
Column() {
Text('67').fontSize(22).fontWeight(800).fontColor(SJ.accent)
Text('伞服').fontSize(11).fontColor(SJ.sub).margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center).padding({ top: 12, bottom: 12 })
}
.width('100%').backgroundColor(SJ.card).margin({ top: 8 })
// 功能宫格
Column() {
Text('快捷功能').fontSize(14).fontWeight(700).fontColor(SJ.dark).margin({ bottom: 12 })
Row() {
Column() {
Column().width(44).height(44).borderRadius(22).backgroundColor(SJ.primary)
Text('预约').fontSize(11).fontColor(SJ.text).margin({ top: 4 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
.onClick(() => {
this.onBase()
})
Column() {
Column().width(44).height(44).borderRadius(22).backgroundColor(SJ.accent)
Text('课堂').fontSize(11).fontColor(SJ.text).margin({ top: 4 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
.onClick(() => {
this.onCourse()
})
Column() {
Column().width(44).height(44).borderRadius(22).backgroundColor(SJ.glow)
Text('预购').fontSize(11).fontColor(SJ.text).margin({ top: 4 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
.onClick(() => {
this.onPre()
})
Column() {
Column().width(44).height(44).borderRadius(22).backgroundColor(SJ.dark)
Text('社区').fontSize(11).fontColor(SJ.text).margin({ top: 4 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
.onClick(() => {
this.onCommunity()
})
}
.width('100%')
}
.width('100%').padding(16).backgroundColor(SJ.card).margin({ top: 8 })
// 热销榜单
Column() {
Row() {
Text('热销榜单').fontSize(14).fontWeight(700).fontColor(SJ.dark)
Column().layoutWeight(1)
Text('查看全部').fontSize(12).fontColor(SJ.sub)
}
.width('100%').margin({ bottom: 12 })
ForEach(this.rankList, (c: SjCanopy, i: number) => {
Row() {
Text(sjTopN(i)).fontSize(18).fontWeight(900).fontColor(i < 3 ? SJ.accent : SJ.sub).width(28)
Column() {
Text(c.name).fontSize(14).fontWeight(600).fontColor(SJ.text)
Text(c.brand + ' · ' + c.type).fontSize(11).fontColor(SJ.sub).margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
Column() {
Text('已售 ' + c.sold.toString()).fontSize(12).fontColor(SJ.accent).fontWeight(700)
Text('库存 ' + c.stock.toString()).fontSize(10).fontColor(SJ.sub).margin({ top: 2 })
}
.alignItems(HorizontalAlign.End)
}
.width('100%').padding({ top: 8, bottom: 8 })
.border({ width: { bottom: 1 }, color: SJ.line })
.onClick(() => {
this.selName = c.name
this.showDetail = true
})
}, (c: SjCanopy) => c.name)
}
.width('100%').padding(16).backgroundColor(SJ.card).margin({ top: 8 })
// 联名限定横滑
Column() {
Text('联名限定').fontSize(14).fontWeight(700).fontColor(SJ.dark).margin({ bottom: 8 })
Scroll() {
Row({ space: 12 }) {
ForEach(SJ_CANOPIES.slice(5, 10), (c: SjCanopy) => {
Column() {
Column().width(140).height(80).borderRadius(12)
.linearGradient({ angle: 135, colors: [[SJ.primary, 0], [SJ.glow, 1]] })
.padding(12)
Text(c.name).fontSize(12).fontWeight(600).fontColor(SJ.text).margin({ top: 6 })
Text(c.tag).fontSize(10).fontColor(SJ.accent).margin({ top: 2 })
}
.width(140).alignItems(HorizontalAlign.Start)
}, (c: SjCanopy) => c.name)
}
.padding({ left: 4, right: 4 })
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
}
.width('100%').padding(16).backgroundColor(SJ.card).margin({ top: 8, bottom: 8 })
}
.width('100%')
}
.scrollBar(BarState.Off)
.layoutWeight(1)
.width('100%')
// 伞包详情弹框
if (this.showDetail) {
Stack() {
this.modalOverlay()
Column() {
Text('装备详情').fontSize(18).fontWeight(700).fontColor(SJ.dark).margin({ top: 20 })
Text(sjCanopyByName(this.selName).name).fontSize(16).fontWeight(600).fontColor(SJ.text).margin({ top: 8 })
Text(sjCanopyByName(this.selName).brand + ' · ' + sjCanopyByName(this.selName).type).fontSize(12).fontColor(SJ.sub).margin({ top: 4 })
Column() {
Text('面积 (sq.ft)').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Stack({ alignContent: Alignment.Start }) {
Column().width('100%').height(8).borderRadius(4).backgroundColor(SJ.bg)
Column().width(sjAreaW(sjCanopyByName(this.selName).area).toString() + '%').height(8).borderRadius(4).backgroundColor(SJ.primary)
}
.width('100%')
Text(sjCanopyByName(this.selName).area.toString() + ' sq.ft').fontSize(12).fontColor(SJ.text).fontWeight(600).margin({ top: 4 })
}
.width('100%').margin({ top: 16 })
Column() {
Text('格子数').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Stack({ alignContent: Alignment.Start }) {
Column().width('100%').height(8).borderRadius(4).backgroundColor(SJ.bg)
Column().width(sjCellsBarW(sjCanopyByName(this.selName).cells).toString() + '%').height(8).borderRadius(4).backgroundColor(SJ.accent)
}
.width('100%')
Text(sjCanopyByName(this.selName).cells.toString() + ' cells').fontSize(12).fontColor(SJ.text).fontWeight(600).margin({ top: 4 })
}
.width('100%').margin({ top: 12 })
Row() {
Column() {
Text('重量').fontSize(11).fontColor(SJ.sub)
Text(sjCanopyByName(this.selName).weight.toString() + 'kg').fontSize(14).fontWeight(700).fontColor(SJ.text)
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() {
Text('开伞速度').fontSize(11).fontColor(SJ.sub)
Text(sjCanopyByName(this.selName).deploy.toString() + 'km/h').fontSize(14).fontWeight(700).fontColor(SJ.text)
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() {
Text('库存').fontSize(11).fontColor(SJ.sub)
Text(sjCanopyByName(this.selName).stock.toString()).fontSize(14).fontWeight(700).fontColor(SJ.text)
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() {
Text('已售').fontSize(11).fontColor(SJ.sub)
Text(sjCanopyByName(this.selName).sold.toString()).fontSize(14).fontWeight(700).fontColor(SJ.accent)
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
}
.width('100%').padding(12).backgroundColor(SJ.bg).borderRadius(12).margin({ top: 12 })
Row() {
Text().width(28).height(28).borderRadius(14).backgroundColor(SJ.sub).onClick(() => { this.showDetail = false })
Column().layoutWeight(1)
Text('加入购物车').fontSize(13).fontColor(SJ.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(SJ.primary)
}
.width('100%').margin({ top: 16 })
}
.width('86%').backgroundColor(SJ.card).borderRadius(16).padding(20)
.constraintSize({ maxHeight: '80%' }).zIndex(1000)
}
.width('100%').height('100%')
}
}
.width('100%').height('100%')
}
}
// ============ Tab 2: 伞包 ============
@Component
struct SjCanopyTab {
@State wave: number = 0
@State list: SjCanopy[] = []
@State filterTag: string = '全部'
private timer: number = -1
@State showDetail: boolean = false
@State showAdd: boolean = false
@State showEdit: boolean = false
@State showDelete: boolean = false
@State selName: string = ''
aboutToAppear() {
this.list = sjFilterCanopies('全部')
this.timer = setInterval(() => {
this.wave += 1
}, 60)
}
aboutToDisappear() {
if (this.timer !== -1) {
clearInterval(this.timer)
}
}
@Builder modalOverlay() {
Column() {
Column().width('100%').height('100%').backgroundColor('rgba(0,43,92,0.65)')
}
.width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
.onClick(() => {
this.showDetail = false
this.showAdd = false
this.showEdit = false
this.showDelete = false
})
}
build() {
Stack() {
Scroll() {
Column() {
// 筛选 chips
Scroll() {
Row({ space: 8 }) {
ForEach(['全部', '方型', '十字', '椭圆'], (t: string) => {
Text(t).fontSize(12).fontColor(this.filterTag === t ? SJ.card : SJ.text)
.padding({ left: 16, right: 16, top: 6, bottom: 6 })
.borderRadius(16)
.backgroundColor(this.filterTag === t ? SJ.primary : SJ.bg)
.onClick(() => {
this.filterTag = t
this.list = sjFilterCanopies(t)
})
}, (t: string) => t)
}
.padding({ left: 12, right: 12 })
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%')
.height(36)
// 双列网格
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(this.list, (c: SjCanopy, i: number) => {
Column() {
Stack() {
Column().width('100%').height(80).borderRadius(12)
.linearGradient({ angle: 135, colors: [[SJ.glow, 0], [SJ.primary, 1]] })
Text(c.type).fontSize(11).fontColor('#FFFFFF').fontWeight(700)
Column().width(20).height(20).borderRadius(10).backgroundColor('rgba(255,255,255,0.3)')
.scale({ x: sjOpen(this.wave, i), y: sjOpen(this.wave, i) })
}
.width('100%').height(80)
Text(c.name).fontSize(13).fontWeight(700).fontColor(SJ.text).margin({ top: 8 })
Text(c.brand).fontSize(10).fontColor(SJ.sub).margin({ top: 2 })
Row() {
Text(c.tag).fontSize(10).fontColor(SJ.accent).fontWeight(600)
Column().layoutWeight(1)
Text('库存 ' + c.stock.toString()).fontSize(10).fontColor(SJ.sub)
}
.width('100%').margin({ top: 4 })
Row() {
Text('已售 ').fontSize(10).fontColor(SJ.sub)
Text(c.sold.toString()).fontSize(12).fontColor(SJ.accent).fontWeight(700)
Column().layoutWeight(1)
Text('查看 >').fontSize(10).fontColor(SJ.primary)
}
.width('100%').margin({ top: 4 })
}
.width('48.5%')
.padding(10)
.backgroundColor(SJ.card)
.borderRadius(12)
.margin({ bottom: 8 })
.onClick(() => {
this.selName = c.name
this.showDetail = true
})
}, (c: SjCanopy) => c.name)
}
.width('100%').padding({ left: 12, right: 12 })
// 面积对比图
Column() {
Text('面积对比 (sq.ft)').fontSize(13).fontWeight(700).fontColor(SJ.dark).margin({ bottom: 12 })
ForEach(this.list.slice(0, 5), (c: SjCanopy, i: number) => {
Row() {
Text(c.name.substring(0, 8)).fontSize(10).fontColor(SJ.sub).width(60)
Stack({ alignContent: Alignment.Start }) {
Column().width('100%').height(10).borderRadius(5).backgroundColor(SJ.bg)
Column().width(sjAreaW(c.area).toString() + '%').height(10).borderRadius(5).backgroundColor(SJ.primary)
}
.layoutWeight(1)
Text(c.area.toString()).fontSize(10).fontColor(SJ.text).fontWeight(600).width(32)
}
.width('100%').margin({ bottom: 6 })
}, (c: SjCanopy) => c.name)
}
.width('100%').padding(16).backgroundColor(SJ.card).margin({ top: 8 })
// 格子数柱状图
Column() {
Text('格子数分布').fontSize(13).fontWeight(700).fontColor(SJ.dark).margin({ bottom: 12 })
Row({ space: 4 }) {
ForEach(this.list.slice(0, 6), (c: SjCanopy, i: number) => {
Column() {
Column().width(24).height(sjCellsBarW(c.cells)).borderRadius(4).backgroundColor(SJ.accent)
Text(c.cells.toString()).fontSize(9).fontColor(SJ.sub).margin({ top: 4 })
}
.alignItems(HorizontalAlign.Center)
}, (c: SjCanopy) => c.name)
}
.width('100%').justifyContent(FlexAlign.SpaceAround)
}
.width('100%').padding(16).backgroundColor(SJ.card).margin({ top: 8, bottom: 8 })
}
.width('100%')
}
.scrollBar(BarState.Off)
.layoutWeight(1)
.width('100%')
// 伞包详情弹框
if (this.showDetail) {
Stack() {
this.modalOverlay()
Column() {
Text('伞包详情').fontSize(18).fontWeight(700).fontColor(SJ.dark).margin({ top: 20 })
Text(sjCanopyByName(this.selName).name).fontSize(16).fontWeight(600).fontColor(SJ.text).margin({ top: 8 })
Text(sjCanopyByName(this.selName).brand + ' · ' + sjCanopyByName(this.selName).type + ' · ' + sjCanopyByName(this.selName).tag)
.fontSize(12).fontColor(SJ.sub).margin({ top: 4 })
Column() {
Text('面积').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Stack({ alignContent: Alignment.Start }) {
Column().width('100%').height(8).borderRadius(4).backgroundColor(SJ.bg)
Column().width(sjAreaW(sjCanopyByName(this.selName).area).toString() + '%').height(8).borderRadius(4).backgroundColor(SJ.primary)
}
Text(sjCanopyByName(this.selName).area.toString() + ' sq.ft').fontSize(12).fontColor(SJ.text).fontWeight(600).margin({ top: 4 })
}
.width('100%').margin({ top: 16 })
Column() {
Text('格子数').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Stack({ alignContent: Alignment.Start }) {
Column().width('100%').height(8).borderRadius(4).backgroundColor(SJ.bg)
Column().width(sjCellsBarW(sjCanopyByName(this.selName).cells).toString() + '%').height(8).borderRadius(4).backgroundColor(SJ.accent)
}
Text(sjCanopyByName(this.selName).cells.toString() + ' cells').fontSize(12).fontColor(SJ.text).fontWeight(600).margin({ top: 4 })
}
.width('100%').margin({ top: 12 })
Row() {
Column() { Text('重量').fontSize(11).fontColor(SJ.sub); Text(sjCanopyByName(this.selName).weight.toString() + 'kg').fontSize(14).fontWeight(700).fontColor(SJ.text) }
.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() { Text('开伞').fontSize(11).fontColor(SJ.sub); Text(sjCanopyByName(this.selName).deploy.toString() + 'km/h').fontSize(14).fontWeight(700).fontColor(SJ.text) }
.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() { Text('库存').fontSize(11).fontColor(SJ.sub); Text(sjCanopyByName(this.selName).stock.toString()).fontSize(14).fontWeight(700).fontColor(SJ.text) }
.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() { Text('已售').fontSize(11).fontColor(SJ.sub); Text(sjCanopyByName(this.selName).sold.toString()).fontSize(14).fontWeight(700).fontColor(SJ.accent) }
.layoutWeight(1).alignItems(HorizontalAlign.Center)
}
.width('100%').padding(12).backgroundColor(SJ.bg).borderRadius(12).margin({ top: 12 })
Row() {
Text().width(28).height(28).borderRadius(14).backgroundColor(SJ.sub).onClick(() => { this.showDetail = false })
Column().layoutWeight(1)
Text('编辑').fontSize(12).fontColor(SJ.card).padding({ left: 14, right: 14, top: 8, bottom: 8 }).borderRadius(16).backgroundColor(SJ.accent)
.onClick(() => { this.showDetail = false; this.showEdit = true })
Text('删除').fontSize(12).fontColor(SJ.card).padding({ left: 14, right: 14, top: 8, bottom: 8 }).borderRadius(16).backgroundColor('#CC3333')
.margin({ left: 8 }).onClick(() => { this.showDetail = false; this.showDelete = true })
}
.width('100%').margin({ top: 16 })
}
.width('86%').backgroundColor(SJ.card).borderRadius(16).padding(20)
.constraintSize({ maxHeight: '80%' }).zIndex(1000)
}
.width('100%').height('100%')
}
// 新增伞包弹框
if (this.showAdd) {
Stack() {
this.modalOverlay()
Column() {
Text('新增伞包').fontSize(18).fontWeight(700).fontColor(SJ.dark).margin({ top: 20 })
Column() {
Text('名称').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Row() {
Text('请输入伞包名称').fontSize(12).fontColor(SJ.sub)
Column().layoutWeight(1)
}
.width('100%').height(36).backgroundColor(SJ.bg).borderRadius(8).padding({ left: 12, right: 12 })
}
.width('100%').margin({ top: 16 })
Column() {
Text('品牌').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Row() {
Text('PD').fontSize(12).fontColor(SJ.card).padding(8).borderRadius(8).backgroundColor(SJ.primary)
Text('Vector').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
Text('Aerodyne').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
Text('NZ').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
}
}
.width('100%').margin({ top: 12 })
Column() {
Text('类型').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Row() {
Text('方型').fontSize(12).fontColor(SJ.card).padding(8).borderRadius(8).backgroundColor(SJ.primary)
Text('十字').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
Text('椭圆').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
}
}
.width('100%').margin({ top: 12 })
Column() {
Text('面积 (sq.ft)').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Row() {
Text('120').fontSize(16).fontWeight(700).fontColor(SJ.text)
Column().layoutWeight(1)
Text('170').fontSize(16).fontWeight(700).fontColor(SJ.text)
}
Stack({ alignContent: Alignment.Start }) {
Column().width('100%').height(6).borderRadius(3).backgroundColor(SJ.bg)
Column().width('50%').height(6).borderRadius(3).backgroundColor(SJ.primary)
}
}
.width('100%').margin({ top: 12 })
Row() {
Text().width(28).height(28).borderRadius(14).backgroundColor(SJ.sub).onClick(() => { this.showAdd = false })
Column().layoutWeight(1)
Text('确认新增').fontSize(13).fontColor(SJ.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(SJ.primary)
.onClick(() => { this.showAdd = false })
}
.width('100%').margin({ top: 20 })
}
.width('86%').backgroundColor(SJ.card).borderRadius(16).padding(20)
.constraintSize({ maxHeight: '80%' }).zIndex(1000)
}
.width('100%').height('100%')
}
// 编辑弹框
if (this.showEdit) {
Stack() {
this.modalOverlay()
Column() {
Text('编辑伞包').fontSize(18).fontWeight(700).fontColor(SJ.dark).margin({ top: 20 })
Text('当前:' + this.selName).fontSize(12).fontColor(SJ.sub).margin({ top: 8 })
Column() {
Text('修改名称').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Row() {
Text(this.selName).fontSize(12).fontColor(SJ.text)
Column().layoutWeight(1)
}
.width('100%').height(36).backgroundColor(SJ.bg).borderRadius(8).padding({ left: 12, right: 12 })
}
.width('100%').margin({ top: 16 })
Column() {
Text('修改面积').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Row() {
Text(sjCanopyByName(this.selName).area.toString()).fontSize(12).fontColor(SJ.text)
Column().layoutWeight(1)
Text('150').fontSize(12).fontColor(SJ.accent)
}
.width('100%').height(36).backgroundColor(SJ.bg).borderRadius(8).padding({ left: 12, right: 12 })
}
.width('100%').margin({ top: 12 })
Column() {
Text('修改类型').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Row() {
Text('方型').fontSize(12).fontColor(SJ.card).padding(8).borderRadius(8).backgroundColor(SJ.accent)
Text('十字').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
Text('椭圆').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
}
}
.width('100%').margin({ top: 12 })
Row() {
Text().width(28).height(28).borderRadius(14).backgroundColor(SJ.sub).onClick(() => { this.showEdit = false })
Column().layoutWeight(1)
Text('保存修改').fontSize(13).fontColor(SJ.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(SJ.primary)
.onClick(() => { this.showEdit = false })
}
.width('100%').margin({ top: 20 })
}
.width('86%').backgroundColor(SJ.card).borderRadius(16).padding(20)
.constraintSize({ maxHeight: '80%' }).zIndex(1000)
}
.width('100%').height('100%')
}
// 删除弹框
if (this.showDelete) {
Stack() {
this.modalOverlay()
Column() {
Text('确认删除').fontSize(18).fontWeight(700).fontColor('#CC3333').margin({ top: 20 })
Text('删除后不可恢复,请谨慎操作').fontSize(12).fontColor(SJ.sub).margin({ top: 8 })
Column() {
Text('删除目标').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Text(this.selName).fontSize(16).fontWeight(700).fontColor(SJ.text)
}
.width('100%').padding(16).backgroundColor('#FFF5F5').borderRadius(12)
.border({ width: 1, color: '#FFCCCC' })
.margin({ top: 16 })
Row() {
Text().width(28).height(28).borderRadius(14).backgroundColor(SJ.sub).onClick(() => { this.showDelete = false })
Column().layoutWeight(1)
Text('取消').fontSize(13).fontColor(SJ.text).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(SJ.bg)
.onClick(() => { this.showDelete = false })
Text('确认删除').fontSize(13).fontColor('#FFFFFF').padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor('#CC3333')
.margin({ left: 8 }).onClick(() => { this.showDelete = false })
}
.width('100%').margin({ top: 20 })
}
.width('86%').backgroundColor(SJ.card).borderRadius(16).padding(20)
.constraintSize({ maxHeight: '80%' }).zIndex(1000)
}
.width('100%').height('100%')
}
}
.width('100%').height('100%')
}
}
// ============ Tab 3: 头盔 ============
@Component
struct SjHelmetTab {
@State wave: number = 0
@State list: SjHelmet[] = []
@State filterType: string = '全部'
private timer: number = -1
@State showDetail: boolean = false
@State showAdd: boolean = false
@State showEdit: boolean = false
@State selName: string = ''
aboutToAppear() {
this.list = sjFilterHelmets('全部')
this.timer = setInterval(() => {
this.wave += 1
}, 60)
}
aboutToDisappear() {
if (this.timer !== -1) {
clearInterval(this.timer)
}
}
@Builder modalOverlay() {
Column() {
Column().width('100%').height('100%').backgroundColor('rgba(0,43,92,0.65)')
}
.width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
.onClick(() => {
this.showDetail = false
this.showAdd = false
this.showEdit = false
})
}
build() {
Stack() {
Scroll() {
Column() {
// 排行榜
Column() {
Text('头盔热销榜').fontSize(14).fontWeight(700).fontColor(SJ.dark).margin({ bottom: 12 })
ForEach(this.list, (h: SjHelmet, i: number) => {
Row() {
Text(sjTopN(i)).fontSize(20).fontWeight(900).fontColor(i < 3 ? SJ.accent : SJ.sub).width(28)
Column() {
Column().width(56).height(56).borderRadius(12).backgroundColor(SJ.bg)
.border({ width: 1, color: SJ.line })
Text(h.type).fontSize(10).fontColor(SJ.primary).fontWeight(600).margin({ top: 2 })
}
.width(56).alignItems(HorizontalAlign.Center)
Column() {
Text(h.name).fontSize(14).fontWeight(700).fontColor(SJ.text)
Text(h.brand + ' · ' + h.material).fontSize(10).fontColor(SJ.sub).margin({ top: 2 })
// 安全等级条
Row() {
ForEach([1, 2, 3, 4, 5], (n: number) => {
Column().width(16).height(4).borderRadius(2)
.backgroundColor(n <= h.safety ? SJ.accent : SJ.line)
.margin({ right: 2 })
}, (n: number) => n.toString())
}
.margin({ top: 4 })
Row() {
Text(h.visor).fontSize(10).fontColor(SJ.primary).padding({ left: 6, right: 6, top: 2, bottom: 2 }).borderRadius(4).backgroundColor(SJ.bg)
Column().layoutWeight(1)
Text(h.weight.toString() + 'kg').fontSize(10).fontColor(SJ.sub)
}
.width('100%').margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start).layoutWeight(1).padding({ left: 12 })
Column() {
Text('已售').fontSize(10).fontColor(SJ.sub)
Text(h.sold.toString()).fontSize(14).fontWeight(700).fontColor(SJ.accent)
}
.alignItems(HorizontalAlign.End)
}
.width('100%').padding(12).backgroundColor(SJ.card).borderRadius(12).margin({ bottom: 8 })
.onClick(() => {
this.selName = h.name
this.showDetail = true
})
}, (h: SjHelmet) => h.name)
}
.width('100%').padding(16).backgroundColor(SJ.bg)
// 面罩类型分布
Column() {
Text('面罩类型分布').fontSize(13).fontWeight(700).fontColor(SJ.dark).margin({ bottom: 12 })
Column() {
Row() {
Text('电变镜').fontSize(11).fontColor(SJ.sub).width(60)
Stack({ alignContent: Alignment.Start }) {
Column().width('100%').height(8).borderRadius(4).backgroundColor(SJ.bg)
Column().width('35%').height(8).borderRadius(4).backgroundColor(SJ.primary)
}
.layoutWeight(1)
Text('35%').fontSize(11).fontColor(SJ.text).fontWeight(600).width(32)
}
.margin({ bottom: 8 })
Row() {
Text('透明镜').fontSize(11).fontColor(SJ.sub).width(60)
Stack({ alignContent: Alignment.Start }) {
Column().width('100%').height(8).borderRadius(4).backgroundColor(SJ.bg)
Column().width('25%').height(8).borderRadius(4).backgroundColor(SJ.accent)
}
.layoutWeight(1)
Text('25%').fontSize(11).fontColor(SJ.text).fontWeight(600).width(32)
}
.margin({ bottom: 8 })
Row() {
Text('彩色镜').fontSize(11).fontColor(SJ.sub).width(60)
Stack({ alignContent: Alignment.Start }) {
Column().width('100%').height(8).borderRadius(4).backgroundColor(SJ.bg)
Column().width('15%').height(8).borderRadius(4).backgroundColor(SJ.glow)
}
.layoutWeight(1)
Text('15%').fontSize(11).fontColor(SJ.text).fontWeight(600).width(32)
}
.margin({ bottom: 8 })
Row() {
Text('无面罩').fontSize(11).fontColor(SJ.sub).width(60)
Stack({ alignContent: Alignment.Start }) {
Column().width('100%').height(8).borderRadius(4).backgroundColor(SJ.bg)
Column().width('25%').height(8).borderRadius(4).backgroundColor(SJ.sub)
}
.layoutWeight(1)
Text('25%').fontSize(11).fontColor(SJ.text).fontWeight(600).width(32)
}
}
.width('100%')
}
.width('100%').padding(16).backgroundColor(SJ.card).margin({ top: 8 })
// 销量对比
Column() {
Text('销量对比').fontSize(13).fontWeight(700).fontColor(SJ.dark).margin({ bottom: 12 })
ForEach(this.list.slice(0, 5), (h: SjHelmet, i: number) => {
Row() {
Text(h.name.substring(0, 8)).fontSize(10).fontColor(SJ.sub).width(56)
Stack({ alignContent: Alignment.Start }) {
Column().width('100%').height(10).borderRadius(5).backgroundColor(SJ.bg)
Column().width(Math.min(h.sold / 512 * 100, 100).toString() + '%').height(10).borderRadius(5).backgroundColor(SJ.accent)
}
.layoutWeight(1)
Text(h.sold.toString()).fontSize(10).fontColor(SJ.text).fontWeight(600).width(32)
}
.width('100%').margin({ bottom: 6 })
}, (h: SjHelmet) => h.name)
}
.width('100%').padding(16).backgroundColor(SJ.card).margin({ top: 8, bottom: 8 })
}
.width('100%')
}
.scrollBar(BarState.Off)
.layoutWeight(1)
.width('100%')
// 头盔详情弹框
if (this.showDetail) {
Stack() {
this.modalOverlay()
Column() {
Text('头盔详情').fontSize(18).fontWeight(700).fontColor(SJ.dark).margin({ top: 20 })
Text(this.selName).fontSize(16).fontWeight(600).fontColor(SJ.text).margin({ top: 8 })
Row() {
ForEach([1, 2, 3, 4, 5], (n: number) => {
Column().width(20).height(6).borderRadius(3)
.backgroundColor(n <= 3 ? SJ.accent : SJ.line).margin({ right: 3 })
}, (n: number) => n.toString())
}
.margin({ top: 8 })
Row() {
Column() { Text('材质').fontSize(11).fontColor(SJ.sub); Text('碳纤维').fontSize(13).fontWeight(700).fontColor(SJ.text) }
.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() { Text('面罩').fontSize(11).fontColor(SJ.sub); Text('电变镜').fontSize(13).fontWeight(700).fontColor(SJ.text) }
.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() { Text('重量').fontSize(11).fontColor(SJ.sub); Text('0.65kg').fontSize(13).fontWeight(700).fontColor(SJ.text) }
.layoutWeight(1).alignItems(HorizontalAlign.Center)
}
.width('100%').padding(12).backgroundColor(SJ.bg).borderRadius(12).margin({ top: 12 })
Row() {
Text().width(28).height(28).borderRadius(14).backgroundColor(SJ.sub).onClick(() => { this.showDetail = false })
Column().layoutWeight(1)
Text('编辑').fontSize(12).fontColor(SJ.card).padding({ left: 14, right: 14, top: 8, bottom: 8 }).borderRadius(16).backgroundColor(SJ.accent)
.onClick(() => { this.showDetail = false; this.showEdit = true })
}
.width('100%').margin({ top: 16 })
}
.width('86%').backgroundColor(SJ.card).borderRadius(16).padding(20)
.constraintSize({ maxHeight: '80%' }).zIndex(1000)
}
.width('100%').height('100%')
}
// 新增头盔弹框
if (this.showAdd) {
Stack() {
this.modalOverlay()
Column() {
Text('新增头盔').fontSize(18).fontWeight(700).fontColor(SJ.dark).margin({ top: 20 })
Column() {
Text('名称').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Row() {
Text('请输入头盔名称').fontSize(12).fontColor(SJ.sub)
Column().layoutWeight(1)
}
.width('100%').height(36).backgroundColor(SJ.bg).borderRadius(8).padding({ left: 12, right: 12 })
}
.width('100%').margin({ top: 16 })
Column() {
Text('类型').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Row() {
Text('全盔').fontSize(12).fontColor(SJ.card).padding(8).borderRadius(8).backgroundColor(SJ.primary)
Text('半盔').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
}
}
.width('100%').margin({ top: 12 })
Column() {
Text('材质选择').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Row() {
Text('碳纤维').fontSize(12).fontColor(SJ.card).padding(8).borderRadius(8).backgroundColor(SJ.accent)
Text('玻璃钢').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
Text('凯夫拉').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
Text('聚碳酸酯').fontSize(12).fontColor(SJ.text).padding(8).borderRadius(8).backgroundColor(SJ.bg).margin({ left: 8 })
}
}
.width('100%').margin({ top: 12 })
Column() {
Text('安全等级').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Row() {
ForEach([1, 2, 3, 4, 5], (n: number) => {
Column().width(24).height(8).borderRadius(4)
.backgroundColor(n <= 4 ? SJ.accent : SJ.bg).margin({ right: 4 })
}, (n: number) => n.toString())
}
}
.width('100%').margin({ top: 12 })
Row() {
Text().width(28).height(28).borderRadius(14).backgroundColor(SJ.sub).onClick(() => { this.showAdd = false })
Column().layoutWeight(1)
Text('确认新增').fontSize(13).fontColor(SJ.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(SJ.primary)
.onClick(() => { this.showAdd = false })
}
.width('100%').margin({ top: 20 })
}
.width('86%').backgroundColor(SJ.card).borderRadius(16).padding(20)
.constraintSize({ maxHeight: '80%' }).zIndex(1000)
}
.width('100%').height('100%')
}
// 编辑头盔弹框
if (this.showEdit) {
Stack() {
this.modalOverlay()
Column() {
Text('编辑头盔').fontSize(18).fontWeight(700).fontColor(SJ.dark).margin({ top: 20 })
Text('当前:' + this.selName).fontSize(12).fontColor(SJ.sub).margin({ top: 8 })
Column() {
Text('修改名称').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Row() {
Text(this.selName).fontSize(12).fontColor(SJ.text)
Column().layoutWeight(1)
}
.width('100%').height(36).backgroundColor(SJ.bg).borderRadius(8).padding({ left: 12, right: 12 })
}
.width('100%').margin({ top: 16 })
Column() {
Text('修改安全等级').fontSize(12).fontColor(SJ.sub).margin({ bottom: 4 })
Row() {
ForEach([1, 2, 3, 4, 5], (n: number) => {
Column().width(24).height(8).borderRadius(4)
.backgroundColor(n <= 5 ? SJ.accent : SJ.bg).margin({ right: 4 })
}, (n: number) => n.toString())
}
}
.width('100%').margin({ top: 12 })
Row() {
Text().width(28).height(28).borderRadius(14).backgroundColor(SJ.sub).onClick(() => { this.showEdit = false })
Column().layoutWeight(1)
Text('保存修改').fontSize(13).fontColor(SJ.card).padding({ left: 20, right: 20, top: 8, bottom: 8 }).borderRadius(20).backgroundColor(SJ.primary)
.onClick(() => { this.showEdit = false })
}
.width('100%').margin({ top: 20 })
}
.width('86%').backgroundColor(SJ.card).borderRadius(16).padding(20)
.constraintSize({ maxHeight: '80%' }).zIndex(1000)
}
.width('100%').height('100%')
}
}
.width('100%').height('100%')
}
}
function sjAltiByName(name: string): SjAlti {
let idx: number = 0
for (let i = 0; i < SJ_ALTIS.length; i++) {
if (SJ_ALTIS[i].name === name) {
idx = i
}
}
return SJ_ALTIS[idx]
}
// ============ Tab 4: 高度表 ============
@Component
struct SjAltiTab {
@State wave: number = 0
}
总结

本文全面解析了一款基于HarmonyOS ArkTS构建的高空跳伞装备馆应用,从天空主题设计、专业数据模型、天空元素动画到伞包头盔等核心功能模块,深入探讨了声明式UI在专业运动装备领域的应用实践。该应用以清爽的天空蓝调为视觉基调,通过专业的数据可视化和丰富的天空主题动画,为跳伞爱好者打造了兼具专业感和美感的装备选购平台。
从技术角度来看,该应用展现了多个值得借鉴的设计和实现思路。在主题设计方面,天空蓝与安全橙的配色不仅美观,更在语义层面与跳伞运动的专业属性形成了深度呼应,体现了"功能即设计"的高级设计理念。在数据可视化方面,面积对比图、格子数柱状图、安全等级条等图表组件的运用,将专业参数以直观易懂的方式呈现给用户,有效降低了专业装备的选购门槛。在动画设计方面,开伞动画、云朵飘动、自由落体等主题化动效的融入,让整个应用充满了天空的自由感,大幅提升了品牌辨识度。
更多推荐



所有评论(0)