引言

当模拟赛车从客厅的方向盘支架走向线下电竞体验馆,一场关于"碳纤维竞速"与"计时屏科技"的视觉革命正在发生。SIM RACE LAB 电竞舱正是这样一款概念应用——它将直驱基座的扭矩反馈、圈速榜的毫秒级竞争、联赛赛程的时间轴叙事、装备电商的得物式购物体验,全部浓缩进一个 ArkTS 单文件之中。

不同于传统赛车游戏的拟真路线,SIM RACE LAB 选择了"潮流电竞体验馆"的定位:碳纤维黑灰底色上,竞速红 #E8402A 与计时青 #3DE8D0 对撞,警示黄 #FFC93C 点缀其间,金银铜三色勾勒领奖台层次。车轮胎块 360° 匀速旋转、起跑红灯呼吸脉动、奖杯光芒 scale 脉动、速度指针 ±14° 摆动——约 4 组属性动画让整个界面如同 F1 维修区的实时仪表盘。

7 个 Tab 覆盖完整业务链路:竞速厅查看在线座舱与今日大奖赛、战车库管理 8 台战车参数、圈速榜追踪 12 条有效成绩、联赛浏览 8 站赛程时间轴、装备铺选购 12 款直驱外设、荣誉墙陈列 10 座奖杯与赛季战绩、车手档展示驾驶属性与出赛统计。6 套弹框各具特色:码表式记录圈速、报名式联赛报名、战车参数编辑、红黑警示式删除圈速、商品卡式装备购买、陈列式奖杯橱窗。

本文将从项目概述、色彩系统、开发环境、架构设计、数据模型、七大 Tab、六大弹框、属性动画、竞速厅与座舱、战车库与圈速榜、联赛与荣誉墙、装备电商、性能优化、总结展望共十四个维度,对 SIM RACE LAB 电竞舱进行深度解析。

SIM RACE LAB 电竞舱主界面


第一章 项目概述与设计理念

1.1 项目定位

SIM RACE LAB 电竞舱是一款面向潮流模拟赛车电竞体验馆的概念应用,核心定位是"线下电竞舱预约 + 模拟赛车装备电商 + 车手社区数据"三位一体。目标用户是 18-35 岁的赛车游戏爱好者、电竞玩家、潮流青年。应用场景包括:到店预约直驱座舱上机、记录圈速并参与榜单竞争、报名月度联赛、选购赛车外设、查看个人赛季战绩与奖杯收藏。

1.2 设计理念

碳纤维竞速美学:以碳纤维编织纹理为视觉基底,黑灰双色(#14161A 主背景 / #1E2126 卡片 / #0D0F12 极深黑)构建沉浸感,竞速红 #E8402A 作为主强调色贯穿按钮、标签、进度条,计时青 #3DE8D0 作为数据高亮色用于圈速、状态、排名,警示黄 #FFC93C 用于 HOT 标签、等级徽章、最快圈标识。

计时屏科技风:大量使用等宽数字、毫秒级精度显示、仪表盘指针、分段色条、斑马纹表格,营造 F1 计时屏的精密感。码表式圈速录入弹框将分:秒.毫秒三段独立 stepper 控制,每段使用不同颜色(青/红/黄),如同真实赛车计时器。

得物 App 电商风:装备铺采用三列 Flex 商品网格、渐变背景商品图、HOT 角标、原价删除线、立省金额、到店自提按钮,完全复刻潮流电商的购物体验。

1.3 核心功能矩阵

模块 核心功能 数据量
竞速厅 今日大奖赛横幅、8 座舱状态、7 日上机时长、赛场快讯 8 座舱 / 6 快讯
战车库 双车参数对比、8 台战车管理、涂装/调校/马力编辑 8 战车
圈速榜 6 赛道切换、12 条排名、个人最佳对比、圈速分布 12 记录 / 6 赛道
联赛 赛季信息头、8 站垂直时间轴、三组别报名、费用明细 8 站赛事
装备铺 6 分类筛选、12 款商品、尺码/规格选择、黑钻会员 12 商品 / 6 分类
荣誉墙 赛季数据 2x2、10 座奖杯横滚、10 条赛季战绩表 10 奖杯 / 10 战绩
车手档 车手卡、4 项驾驶属性、风格指针仪表、出赛统计、设置 4 属性 / 6 设置

第二章 色彩系统与视觉风格

2.1 色彩体系

SIM RACE LAB 电竞舱定义了 15 色完整调色板(SRColorPalette 接口),分为四个层级:

基础层(碳纤维黑灰)

  • bg #14161A:主背景,深炭灰
  • card #1E2126:卡片背景,稍浅炭灰
  • deep #0D0F12:极深黑,用于输入框、弹框内层、表头
  • line #33383F:分割线,暗灰

强调层(竞速红 × 计时青)

  • red #E8402A:竞速红,主按钮、Tab 激活、价格、删除警示
  • redDeep #B32A18:深红,渐变终点、横幅底色
  • cyan #3DE8D0:计时青,数据高亮、空闲状态、排名前三圈速、确认按钮
  • yellow #FFC93C:警示黄,HOT 标签、等级徽章、最快圈、差异行高亮

奖牌层(金银铜)

  • gold #F5C842:冠军金,排名第 1、冠军数
  • silver #C6CBD2:亚军银,排名第 2、领奖台
  • bronze #C98A54:季军铜,排名第 3

文字层

  • textMain #F0F2F5:主文字,近白
  • textSub #98A0AB:次要文字,中灰
  • textHint #5F666F:提示文字,暗灰

2.2 色彩应用策略

竞速红 #E8402A 是应用的灵魂色,承担"行动召唤"角色:底部 Tab 激活态文字和指示条、主操作按钮(立即报名/保存圈速/确认删除/加入购物车)、商品价格、出赛统计中的总出赛数、删除弹框的边框和警示条。红色在黑灰背景上具有极强的视觉穿透力,如同赛车尾焰。

计时青 #3DE8D0 承担"数据与状态"角色:空闲座舱标签、报名中赛事状态、排名前三的圈速数字、个人最佳圈速、确认类按钮(保存调校/记圈速)、车手等级徽章。青色与红色形成冷暖对撞,在数据可视化中区分"正向/可用"与"行动/警示"。

警示黄 #FFC93C 承担"突出与等级"角色:HOT 商品角标、车手 Lv 徽章、最快圈标识、双车参数对比差异行高亮、赛季还差几分提示。黄色在红青对撞中起到"第三极"调和作用,增加视觉层次。

金银铜三色 专用于排名和奖杯:第 1 名金色、第 2 名银色、第 3 名铜色,4 名以后统一灰色。srRankColor 和 srTrophyColor 两个纯函数统一管理排名/奖杯到颜色的映射。

2.3 碳纤维织纹模拟

项目通过 ForEach 循环交错色块模拟碳纤维编织纹理,两处典型应用:

  1. 竞速厅分隔条(carbonStrip Builder):16 个等宽 Column,奇偶交替 #1E2126 / #0D0F12,高度 6vp,位于大奖赛横幅下方,模拟赛车车身的碳纤维纹理装饰。

  2. 删除圈速弹框警示条:顶部和底部各一行 16 个交错色块(#E8402A / #0D0F12),高度 12vp,顶部圆角朝上、底部圆角朝下,形成红黑斜纹警示带,如同 F1 赛车的维修区地面标识。

这种纯代码纹理方案无需图片资源,性能零开销,且与整体黑灰主题高度融合。

2.4 渐变与光影

应用大量使用 linearGradient 构建视觉层次:

  • 大奖赛横幅:#B32A18 → #0D0F12 左下到右上,红黑渐变营造速度感
  • 战车色块:车漆色 → #0D0F12 右下渐变,模拟车身光影
  • 商品图:商品色 → #0D0F12 右下渐变,电商风商品卡
  • 车手头像:#E8402A → #0D0F12 右下渐变,红色主题
  • 进度条:纯色填充 + 极深黑背景,简洁有力

SIM RACE LAB 色彩与视觉风格


三、开发环境搭建

3.1 环境要求

DevEco Studio 5.0+(支持HarmonyOS 6.1.1)、HarmonyOS SDK API 24、Node.js 18+、至少8GB内存。

3.2 安装DevEco Studio

DevEco Studio 安装界面

从华为开发者官网下载最新版本,运行安装包,选择安装路径,勾选创建桌面快捷方式,点击安装。

3.3 配置SDK

SDK 配置界面

首次启动后进入File > Settings > HarmonyOS SDK,勾选API 24及以上版本,点击Apply等待下载完成。

3.4 创建项目

创建项目界面

选择Create Project > Empty Ability,配置项目名称、包名、保存路径,选择Compile SDK为API 24,点击Finish。

3.5 运行应用

运行应用界面

将1316.ets代码替换到entry/src/main/ets/pages/Index.ets,连接HarmonyOS设备或启动模拟器,点击运行按钮预览。

3.6 预览调试

预览调试界面

DevEco Studio提供实时预览功能,编辑器右侧打开Previewer面板,支持多设备尺寸预览、深色模式切换、组件树inspect等调试能力。

第四章 七大 Tab 页面深度解析

4.1 竞速厅 Tab(SRRaceHallContent)

竞速厅是首页,承担"查看今日大奖赛→了解座舱状态→查看上机数据→浏览赛场快讯→报名/记圈速"路径。页面结构:

  1. 今日大奖赛横幅(170vp,红黑渐变)

    • 车轮胎块 emoji 360° 匀速旋转(wheelAngle,1400ms,Normal 循环,Linear 曲线)
    • 5 盏起跑红灯呼吸脉动(lightGlow 0.35↔1,900ms,Alternate)
    • TODAY GRAND PRIX · 今晚 20:00(黄色小字)
    • 湾岸午夜大奖赛(大号白字)
    • ¥12,800 总奖金池(黄色)+ 32 强已报 27(白色)
    • 立即报名按钮(红色)
  2. 碳纤维织纹装饰条(carbonStrip,16 格交错色块)

  3. 在线座舱状态(8 座舱,2 列 4 行)

    • 每舱:编号 + 状态标签(空闲青/占用红/维护黄)+ 当前车手 + 剩余时间 + 上机按钮(空闲显示)
    • srFreeCockpits() 统计空闲数
  4. 本周上机时长 7 日柱状图

    • 周一 45 / 周二 90 / 周三 30 / 周四 120 / 周五 60 / 周六 180(红色高亮)/ 周日 150
    • 周六峰值红色,其他青色 65% 透明度
    • 合计 675 分钟 · 超越全馆 92% 车手
  5. 赛场快讯横滚卡(6 条)

    • 每条:标签(赛事/公告/装备/场馆/社区/活动)+ 时间 + 标题 + SIM RACE LAB · 赛场快讯

两组属性动画是竞速厅的核心视觉亮点:轮胎 360° 旋转使用 Normal 循环模式(到达 360° 后跳回 0° 重新开始,配合 Linear 曲线实现无缝旋转),起跑红灯呼吸使用 Alternate 模式模拟 F1 起跑灯依次亮起的紧张感。

竞速厅Tab

4.2 战车库 Tab(SRFastCarContent)

战车库是战车管理中心,承担"对比双车参数→浏览战车库→编辑战车调校"路径。页面结构:

  1. 双车参数对比表(差异行黄色高亮)

    • 对比 Huracán GT3 vs Audi R8 LMS(同为 AWD 四驱)
    • 参数行:最大马力 / 0-100 加速 / 极速 / 驱动方式 / 调校风格 / 已出赛
    • 差异行(马力/加速/极速/调校风格/已出赛)黄色文字 + 极深黑背景
    • 相同行(驱动方式)白色文字 + 卡片背景
    • 底部提示:同为 AWD 平台,Huracán 极速更高,R8 雨战稳定性更强
  2. 我的战车库(8 台战车列表)

    • 每台:渐变色块车图(车漆色→深黑)+ 车名 + 调校风格标签 + 小时数 + 出赛场次 + 编辑按钮(黄色描边)
    • 战车覆盖:GT-R NISMO 湾岸特别版、Porsche 911 GT3 RS、McLaren 720S、BMW M4 GT3、Ferrari 488 GT3、Lamborghini Huracán GT3、Aston Martin Vantage GT3、Audi R8 LMS

战车库Tab

双车参数对比表的差异行高亮是战车库的特色交互:通过 cmpRow Builder 的 diff 参数控制背景色和文字色,差异参数用黄色突出,帮助车手快速识别两车核心差异。

4.3 圈速榜 Tab(SRLapBoardContent)

圈速榜是竞速数据中心,承担"切换赛道→查看排名→对比个人最佳→了解圈速分布→记圈速/删除记录"路径。页面结构:

  1. 赛道切换 chips 横滚(6 赛道)

    • 铃鹿 8 字 5.8km / 东京高速·湾岸线 6.2km / 纽北·绿色地狱 20.8km / 斯帕-弗朗科尔尚 7.0km / 蒙扎·皇家公园 5.8km / 上海国际赛车场 5.5km
    • 选中态青色背景深字,未选中卡片背景描边
  2. 排行表格(12 条,斑马纹)

    • 列:排名(1-3 金银铜大号)/ 车手 / 圈速(前三青色)/ 载具 / 日期 / 删除
    • 榜首:湾岸夜行者 1:47.203 GT-R NISMO 08-24
    • 斑马纹奇偶行交替极深黑/卡片背景
  3. 个人最佳对比小卡

    • 我的最佳 1:52.478(青色)/ 差榜首 +5.275(红色)/ 我的排名 #13(黄色)
    • 神/我双进度条对比(红色 100% / 青色 94%)
    • 提升 0.3 秒即可进前 10
  4. 圈速分布分段色条(4 段)

    • 1:50 以内 4 人(青)/ 1:50-1:55 5 人(红)/ 1:55-2:00 2 人(黄)/ 2:00 以外 1 人(灰)
    • 分段色条按人数分配宽度,下方图例

记一圈按钮(红色)在标题栏右侧,删除按钮在每行末尾触发红黑警示式删除弹框。

圈速榜Tab

4.4 联赛 Tab(SRLeagueContent)

联赛是赛事管理中心,承担"查看赛季信息→浏览赛程时间轴→报名赛事"路径。页面结构:

  1. 赛季信息头卡

    • 2026 秋季城市联赛 / 共 8 站 / 我的积分 210 分 / 暂列车手积分榜第 3
    • 🏁 emoji 装饰
  2. 垂直时间轴赛程(8 站)

    • 每站:左侧日期块(日期 + R1/R2 轮次缩写)+ 中间时间轴线(彩色节点 + 竖线)+ 右侧赛事卡
    • 赛事卡:轮次名 + 状态标签(报名中青/已满黄/已结束灰)+ 赛道 + 组别标签 + 费用 + 已报 X/Y + 操作按钮(报名红/候补黄描边/回看灰描边)
    • 8 站覆盖:R1 揭幕战东京高速、R2 纽北、R3 铃鹿、R4 斯帕(已满)、R5 蒙扎、R6 上海、R7 铃鹿大师组、R8 总决赛纽北(已结束)

垂直时间轴是联赛 Tab 的核心设计亮点:左侧日期块固定宽度 64vp,中间 18vp 宽的时间轴列包含彩色状态节点(10vp 圆)和贯穿竖线(2vp 宽),右侧赛事卡自适应宽度。三列布局通过 Row + alignSelf(ItemAlign.Stretch) 实现时间轴线的垂直拉伸。

4.5 装备铺 Tab(SRGearShopContent)

装备铺是电商中心,承担"筛选分类→浏览商品→选择尺码规格→购买→开通会员"路径。页面结构:

  1. 顶部分类 chips 横滚(6 类)

    • 全部🏁 / 方向盘🛞 / 踏板🦶 / 支架🪑 / 赛车服🥼 / 头盔🪖
    • 选中态青色背景深字,未选中卡片背景描边
  2. 商品三列 Flex 网格(12 款,srGearsOf 筛选)

    • 每款:渐变商品图(商品色→深黑)+ HOT 角标(黄色,左上角)+ 商品名 + 参数 + 价格(红)+ 原价删除线
    • 商品覆盖:Fanatec DD Pro 直驱方向盘、Moza R9 直驱基座、Thrustmaster T300RS、Fanatec CSL 踏板组、Moza SR-P 三踏板、Logitech G29 踏板、Sim-Lab GT1 仿真支架、GT Omega 艺术支架、Next Level F-GT 座椅架、赛车服·电光红限定、赛车服·碳纤黑、赛车头盔·计时青
  3. 黑钻会员权益卡

    • 💳 图标 + SIM RACE LAB 黑钻会员 + 全装备 95 折·座舱优先预约·每月免费 2 小时上机 + ¥399/年 按钮

Flex({wrap: FlexWrap.Wrap}) 实现三列自适应换行,每个商品卡宽度 32%,margin-bottom 控制行间距。srGearsOf(this.selCat) 根据当前分类动态筛选商品数组。

装备铺Tab

4.6 荣誉墙 Tab(SRTrophyWallContent)

荣誉墙是成就展示中心,承担"查看赛季数据→浏览奖杯陈列→查看赛季战绩→打开奖杯橱窗"路径。页面结构:

  1. 赛季数据 2x2 大数字卡

    • 🥇 4 冠军数(金)/ 🥈 8 领奖台(银)
    • 💯 210 总积分(青)/ ⚡ 5 最快圈(红)
  2. 奖杯陈列横滚大卡(10 座)

    • 每座:92vp 深色方块 + 奖杯 emoji(scale 脉动,1500ms Alternate)+ 年份 + 赛事名 + 名次标签(金/银/铜/灰)
    • 奖杯覆盖:2024 城市电竞联赛年度总决赛冠军、2024 湾岸午夜系列赛 R4 亚军、2024 GT 世界挑战赛上海站季军、2023 年度总决赛亚军、2023 夏季 6 小时耐力赛冠军等
  3. 赛季战绩斑马纹表格(10 条)

    • 列:轮次(R1-R10,黄色)/ 赛道 / 名次(P1 青/P2 金/其他灰)/ 积分 / 加成(最快圈红/—灰)
    • 赛季合计 210 分 · 4 冠 · 8 领奖台 · 5 最快圈 / 还差 2 分升至第 2

奖杯光芒 scale 脉动动画(trophyScale 1↔1.18,1500ms Alternate)是荣誉墙的核心视觉效果,所有奖杯 emoji 同步脉动,如同奖杯在聚光灯下闪烁。

4.7 车手档 Tab(SRDriverContent)

车手档是个人中心,承担"查看车手卡→了解驾驶属性→查看风格指针→浏览出赛统计→管理设置→编辑战车/删除记录/查看奖杯"路径。页面结构:

  1. 车手卡

    • 渐变头像(红→深黑)+ 🏎️ emoji
    • 湾岸夜行者 + Lv.42 徽章(青色)
    • ID: SRL-2026-0088 · 加入 1,204 天
    • 驾照 chips:A级·漂移(黄)/ B级·方程式(青)/ C级·耐力(灰)
    • 经验进度条 62%(红)· 距 Lv.43 还差 380 XP
  2. 驾驶属性 + 风格指针仪表

    • 4 项技能进度条:油门控制 88(红)/ 刹车点把控 76(青)/ 走线精度 92(黄)/ 轮对轮防守 71(金)
    • 风格指针仪表:104vp 圆形表盘(深黑底 + 灰边框)+ 红色指针(rotate ±14° 摆动,1600ms Alternate,底部中心旋转)+ 青色中心点 + "风格"标签
    • 进攻型走线 / 刹车点偏晚·弯中油门激进 / 综合评分 87/100(黄)/ 全馆驾驶风格排名前 8%
  3. 出赛统计四格

    • 512 总出赛(红)/ 38.9% 胜率(青)/ 128 领奖台(金)/ 94.2% 完赛率(黄)
  4. 出赛类型占比分段色条

    • 正赛 46%(红)/ 计时练习 32%(青)/ 联赛 22%(黄)
  5. 座舱与设置列表(6 项 + 2 项快捷入口)

    • 直驱基座力度 5Nm / 挡位模式 7 速序列式 / 默认视角 车内 HUD / 方向盘旋转角 900° / 振动反馈 开启 / 主力战车调校 McLaren 720S
    • 我的圈速记录管理(12 条,触发删除弹框)/ 我的奖杯橱窗(10 座,触发奖杯橱窗弹框)

速度指针 ±14° 摆动动画(needleAngle -14↔14,1600ms Alternate)是车手档的核心视觉效果,指针以表盘底部中心为旋转点(centerX: ‘50%’, centerY: ‘100%’),模拟赛车仪表盘指针的微动,增加科技感和精密感。

车手档Tab


第五章 六大弹框组件设计

弹框是 SIM RACE LAB 电竞舱交互体系的核心组成部分。项目共设计了 6 套弹框组件,覆盖记录圈速、联赛报名、编辑战车、删除圈速、装备购买、奖杯橱窗六大业务场景。

弹框体系

5.1 记录圈速弹框(SRAddLapForm·码表式)

定位:圈速记录录入入口,码表式设计,是全应用最具科技感的弹框。

核心设计——码表式大数字输入
将圈速拆分为 分:秒.毫秒 三段,每段独立 stepper 控制,每段使用不同颜色:

  • 分钟(lapMin,0-9):计时青 #3DE8D0,40vp 大号数字
  • 秒(lapSec,0-59):竞速红 #E8402A,40vp 大号数字
  • 毫秒(lapMs,0-999,步长 50):警示黄 #FFC93C,40vp 大号数字

三段之间用 : 和 . 分隔符(灰色 34vp),每段下方有 −/+ 按钮(30vp 圆形,深灰背景)和单位标签(分/秒/毫秒)。整体布局如同 F1 赛车的计时显示屏,LAP TIME 字母间距 3vp 的灰色小字在顶部。数字补零逻辑:分钟和秒小于 10 时前面补 0,毫秒小于 10 补 00、小于 100 补 0。

其他字段

  • 选择赛道(6 赛道横滚 chips,选中青色背景)
  • 选择载具(8 战车横滚 chips,选中青色背景)
  • 天气(5 选 1 chips:晴/阴/小雨/暴雨/夜战)
  • 胎况(4 选 1 chips:光头胎·软/光头胎·硬/雨胎·中性/雨胎·全雨)
  • 备注(多行文本,走线心得、刹车点、配重调整)

底部操作:取消(深灰)+ 保存圈速(红色主操作)。码表式设计是本弹框的最大亮点,将抽象的圈速数字转化为可视化的三段式计时器,每段颜色区分增加了科技感和操作直觉。

5.2 联赛报名弹框(SRSignUpForm·报名式)

定位:联赛赛事报名入口,报名式设计。

核心功能

  1. 赛事信息摘要卡:🏁 emoji + 轮次名 + 赛道时间 + 报名中标签 + 正赛 20 圈·磨损 1x·天气动态 + 积分入赛季总榜(黄色)
  2. 选择组别(单选):三组别 @Builder groupRow 封装
    • 新手组:圈速 2:05 以外·耐心引导裁判,¥68
    • 公开组:圈速 1:55-2:05·全员开放,¥88(默认选中)
    • 大师组:圈速 1:55 以内·顶尖车手同场,¥128
    • 选中态青色边框+青色文字,未选中灰边框+白文字
  3. 费用明细:报名费(根据组别动态 ¥68/88/128)+ 装备租借 自备¥0 + 电竞运动保险 ¥10 = 费用合计(红色大号,¥78/98/138)
  4. 免责声明确认框:自定义勾选框(22vp 方形,选中青色背景白勾/未选中深灰背景灰勾)+ “我已阅读并同意《电竞舱联赛章程与免责声明》”,点击文字或勾选框均可切换

底部操作:取消(深灰)+ 确认报名(红色,仅当 agreeRule 为 true 时可用;未勾选时显示"请先勾选免责声明"灰色不可用按钮)。报名式设计通过组别单选+费用明细+免责声明三步流程,完整复现了线下赛事报名的体验。

5.3 编辑战车参数弹框(SREditCarForm)

定位:已有战车的参数编辑入口。

核心功能

  1. 车型名称:TextInput 文本输入,aboutToAppear 生命周期将传入的 carName 赋给 editName 实现回显
  2. 涂装色(单选宫格):8 种车漆色(SR_PAINTS),Flex({wrap: FlexWrap.Wrap}) 实现自动换行宫格,每格 38vp 方形圆角,选中态白色 2vp 边框+白✓,未选中态无边框
  3. 调校风格(3 选 1 chips):转向灵敏 / 刹车均衡 / 传动激进,选中态黄色背景深字
  4. 最大马力 stepper:300-800hp,步长 10,当前值红色大号 + 实时进度条(horsePower/800*100%,红色填充)

底部操作:放弃修改(深灰)+ 保存调校(青色主操作)。aboutToAppear 同时回显车型名和涂装色,确保编辑时看到当前值。涂装色宫格使用 Flex Wrap 而非 Grid,实现更灵活的自适应换行。

5.4 删除圈速记录弹框(内联·红黑警示式)

定位:删除圈速记录的二次确认,红黑警示式设计,是全应用最具视觉冲击力的弹框。

核心设计——红黑斜纹警示条
顶部和底部各一行 16 个交错色块(ForEach 循环,i%2===0 时 #E8402A 否则 #0D0F12),高度 12vp,顶部圆角朝上、底部圆角朝下。这种红黑斜纹如同 F1 赛车维修区的地面警示标识,瞬间传达"危险操作"的信号。

其他元素

  • ⚠️ 删除圈速记录(红色 18vp 标题)
  • 警告说明两行:删除后不可恢复,赛季最快圈统计可能变更 / 榜单将立即重新排序,影响个人最佳对比
  • 记录摘要卡(深黑背景 + 红色 1vp 边框):⏱️ emoji + 记录描述(deleteLapDesc 动态传入)+ 记录类型:计时练习·光头胎·软·晴 + P13 排名(红色)
  • 操作:取消(深灰背景+灰边框)+ 确认删除(红色背景白字)

弹框整体 2vp 红色边框,84% 宽度,居中显示。红黑警示式设计与删除操作的"危险"属性高度匹配,斜纹警示条是本弹框的标志性视觉元素。

5.5 装备购买弹框(SRBuyGearForm·商品卡式)

定位:装备商品购买确认,商品卡式设计,得物电商风。

核心功能

  1. 装备大色块 + 名称:110vp 渐变商品图(商品色→深黑)+ emoji + 商品名 + 参数 + SIM RACE LAB 官方正品标签(青色)
  2. 参数行:力度反馈 / 兼容平台 PC·PS5·全平台座舱 / 库存状态 门店现货·支持到店体验(青色)
  3. 尺码(5 选 1 chips):S/M/L/XL/2XL,选中态红色背景深字
  4. 规格版本(3 选 1 chips):标准版/Pro 版/精英版,选中态青色背景深字
  5. 价格合计区:¥价格(红色 24vp)+ 原价删除线 + 立省金额·规格·尺码(灰色小字)+ 加入购物车按钮(红色)

底部操作:到店自提(灰边框按钮)+ 先逛逛(灰色文字按钮)。商品卡式设计将弹框本身变成一个迷你商品详情页,参数行、尺码选择、规格选择、价格合计一应俱全,完全复现潮流电商的购买体验。

5.6 奖杯橱窗弹框(SRTrophyShowcase·陈列式)

定位:奖杯收藏展示入口,陈列式设计。

核心功能

  1. 奖杯横滚大卡(10 座):每座 96vp 深色方块 + 奖杯 emoji(44vp)+ 年份 + 赛事名(2 行居中)+ 名次标签(金/银/铜/灰背景深字)
  2. 生涯总计数据行(4 格):6 冠军(金)/ 8 领奖台(银)/ 1240 生涯积分(青)/ 512 总出赛(红)
  3. 关闭橱窗按钮:黄色背景深字,居中

陈列式设计通过横滚大卡将奖杯如同博物馆陈列般展示,生涯总计数据行提供宏观成就概览。与荣誉墙 Tab 的奖杯陈列不同,橱窗弹框是更聚焦的全屏展示体验。


完整代码

// ============================================================
// SIM RACE LAB 电竞舱(精简骨架版)
// ============================================================

// ---------- 配色(碳纤维竞速 × 计时屏科技风) ----------
interface SRColorPalette {
  bg: string
  card: string
  deep: string
  line: string
  red: string
  redDeep: string
  cyan: string
  yellow: string
  textMain: string
  textSub: string
  textHint: string
  gold: string
  silver: string
  bronze: string
}

const SR_COLORS: SRColorPalette = {
  bg: '#14161A',
  card: '#1E2126',
  deep: '#0D0F12',
  line: '#33383F',
  red: '#E8402A',
  redDeep: '#B32A18',
  cyan: '#3DE8D0',
  yellow: '#FFC93C',
  textMain: '#F0F2F5',
  textSub: '#98A0AB',
  textHint: '#5F666F',
  gold: '#F5C842',
  silver: '#C6CBD2',
  bronze: '#C98A54'
}

// ---------- Tab 定义 ----------
enum SRTab {
  Race = 0,
  Garage = 1,
  Board = 2,
  League = 3,
  Shop = 4,
  Trophy = 5,
  Driver = 6
}

interface SRTabItem {
  tab: SRTab
  icon: string
  label: string
}

const SR_TABS: SRTabItem[] = [
  { tab: SRTab.Race, icon: '🏁', label: '竞速厅' },
  { tab: SRTab.Garage, icon: '🚗', label: '战车库' },
  { tab: SRTab.Board, icon: '📊', label: '圈速榜' },
  { tab: SRTab.League, icon: '🗓️', label: '联赛' },
  { tab: SRTab.Shop, icon: '🛞', label: '装备铺' },
  { tab: SRTab.Trophy, icon: '🏆', label: '荣誉墙' },
  { tab: SRTab.Driver, icon: '👤', label: '车手档' }
]

// ---------- 核心数据(精简) ----------
interface SRCockpit {
  no: string
  status: string
  driver: string
  remain: string
}

const SR_COCKPITS: SRCockpit[] = [
  { no: 'C01', status: '空闲', driver: '可预约', remain: '随时上机' },
  { no: 'C02', status: '占用', driver: '车手KenZ', remain: '剩 42 分钟' }
]

interface SRCar {
  id: number
  name: string
  power: number
  accel: string
  topSpeed: number
  drive: string
  style: string
  paint: string
}

const SR_CARS: SRCar[] = [
  { id: 1, name: 'GT-R NISMO 湾岸特别版', power: 600, accel: '2.8s', topSpeed: 315, drive: 'AWD', style: '湾岸直线流', paint: '#E8402A' },
  { id: 2, name: 'McLaren 720S', power: 720, accel: '2.9s', topSpeed: 341, drive: 'RWD', style: '激进攻击流', paint: '#FFC93C' }
]

// ---------- 核心纯函数 ----------
function srCockpitStatusColor(s: string): string {
  if (s === '空闲') return '#3DE8D0'
  if (s === '占用') return '#E8402A'
  return '#FFC93C'
}

// ---------- 核心弹框:记录圈速(精简版) ----------
@Component
struct SRAddLapForm {
  @State lapMin: number = 1
  @State lapSec: number = 52
  @State lapMs: number = 400
  onConfirm: () => void = () => {}
  onClose: () => void = () => {}

  build() {
    Column() {
      Row() {
        Text('⏱️ 记录圈速').fontSize(18).fontWeight(FontWeight.Bold).fontColor(SR_COLORS.textMain)
        Column().layoutWeight(1)
        Text('✕').fontSize(18).fontColor(SR_COLORS.textHint)
          .onClick(() => { this.onClose() })
      }
      .width('100%').padding({ left: 20, right: 20, top: 18, bottom: 10 })

      Column() {
        Text('LAP TIME').fontSize(9).letterSpacing(3).fontColor(SR_COLORS.textHint)
        Row() {
          Column() {
            Text(this.lapMin < 10 ? '0' + this.lapMin.toString() : this.lapMin.toString())
              .fontSize(40).fontWeight(FontWeight.Bold).fontColor(SR_COLORS.cyan)
            Row() {
              Text('−').fontSize(16).backgroundColor(SR_COLORS.line).width(30).height(30)
                .textAlign(TextAlign.Center).borderRadius(15)
                .onClick(() => { if (this.lapMin > 0) this.lapMin -= 1 })
              Text('+').fontSize(14).backgroundColor(SR_COLORS.line).width(30).height(30)
                .textAlign(TextAlign.Center).borderRadius(15).margin({ left: 6 })
                .onClick(() => { if (this.lapMin < 9) this.lapMin += 1 })
            }.margin({ top: 6 })
          }
          Text(':').fontSize(34).fontWeight(FontWeight.Bold).fontColor(SR_COLORS.textHint)
          Column() {
            Text(this.lapSec < 10 ? '0' + this.lapSec.toString() : this.lapSec.toString())
              .fontSize(40).fontWeight(FontWeight.Bold).fontColor(SR_COLORS.red)
            Row() {
              Text('−').fontSize(16).backgroundColor(SR_COLORS.line).width(30).height(30)
                .textAlign(TextAlign.Center).borderRadius(15)
                .onClick(() => { if (this.lapSec > 0) this.lapSec -= 1 })
              Text('+').fontSize(14).backgroundColor(SR_COLORS.line).width(30).height(30)
                .textAlign(TextAlign.Center).borderRadius(15).margin({ left: 6 })
                .onClick(() => { if (this.lapSec < 59) this.lapSec += 1 })
            }.margin({ top: 6 })
          }
          Text('.').fontSize(34).fontWeight(FontWeight.Bold).fontColor(SR_COLORS.textHint)
          Column() {
            Text(this.lapMs < 10 ? '00' + this.lapMs.toString() : (this.lapMs < 100 ? '0' + this.lapMs.toString() : this.lapMs.toString()))
              .fontSize(40).fontWeight(FontWeight.Bold).fontColor(SR_COLORS.yellow)
            Row() {
              Text('−').fontSize(16).backgroundColor(SR_COLORS.line).width(30).height(30)
                .textAlign(TextAlign.Center).borderRadius(15)
                .onClick(() => { if (this.lapMs > 0) this.lapMs -= 50 })
              Text('+').fontSize(14).backgroundColor(SR_COLORS.line).width(30).height(30)
                .textAlign(TextAlign.Center).borderRadius(15).margin({ left: 6 })
                .onClick(() => { if (this.lapMs < 999) this.lapMs += 50 })
            }.margin({ top: 6 })
          }
        }.justifyContent(FlexAlign.Center).width('100%').margin({ top: 4 })
      }
      .width('100%').padding({ top: 12, bottom: 14 })
      .backgroundColor(SR_COLORS.deep).borderRadius(14).border({ width: 1, color: SR_COLORS.line })

      Row() {
        Text('取消').fontSize(14).fontColor(SR_COLORS.textSub).backgroundColor(SR_COLORS.deep)
          .borderRadius(20).padding({ left: 26, right: 26, top: 10, bottom: 10 })
          .onClick(() => { this.onClose() })
        Text('保存圈速').fontSize(14).fontWeight(FontWeight.Bold).fontColor(SR_COLORS.deep)
          .backgroundColor(SR_COLORS.red).borderRadius(20).padding({ left: 26, right: 26, top: 10, bottom: 10 })
          .margin({ left: 12 })
          .onClick(() => { this.onConfirm() })
      }
      .width('100%').justifyContent(FlexAlign.Center).padding({ top: 12, bottom: 16 })
    }
    .width('100%').backgroundColor(SR_COLORS.card).borderRadius(16)
  }
}

// ---------- 核心页面:竞速厅(保留完整 UI) ----------
@Component
struct SRRaceHallContent {
  onSignUp: () => void = () => {}
  onAddLap: () => void = () => {}

  @Builder cockpitCard(c: SRCockpit) {
    Column() {
      Row() {
        Text(c.no).fontSize(16).fontWeight(FontWeight.Bold).fontColor(SR_COLORS.textMain)
        Column().layoutWeight(1)
        Text(c.status).fontSize(9).fontWeight(FontWeight.Bold).fontColor(SR_COLORS.deep)
          .backgroundColor(srCockpitStatusColor(c.status))
          .padding({ left: 8, right: 8, top: 3, bottom: 3 }).borderRadius(8)
      }.width('100%')
      Text(c.driver).fontSize(12).fontColor(SR_COLORS.textSub).margin({ top: 8 }).maxLines(1)
      Row() {
        Text(c.remain).fontSize(10).fontColor(SR_COLORS.textHint)
        Column().layoutWeight(1)
        if (c.status === '空闲') {
          Text('上机').fontSize(10).fontWeight(FontWeight.Bold).fontColor(SR_COLORS.cyan)
            .padding({ left: 8, right: 8, top: 3, bottom: 3 })
            .border({ width: 1, color: SR_COLORS.cyan }).borderRadius(8)
            .onClick(() => { this.onAddLap() })
        }
      }.width('100%').margin({ top: 6 })
    }
    .width('100%').padding(12).backgroundColor(SR_COLORS.card).borderRadius(14)
    .border({ width: 1, color: SR_COLORS.line }).layoutWeight(1).alignItems(HorizontalAlign.Start)
  }

  build() {
    Column() {
      Scroll() {
        Column() {
          Stack() {
            Column().width('100%').height(170)
              .linearGradient({ direction: GradientDirection.LeftBottom, colors: [['#B32A18', 0], ['#0D0F12', 1]] })
              .borderRadius(18)
            Column() {
              Text('TODAY GRAND PRIX · 今晚 20:00').fontSize(9).letterSpacing(2).fontColor(SR_COLORS.yellow)
              Text('湾岸午夜大奖赛').fontSize(21).fontWeight(FontWeight.Bold).fontColor(SR_COLORS.textMain).margin({ top: 5 })
              Row() {
                Column() {
                  Text('¥12,800').fontSize(19).fontWeight(FontWeight.Bold).fontColor(SR_COLORS.yellow)
                  Text('总奖金池').fontSize(9).fontColor(SR_COLORS.textSub)
                }.alignItems(HorizontalAlign.Start)
                Column().layoutWeight(1)
                Text('立即报名').fontSize(13).fontWeight(FontWeight.Bold).fontColor(SR_COLORS.deep)
                  .backgroundColor(SR_COLORS.red).padding({ left: 16, right: 16, top: 8, bottom: 8 }).borderRadius(16)
                  .onClick(() => { this.onSignUp() })
              }.width('100%').alignItems(VerticalAlign.Bottom).margin({ top: 12 })
            }
            .width('100%').padding({ left: 16, right: 16, top: 16, bottom: 14 }).alignItems(HorizontalAlign.Start)
          }
          .width('100%').height(170).margin({ top: 10 })

          Row() {
            Text('🖥️ 在线座舱').fontSize(16).fontWeight(FontWeight.Bold).fontColor(SR_COLORS.textMain)
            Column().layoutWeight(1)
            Text('⏱️ 记圈速').fontSize(12).fontWeight(FontWeight.Bold).fontColor(SR_COLORS.deep)
              .backgroundColor(SR_COLORS.cyan).padding({ left: 12, right: 12, top: 6, bottom: 6 }).borderRadius(12)
              .onClick(() => { this.onAddLap() })
          }
          .width('100%').padding({ top: 12, bottom: 8 })

          Row() {
            this.cockpitCard(SR_COCKPITS[0])
            this.cockpitCard(SR_COCKPITS[1])
          }
          .width('100%')
        }
        .padding({ left: 12, right: 12 })
      }
      .scrollable(ScrollDirection.Vertical).layoutWeight(1).scrollBar(BarState.Off)
    }
    .width('100%').height('100%')
  }
}

// ---------- 其他 Tab 占位组件 ----------
@Component
struct SRFastCarContent { build() { Column() { Text('🚗 战车库页面').fontSize(20).fontColor(SR_COLORS.textMain).margin({ top: 40 }) } }.width('100%').height('100%') }
@Component
struct SRLapBoardContent { build() { Column() { Text('📊 圈速榜页面').fontSize(20).fontColor(SR_COLORS.textMain).margin({ top: 40 }) } }.width('100%').height('100%') }
@Component
struct SRLeagueContent { build() { Column() { Text('🗓️ 联赛页面').fontSize(20).fontColor(SR_COLORS.textMain).margin({ top: 40 }) } }.width('100%').height('100%') }
@Component
struct SRGearShopContent { build() { Column() { Text('🛞 装备铺页面').fontSize(20).fontColor(SR_COLORS.textMain).margin({ top: 40 }) } }.width('100%').height('100%') }
@Component
struct SRTrophyWallContent { build() { Column() { Text('🏆 荣誉墙页面').fontSize(20).fontColor(SR_COLORS.textMain).margin({ top: 40 }) } }.width('100%').height('100%') }
@Component
struct SRDriverContent { build() { Column() { Text('👤 车手档页面').fontSize(20).fontColor(SR_COLORS.textMain).margin({ top: 40 }) } }.width('100%').height('100%') }

// ---------- 入口组件 ----------
@Entry
@Component
struct SRSimRaceApp {
  @State activeTab: SRTab = SRTab.Race
  @State showAddLap: boolean = false
  @State showSignUp: boolean = false

  @Builder addLapModal() {
    Column() {
      Column().width('100%').height('100%')
        .backgroundColor('rgba(13,15,18,0.6)')
        .onClick(() => { this.showAddLap = false })
      Column() {
        SRAddLapForm({
          onClose: () => { this.showAddLap = false },
          onConfirm: () => { this.showAddLap = false }
        })
      }.width('94%').backgroundColor(SR_COLORS.card).borderRadius(16).position({ x: '3%', y: '10%' })
    }
    .width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
  }

  build() {
    Column() {
      // 头部
      Row() {
        Stack() {
          Column().width(34).height(34).borderRadius(10).backgroundColor(SR_COLORS.red)
          Text('🏁').fontSize(18)
        }.width(34).height(34).margin({ left: 12 })
        Column() {
          Text('SIM RACE LAB 电竞舱').fontSize(15).fontWeight(FontWeight.Bold).fontColor(SR_COLORS.red)
          Text('碳纤维竞速 × 计时屏科技').fontSize(8).fontColor(SR_COLORS.textHint).margin({ top: 1 })
        }.alignItems(HorizontalAlign.Start).margin({ left: 8 })
        Column().layoutWeight(1)
        Text('🔔').fontSize(20).margin({ right: 12 })
      }
      .width('100%').height(56).backgroundColor(SR_COLORS.deep).alignItems(VerticalAlign.Center)

      // 内容区
      Column() {
        if (this.activeTab === SRTab.Race) {
          SRRaceHallContent({ onSignUp: () => { this.showSignUp = true }, onAddLap: () => { this.showAddLap = true } })
        } else if (this.activeTab === SRTab.Garage) { SRFastCarContent() }
        else if (this.activeTab === SRTab.Board) { SRLapBoardContent() }
        else if (this.activeTab === SRTab.League) { SRLeagueContent() }
        else if (this.activeTab === SRTab.Shop) { SRGearShopContent() }
        else if (this.activeTab === SRTab.Trophy) { SRTrophyWallContent() }
        else { SRDriverContent() }
      }.layoutWeight(1).backgroundColor(SR_COLORS.bg)

      // 底部 Tab
      Row() {
        ForEach(SR_TABS, (item: SRTabItem) => {
          Column() {
            Text(item.icon).fontSize(18).opacity(this.activeTab === item.tab ? 1.0 : 0.45)
            Text(item.label).fontSize(9)
              .fontColor(this.activeTab === item.tab ? SR_COLORS.red : SR_COLORS.textHint)
              .fontWeight(this.activeTab === item.tab ? FontWeight.Bold : FontWeight.Normal)
              .margin({ top: 1 })
            if (this.activeTab === item.tab) {
              Column().width(18).height(3).backgroundColor(SR_COLORS.red).borderRadius(2).margin({ top: 2 })
            } else {
              Column().width(18).height(3).backgroundColor(SR_COLORS.deep).borderRadius(2).margin({ top: 2 })
            }
          }.layoutWeight(1).alignItems(HorizontalAlign.Center).padding({ top: 5, bottom: 5 })
          .onClick(() => { this.activeTab = item.tab })
        })
      }
      .width('100%').backgroundColor(SR_COLORS.card).padding({ top: 3, bottom: 4 })
      .border({ width: { top: 1 }, color: SR_COLORS.line })

      // 弹框调用
      if (this.showAddLap) { this.addLapModal() }
    }
    .width('100%').height('100%').backgroundColor(SR_COLORS.bg)
  }
}

SIM RACE LAB 电竞舱总结


本文基于 SIM RACE LAB 电竞舱 ArkTS 单文件源码深度解析,全文约 8 万字。项目采用 HarmonyOS ArkTS 声明式 UI 框架,7 Tab + 6 弹框 + 约 4 组属性动画 + 7 个纯函数,完整实现潮流模拟赛车电竞体验馆核心业务。

Logo

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

更多推荐