天黑请闭眼真相浮现:基于HarmonyOS ArkTS API 24的QQ剧本杀推理馆酒红暗金悬疑风与粒子动画系统全记录

天黑请闭眼,真相浮现时。剧本杀,是当代年轻人最具沉浸感的社交推理游戏——一场剧本,三五好友,几小时内穿越到另一个时空,扮演另一个身份,在迷雾中抽丝剥茧寻找真相。从情感沉浸到恐怖惊悚,从硬核推理到阵营博弈,从欢乐撕逼到机制变格,剧本杀以其丰富的题材类型和深度的社交体验,成为Z世代最热门的线下娱乐方式。本文完整记录一个基于鸿蒙 HarmonyOS API 24 与 ArkTS 声明式框架构建的「QQ剧本杀·推理馆」组局社交平台,从酒红暗金悬疑剧本风的配色体系,到底部4Tab(剧本/车队/榜单/我的)+顶部6Tab(热门/情感/恐怖/推理/阵营/欢乐)的双层导航架构,到侦探道具粒子动画系统(🔍🕯️🗝️✨💡十六粒子setInterval 130ms向上漂移)与周组局场次柱状图,到4个模态弹窗(组局新增/投稿新本编辑/解散车队删除/剧本详情),到ScriptItem1/PartyItem/RankEntry/RecordItem四类@Observed数据模型与10个工具函数,附完整源码与运行效果截图。


一、引言:天黑请闭眼,真相浮现时

剧本杀,是当代年轻人最具沉浸感的社交推理游戏。一场剧本,三五好友,几小时内穿越到另一个时空,扮演另一个身份,在迷雾中抽丝剥茧寻找真相。剧本杀起源于欧美派对游戏"谋杀之谜"(Murder Mystery Game),传入中国后迅速本土化,结合了中国玩家偏好的情感沉浸、古风权谋、恐怖惊悚等题材,发展出独具特色的"剧本杀"文化。从2016年《明星大侦探》综艺带火剧本杀,到2020年疫情后线下剧本杀门店爆发式增长,再到如今剧本杀成为年轻人社交的标配,剧本杀已经从一个小众桌游发展成为千亿级的文化产业。

剧本杀的魅力在于其"沉浸式角色扮演+推理博弈"的双重体验。每个玩家拿到一个角色剧本,需要在几小时内完全代入角色——你可能是百年老宅的二小姐,可能是跨国公司的财务总监,可能是古代宫廷的皇子,可能是马戏团的小丑——通过搜证、讨论、推理,逐步揭开事件真相,找出隐藏在玩家中的凶手。剧本杀的题材极为丰富:情感本让人哭到崩溃,恐怖本让人尖叫连连,推理本让人烧脑到秃头,阵营本让人口水横飞,欢乐本让人笑到肚子疼,机制本让人欲罢不能。不同题材满足不同玩家的需求,这也是剧本杀能吸引海量玩家的核心原因。

然而,随着剧本杀行业的爆发式增长,玩家面临着越来越多的痛点——好剧本难找、拼车难、车队不稳定、玩家水平参差不齐、剧本质量参差不齐、DM(主持人)水平不一、价格不透明。一个优秀的剧本杀组局社交平台,需要覆盖从"剧本浏览"到"车队拼场"再到"玩家榜单""个人档案"的完整社交闭环,还要在剧本分类、组局管理、玩家评级、战绩记录等维度做到精准而高效。它需要以剧本陈列为核心入口,横向扩展出车队拼场、玩家榜单、个人档案三大功能模块,剧本模块再纵向细分为热门/情感/恐怖/推理/阵营/欢乐六大题材分类,每个分类采用完全不同的布局风格,形成一个完整闭环的剧本杀组局社交生态。

正是在这样的背景下,QQ剧本杀·推理馆类组局社交平台应运而生,并迅速成为剧本杀玩家的文化入口。一个优秀的剧本杀应用,需要以剧本陈列为核心入口,横向扩展出车队/榜单/我的三大功能模块,剧本模块再纵向细分为6大题材分类,每个分类采用完全不同的布局风格——热门=banner大卡+双列瀑布流、情感=横滑圆角卡+竖排榜单、恐怖=暗黑双列格子(#241B2E深紫黑背景)、推理=周组局场次柱状图+硬核榜、阵营=红蓝对决卡+阵营本列表、欢乐=标签云+紧凑列表,形成了一个完整闭环的剧本杀组局社交生态。它不仅是一个功能完备的演示项目,更是一份典型的 ArkUI 工程实践范本,涵盖了@Observed四类数据模型、10个工具函数、侦探道具粒子动画系统(setInterval驱动16粒子向上漂移)、双层导航架构(底部4Tab+顶部6Tab)、4个模态弹窗(组局/投稿/解散/详情)、aboutToAppear/aboutToDisappear生命周期管理定时器等关键技术点。

QQ剧本杀推理馆应用整体运行效果 - 酒红暗金悬疑风主题与底部4Tab顶部6Tab双层导航及侦探道具粒子动画


二、应用整体架构概览

整个应用采用了"单组件多@Builder构建器+状态集中管理"的架构设计。入口组件 Index 作为整个应用的根容器,负责管理当前底部Tab索引、顶部Tab索引、四个弹窗的显隐状态、选中剧本、粒子数组、组局表单状态、投稿表单状态等全部状态。与多组件架构不同,本应用将所有UI构建逻辑都封装为@Builder构建器(headerBuilder/scriptTopTabs/partyTopBar/rankTopBar/mineTopBar/scriptContent/hotScriptContent/emotionScriptContent/horrorScriptContent/reasoningScriptContent/campScriptContent/joyScriptContent/partyContent/rankContent/mineContent/bottomTabs/buildPartyModal/submitScriptModal/disbandPartyModal/scriptDetailModal),入口组件只负责状态管理和构建器调度,通过@Builder实现了逻辑的模块化拆分,是中大型 ArkUI 应用的标准架构实践。

应用采用双层导航架构——底部4Tab + 顶部6Tab。底部Tab(BOTTOM_TABS):剧本📖/车队🚌/榜单🏆/我的👤,是应用的一级导航,决定渲染哪个大模块。顶部Tab(TOP_TABS):热门🔥/情感💔/恐怖👻/推理🕵️/阵营⚔️/欢乐🎉,仅在底部Tab为"剧本"时显示,是剧本模块的二级分类导航,决定渲染哪个题材的剧本列表。这种"底部一级+顶部二级"的双层导航是内容型应用的标准架构——底部Tab切换大模块,顶部Tab在当前模块内细分内容分类,既保证了模块间的快速切换,又实现了模块内的精细分类。顶部6Tab采用横向Scroll可滑动布局(6个Tab超出屏幕宽度时可左右滑动),激活态标签wine酒红色加粗+底部18x3金色Divider指示条,非激活态textSecondary灰字+透明Divider,是分类导航的标准视觉范式。

在数据层面,应用定义了四类@Observed数据模型——ScriptItem1(剧本实体,12字段,15个剧本)、PartyItem(车队实体,8字段,8个车队)、RankEntry(榜单实体,6字段,10个玩家)、RecordItem(战绩实体,5字段,6条战绩)。每个类都通过constructor完成初始化(非对象字面量),@Observed装饰整个类使得属性变更可被框架自动监听。剧本数据通过buildScripts()函数构建ALL_SCRIPTS常量数组,再通过leftScripts()/rightScripts()两个工具函数拆分为左右两列(偶数索引左列、奇数索引右列)用于双列瀑布流布局。车队/榜单/战绩数据直接以const常量数组定义。这种"函数构建+常量拆分"的数据初始化模式比直接在组件内定义数组更灵活——buildScripts()集中管理剧本数据,leftScripts()/rightScripts()按需拆分,便于维护和扩展。

在工具函数层面,应用定义了10个工具函数——heatColor(热度映射颜色:>9000酒红深/>7000酒红/其他金)、diffColor(难度映射颜色:地狱danger红/烧脑酒红/进阶金/入门success绿)、genreTagColor(题材映射标签色:情感粉/恐怖紫/推理蓝/阵营橙/欢乐金)、barHeight(场次映射柱高:v1.1+‘vp’)、formatHeat(热度格式化:heat/1000+‘k’)、partyProgress(车队进度:joined/need100)、winRate(胜率:wins/plays*100)、buildParticles(构建16个粒子初始位置)、driftParticles(粒子漂移:y-4,x用sin摆动,y<60回到底部640)、leftScripts/rightScripts(剧本拆列)。这10个工具函数将数据到样式/位置/格式的映射逻辑从UI层抽离,实现了数据与表现的解耦,是数据可视化和动画系统的核心支撑。

在动画层面,应用独创了侦探道具粒子动画系统——16个粒子(图标为🔍放大镜/🕯️蜡烛/🗝️钥匙/✨闪光/💡灯泡五种侦探道具循环),每个粒子有id/x/y/size/opacity/icon六个属性,通过setInterval每130毫秒调用driftParticles()更新粒子位置(y向上减4,x用sin(p.id+p.y/50)*3左右摆动,y<60时回到底部640形成循环),粒子用ForEach渲染在Stack中position绝对定位,key用p.id.toString()+‘_’+p.y.toFixed(0)保证每帧y变化时触发重新渲染。aboutToAppear生命周期启动setInterval并保存timerId,aboutToDisappear生命周期clearInterval清除定时器防止内存泄漏。这种"setInterval驱动+ForEach渲染+position绝对定位+key包含动态值"的粒子系统实现,是ArkUI中实现自定义动画的高级技巧——ArkUI的animation属性只能做属性过渡动画,无法实现粒子系统这种需要逐帧计算位置的复杂动画,通过setInterval手动驱动状态更新+ForEach重新渲染是实现这类动画的标准方案。

在交互层面,应用通过4个模态弹窗实现了增删改查交互闭环:buildPartyModal组局(新增,酒红渐变头,选择剧本/局类型/开始时间/人数加减器)、submitScriptModal投稿新本(编辑,暗金编辑风#F5EDD8头,剧本名称TextInput/类型Flex选择/难度Row选择/时长Flex选择)、disbandPartyModal解散车队(删除,警告小卡,danger红底确认,操作不可撤销提示)、scriptDetailModal剧本详情(查询,酒红渐变头,大卡信息+角色列表+玩家评价,组局/收藏按钮)。四个弹窗各自独立@Builder,通过show*布尔状态条件渲染,弹窗外层用Column width100% height100% justifyContent Center + backgroundColor ‘99000000’(60%透明黑)实现遮罩+居中,点击遮罩onClick关闭弹窗。四个弹窗覆盖了组局社交平台的核心交互——组局(新增车队)、投稿(编辑剧本)、解散(删除车队)、详情(查看剧本),形成了完整的增删改查交互闭环。

QQ剧本杀推理馆应用架构运行效果 - 酒红渐变头部与剧本Tab热门banner大卡及双列瀑布流布局

下面逐页展示应用的四大功能模块运行效果。


三、剧本Tab:6大题材差异化布局

剧本Tab是应用的核心模块,采用"顶部6Tab分类+差异化内容布局"的设计。6个题材分类每个都有完全不同的布局风格,最大程度还原了不同题材剧本的视觉氛围。

3.1 热门:banner大卡 + 双列瀑布流

剧本模块-热门 - 本周最热编辑力荐banner大卡与双列瀑布流15款剧本列表

热门分类采用"banner大卡+双列瀑布流"的两层布局。

第一部分是banner大卡Column,width100% borderRadius16 margin16,linearGradient angle135从wineDeep到wine酒红渐变。banner内Row布局:

  • 左侧信息区Column layoutWeight1:"本周最热·编辑力荐"goldLight金字11号 + "雾锁孤宅"白字24号加粗margin top6 + "9.6分·5人·4小时·情感沉浸"goldLight金字12号margin top6 + “浓雾封锁的百年老宅,三代人的爱恨纠葛”#E8D5B5浅金字11号margin top8 + 按钮Row margin top12:"立即组局"wineDeep深酒红字12号加粗gold金底圆角14(点击showPartyModal=true)+ "查看详情"白字12号border goldLight边框圆角14 margin left10(点击selectedScript=ALL_SCRIPTS[0],showDetailModal=true)
  • 右侧封面:Text(‘🏚️’) fontSize64 margin left10(老宅Emoji作为剧本封面)

第二部分是"🔥 热门剧本"标题Row + 双列瀑布流Row。双列瀑布流通过leftScripts()和rightScripts()两个工具函数将15个剧本拆分为左右两列(偶数索引左列8个、奇数索引右列7个),每列Column layoutWeight1,ForEach遍历对应剧本数组渲染卡片。每张卡片Column width100% borderRadius12 cardBg白底border边框:

  • 封面Emoji:Text(s.cover) fontSize44 margin top12
  • 剧本名:Text(s.title) fontSize14加粗textPrimary margin top6
  • 题材+人数时长Row margin top6:题材标签Text(s.genre) fontSize9白字padding 6x2圆角4 backgroundColor genreTagColor(s.genre)(情感粉/恐怖紫/推理蓝/阵营橙/欢乐金)+ 人数时长Text(s.players+’ · '+s.duration) fontSize9 textSecondary margin left6
  • 简介:Text(s.intro) fontSize10 textSecondary maxLines2 textOverflow Ellipsis margin top6 left10 right10(两行省略)
  • 评分热度Row width100% padding 10x8:评分Text('⭐ '+s.rating.toFixed(1)) fontSize11加粗gold金 + 弹性占位 + 热度Text('🔥 '+formatHeat(s.heat)) fontSize10 heatColor(s.heat)(>9000酒红深/>7000酒红/其他金)
  • 点击卡片:selectedScript=s,showDetailModal=true(弹出剧本详情弹窗)

15 个剧本:雾锁孤宅(情感·5人·4小时·烧脑·9.6分·9820热度)、长安十二时辰谜(推理·6人·5小时·烧脑·9.4·8760)、病院惊魂夜(恐怖·7人·4.5小时·地狱·9.1·9540)、杀人回忆之雨夜(推理·5人·3.5小时·进阶·8.9·7320)、月光马戏团(欢乐·6人·3小时·入门·8.8·6890)、血色婚礼(情感·6人·4小时·烧脑·9.3·8210)、谁是内鬼(阵营·8人·3.5小时·进阶·8.7·7980)、午夜列车(恐怖·6人·4小时·烧脑·9.0·8450)、商海风云(阵营·7人·5小时·地狱·9.2·7890)、青花瓷的秘密(情感·5人·3小时·入门·8.6·5430)、无人生还的孤岛(推理·6人·4.5小时·地狱·9.5·9120)、校园怪谈会(恐怖·5人·2.5小时·入门·8.4·4760)、皇城争霸(阵营·8人·4小时·烧脑·8.9·7650)、爆笑办公室(欢乐·5人·2.5小时·入门·8.5·5120)、时间旅行者的晚宴(推理·7人·5小时·地狱·9.4·8980)。覆盖情感/推理/恐怖/阵营/欢乐五大题材(机制题材未在数据中但在投稿类型中有),难度从入门到地狱四级,时长从2.5小时到5小时,人数从5人到8人,评分从8.4到9.6,热度从4760到9820,数据丰富度极高。

双列瀑布流是本应用最具特色的布局——通过leftScripts()/rightScripts()将数组按奇偶索引拆分,左右两列独立ForEach渲染,由于每张卡片高度不同(简介两行省略但实际高度可能有差异),形成自然的瀑布流效果。这种布局比单列列表更紧凑,比Grid网格更灵活,是内容型应用展示卡片列表的标准布局。genreTagColor和heatColor两个工具函数让题材标签和热度文字的颜色随数据动态变化,实现了"数据即样式"的动态着色。

3.2 情感:横滑圆角卡 + 竖排榜单

情感分类采用"横滑圆角卡+竖排榜单"的两层布局。

第一部分是"💔 情感沉浸·本周推荐"标题 + 横向Scroll横滑卡Row。横滑卡Scroll scrollable Horizontal scrollBar Off,Row内ForEach遍历ALL_SCRIPTS渲染100x120圆角14卡片:封面Emoji fontSize40 margin top10 + 剧本名fontSize12加粗 + 评分⭐fontSize10 gold金。卡片width100 height120,margin left10,点击弹出详情。横向Scroll让用户可左右滑动浏览所有剧本封面卡,是推荐位的标准布局。

第二部分是"💌 情感本榜单"竖排列表Column width92% borderRadius14 cardBg白底border padding14。ForEach遍历ALL_SCRIPTS渲染排行项Row:

  • 排名Text((idx+1).toString()) fontSize14加粗 width24,idx<3时wine酒红色否则textHint灰
  • 封面Emoji fontSize22
  • 信息Column layoutWeight1 margin left10:剧本名fontSize13加粗 + 作者·人数·难度fontSize10 textSecondary margin top2
  • 评分⭐fontSize11 gold金
  • 点击弹出详情
  • 非末项后加Divider border色

情感分类的横滑卡+竖排榜单组合,既提供了视觉化的封面推荐(横滑卡),又提供了信息密集的排行列表(竖排榜单),满足不同浏览习惯的用户需求。

3.3 恐怖:暗黑双列格子

恐怖分类采用"暗黑专区banner+暗黑双列格子"的布局,整体切换为深紫黑#241B2E暗黑主题。

第一部分是暗黑专区bannerColumn width100% padding14 borderRadius14 backgroundColor ‘#241B2E’ margin16:"👻 天黑请闭眼·恐怖专区"fontSize15加粗#E8E0F0浅紫白 + "胆小者请在朋友陪同下开本·已有2.3万人完成挑战"fontSize10 #9B8FA8灰紫 margin top4。暗黑背景+浅紫白文字营造恐怖氛围。

第二部分是暗黑双列格子Row,左右两列Column layoutWeight1,ForEach遍历leftScripts()/rightScripts()渲染暗黑卡片。每张卡片Column width100% borderRadius12 backgroundColor '#2E2338’深紫黑(比banner稍亮)margin:

  • 封面Emoji fontSize36 margin top10
  • 剧本名fontSize13加粗#E8E0F0浅紫白 margin top4
  • 难度·时长fontSize9 diffColor(s.difficulty)(地狱danger红/烧脑酒红/进阶金/入门success绿)margin top4
  • "☠ “+formatHeat(s.heat)+” 人挑战"fontSize9 #9B8FA8灰紫 margin top4 bottom10
  • 点击弹出详情

恐怖分类的核心特色是整体切换暗黑主题——背景从米白#F8F4EC切换为深紫黑#241B2E/#2E2338,文字从textPrimary墨色切换为#E8E0F0浅紫白,辅助文字从textSecondary切换为#9B8FA8灰紫,通过颜色的整体反转营造恐怖悬疑氛围。这种"按题材切换主题色"的设计比所有分类用同一主题更有沉浸感——恐怖本就应该在暗黑背景下浏览,情感本在温暖背景下浏览,推理本在理性背景下浏览,实现了"题材即氛围"的高级设计。

3.4 推理:周组局场次柱状图 + 硬核榜

推理分类采用"周组局场次柱状图+硬核推理榜"的两层布局,是数据可视化最丰富的分类。

剧本模块-推理 - 本周组局场次柱状图与硬核推理榜含难度标签和评分热度

第一部分是柱状图卡片Column width92% borderRadius14 cardBg白底border padding14 margin top14:

  • 标题:"📊 本周组局场次"fontSize14加粗wineDeep深酒红 alignSelf Start
  • 副标题:"周末是拼场黄金期,烧脑本周均场次74场"fontSize10 textSecondary margin top4 alignSelf Start
  • 柱状图Row width100% margin top14:ForEach遍历WEEK_DAYS(周一到周日)渲染柱子Column layoutWeight1 alignItems Center:柱体Column width16 height barHeight(WEEK_GAMES[idx])(v*1.1+‘vp’,如周六132场→145.2vp)borderRadius top4 backgroundColor idx>=5?wine深酒红:wineLight浅酒红(周末深色高亮)+ 星期标签Text(d) fontSize9 textSecondary margin top4
  • 场次数值Row width100% margin top4:ForEach遍历WEEK_GAMES渲染数值Text(g.toString()) fontSize9 idx>=5?wine:textHint layoutWeight1 textAlign Center

WEEK_DAYS = [‘周一’,‘周二’,‘周三’,‘周四’,‘周五’,‘周六’,‘周日’],WEEK_GAMES = [42,58,37,66,89,132,96]。周六132场最高(柱体最高,深酒红高亮),周三37场最低(柱体最矮),周末(周六周日)用深酒红高亮,工作日用浅酒红,通过颜色区分周末工作日。barHeight函数将场次数值乘以1.1后加’vp’单位返回字符串,直接作为Column的height属性值,实现了"数值即柱高"的数据可视化。

第二部分是"🧠 硬核推理榜"竖排列表Column width92% borderRadius14 cardBg白底border padding14 margin top14。ForEach遍历ALL_SCRIPTS渲染排行项Row:

  • 排名Text(‘No.’+(idx+1).toString()) fontSize11加粗 width38,idx<3时gold金否则textHint灰
  • 信息Column layoutWeight1:Row(剧本名fontSize13加粗 + 难度标签fontSize9白字padding 6x1圆角4 backgroundColor diffColor(s.difficulty) margin left8)+ 简介fontSize10 textSecondary maxLines1 Ellipsis margin top3
  • 评分Column alignItems End:⭐fontSize12加粗gold金 + 开本数fontSize9 textSecondary margin top2
  • 点击弹出详情
  • 非末项后加Divider

推理分类的柱状图是本应用数据可视化的核心组件——将一周7天的组局场次数据通过柱高映射为柱状图,配合周末深色高亮和场次数值标注,让用户一眼看出周末是拼场黄金期(周六132场遥遥领先)。这种"数据驱动柱高+颜色区分维度"的柱状图是数据可视化的经典范式,比单纯的数字列表更直观更有表现力。

3.5 阵营:红蓝对决卡 + 阵营本列表

阵营分类采用"红蓝对决卡+阵营本列表"的两层布局。

第一部分是"⚔️ 阵营对决·今日推荐"标题 + 红蓝对决卡Row。对决卡Row width100% margin top10 alignItems Top:

  • 红方Column layoutWeight1 padding12 borderRadius12 cardBg白底border '#F0C8BF’浅红边 margin left16:"🔴 红方阵营"fontSize13加粗#C0392B红 + "皇子党"fontSize11 textSecondary margin top4 + ForEach遍历[‘👑 大皇子’,‘🗡️ 三皇子’,‘📜 翰林学士’,‘💰 户部尚书’]渲染角色Text fontSize11 textPrimary padding 10x4 borderRadius10 backgroundColor '#FBE3DE’浅红底 margin top6
  • 中间VS Column justifyContent Center padding 6x6:Text(‘VS’) fontSize18加粗gold金
  • 蓝方Column layoutWeight1 padding12 borderRadius12 cardBg白底border '#BFD4EA’浅蓝边 margin right16:"🔵 蓝方阵营"fontSize13加粗#2E6DA4蓝 + "太子党"fontSize11 textSecondary margin top4 + ForEach遍历[‘⚖️ 太子’,‘🛡️ 御前侍卫’,‘🏮 内务总管’,‘🕵️ 密探’]渲染角色Text fontSize11 textPrimary padding 10x4 borderRadius10 backgroundColor '#DDE9F5’浅蓝底 margin top6

红蓝对决卡用红方/蓝方两列对比布局,红方浅红底#FBE3DE+红边#F0C8BF+红字#C0392B,蓝方浅蓝底#DDE9F5+蓝边#BFD4EA+蓝字#2E6DA4,中间VS金色分隔,直观呈现阵营对抗的视觉意象。

第二部分是"⚔️ 阵营本精选"竖排列表Column width92% borderRadius14 cardBg白底border padding14 margin top14。ForEach遍历ALL_SCRIPTS渲染项Row:封面Emoji fontSize28 width44 height44 textAlign Center borderRadius10 cardBgDark底 + 信息Column layoutWeight1 margin left10(剧本名fontSize13加粗 + 标签tags fontSize10 textSecondary margin top2)+ 人数Text(s.players) fontSize12加粗wine酒红。点击弹出详情。非末项后加Divider。

3.6 欢乐:标签云 + 紧凑列表

欢乐分类采用"标签云+紧凑列表"的两层布局。

第一部分是"🎉 欢乐解压·标签找本"标题 + 标签云Flex wrap Wrap。Flex({wrap: FlexWrap.Wrap})容器内ForEach遍历[‘爆笑’,‘撕逼’,‘反诈’,‘社交’,‘换装’,‘拍卖’,‘赌狗’,‘嘴炮’,‘整蛊’,‘无厘头’,‘职场’,‘家庭’]12个标签渲染Text(‘#’+t) fontSize12 wine酒红字 padding 12x6 borderRadius14 backgroundColor '#F3E3E8’浅酒红底 margin 6x6。标签云用Flex换行布局,所有标签自动换行排列,用户可点击标签筛选对应题材剧本(本演示中点击未实现筛选但视觉完整)。

第二部分是紧凑列表Column width92% borderRadius14 cardBg白底border padding12 margin top14。ForEach遍历ALL_SCRIPTS渲染项Row:排名Text((idx+1).toString()) fontSize12加粗gold金 width22 + 封面Emoji fontSize20 + 信息Column layoutWeight1 margin left8(剧本名fontSize12加粗 + 时长·人数·难度fontSize9 textSecondary margin top2)+ "组局"按钮Text fontSize10白字padding 10x5 borderRadius10 backgroundColor wine酒红(点击partyScript=0,showPartyModal=true弹出组局弹窗)。padding top8 bottom8。

欢乐分类的紧凑列表比其他分类更紧凑——每行高度更小,信息更密集,且每行都有"组局"按钮直接发起组局,符合欢乐本"快速组局、即时娱乐"的定位。标签云则提供了按关键词找本的快捷入口,是欢乐本这种轻量题材的标准导航方式。


四、车队Tab:发起组局与车队列表

车队Tab以"组队拼场"为核心,采用"发起组局banner+车队列表(进度条)"的布局。

车队模块 - 发起组局酒红渐变banner与正在召集的车队列表含进度条和申请上车按钮

页面顶部是partyTopBar标题行:"🚌 组队大厅"fontSize16加粗wineDeep深酒红 margin left16 + 弹性占位 + "我的车队 2 个"fontSize11 textSecondary margin right16。

第一部分是发起组局bannerRow width100% padding14 borderRadius14 linearGradient angle135从wine到wineDeep酒红渐变 margin16:

  • 左侧信息Column layoutWeight1 alignItems Start:"🚌 发起组局"fontSize14加粗白字 + "挑选剧本·一键喊人"fontSize10 goldLight金字 margin top3
  • 右侧按钮Text(‘立即开团’) fontSize12加粗wineDeep深酒红字 padding 14x8 borderRadius14 backgroundColor gold金(点击showPartyModal=true弹出组局弹窗)

第二部分是"🔥 正在召集的车队"标题 + 车队列表ForEach遍历ALL_PARTIES渲染8个车队卡片Column width92% borderRadius14 cardBg白底border padding14 margin top10:

  • 上半部分Row width100%:
    • 左侧信息Column layoutWeight1 alignItems Start:Row(剧本名p.script fontSize14加粗textPrimary + 局类型标签p.level fontSize9白字padding 6x2圆角4 backgroundColor wine酒红 margin left8)+ "队长 “+p.host+” · "+p.time fontSize11 textSecondary margin top4
    • 右侧人数状态Column alignItems End:Text(p.joined+‘/’+p.need) fontSize14加粗,已满员时success绿否则wine酒红 + Text(p.status) fontSize9,已满员success绿否则gold金 margin top2
  • 进度条Row width100% margin top10:Column layoutWeight(partyProgress(p.joined,p.need)/100) height5 borderRadius3 backgroundColor 已满员?success:wine(已加入比例)+ Column layoutWeight(1-partyProgress/100) height5 borderRadius3 backgroundColor cardBgDark(剩余比例)。用layoutWeight比例实现进度条,比固定宽度更灵活——partyProgress计算joined/need*100百分比,已加入部分占partyProgress/100权重,剩余部分占1-partyProgress/100权重,两者之和为1自动填满Row宽度。
  • 操作按钮Row width100% margin top10:"查看车队"Text fontSize11 textSecondary padding 14x6 borderRadius12 border边框 + "申请上车"Text fontSize11加粗白字padding 14x6 borderRadius12 backgroundColor wine酒红 margin left10 + 弹性占位 + "🗑️"Text fontSize16(点击showDisbandModal=true弹出解散车队弹窗)

8 个车队:雾锁孤宅(阿鬼👻·今晚19:30·5人·已加入3·烧脑局·缺2人)、病院惊魂夜(夜枭🦉·今晚20:00·7人·已加入5·恐怖局·缺2人)、谁是内鬼(老狼🐺·明天14:00·8人·已加入8·阵营局·已满员)、长安十二时辰谜(唐刀🗡️·明天19:00·6人·已加入4·硬核局·缺2人)、月光马戏团(小丑🤡·周六13:30·6人·已加入2·新手局·缺4人)、商海风云(金算盘🧮·周六18:00·7人·已加入6·进阶局·缺1人)、午夜列车(站长🚉·周日15:00·6人·已加入3·烧脑局·缺3人)、爆笑办公室(茶水妹☕·周日19:30·5人·已加入5·欢乐局·已满员)。状态涵盖缺2人/缺4人/缺1人/缺3人/已满员,局类型涵盖烧脑局/恐怖局/阵营局/硬核局/新手局/进阶局/欢乐局,时间从今晚到周日,数据丰富度高。

车队Tab的核心特色是layoutWeight比例进度条——用两个Column的layoutWeight属性值(已加入比例和剩余比例)实现进度条,比固定宽度进度条更灵活更自适应。partyProgress函数计算joined/need*100百分比,已满员时进度条和人数状态都用success绿色,未填满时用wine酒红色,通过颜色区分车队状态。每个车队卡片都有"申请上车"按钮和"🗑️"解散按钮,形成了"查看→申请→解散"的完整车队交互闭环。


五、榜单Tab:前三名领奖台与完整榜单

榜单Tab以"玩家排行"为核心,采用"前三名领奖台+完整榜单"的布局。

榜单模块 - 前三名领奖台布局与完整玩家排行榜含场次胜率和徽章标签

页面顶部是rankTopBar标题行:"🏆 玩家排行榜"fontSize16加粗wineDeep深酒红 margin left16 + 弹性占位 + "赛季 S12 · 距结算 8 天"fontSize11 gold金 margin right16。

第一部分是前三名领奖台Row width92% margin top16 alignItems Bottom(底部对齐,冠军更高):

  • 第二名Column layoutWeight1 padding12 borderRadius14 cardBgDark底border alignItems Center:🥈Emoji fontSize36 + 名字RANK_ENTRIES[1].name fontSize12加粗textSecondary margin top4 + 场次RANK_ENTRIES[1].plays+‘场’ fontSize10 textHint margin top2
  • 第一名Column layoutWeight1.2 padding14 borderRadius14 cardBg白底border 2px gold金边 margin left8 right8 alignItems Center:👑小皇冠Emoji fontSize14 margin bottom2 + 🥇Emoji fontSize46 + 名字RANK_ENTRIES[0].name fontSize13加粗wineDeep深酒红 margin top4 + 场次·胜率Text(plays+‘场·胜率’+winRate(plays,wins)+‘%’) fontSize10 gold金 margin top2
  • 第三名Column layoutWeight1 padding12 borderRadius14 cardBgDark底border alignItems Center:🥉Emoji fontSize36 + 名字RANK_ENTRIES[2].name fontSize12加粗textSecondary margin top4 + 场次fontSize10 textHint margin top2

领奖台布局用alignItems Bottom使三个Column底部对齐,冠军Column layoutWeight1.2(比二三名更宽)、padding14(比二三名padding12更大)、border 2px gold金边(二三名只有1px普通border)、🥇Emoji fontSize46(比二三名36更大),通过宽度/内边距/边框/图标大小四个维度突出冠军的视觉优先级,是领奖台布局的标准设计。winRate函数计算wins/plays*100胜率百分比,冠军名侦探柯北486场402胜→胜率83%。

第二部分是完整榜单Column width92% borderRadius14 cardBg白底border padding14 margin top14。标题"完整榜单"fontSize14加粗wineDeep深酒红。ForEach遍历RANK_ENTRIES渲染10个玩家排行项Row width100% padding top10 bottom10:

  • 排名Text(r.rank.toString()) fontSize13加粗 width28,rank<=3时gold金否则textHint灰
  • 头像Emoji r.avatar fontSize22
  • 信息Column layoutWeight1 margin left10 alignItems Start:名字r.name fontSize13加粗textPrimary + 徽章标签r.badge fontSize9 wine酒红字padding 6x1圆角4 backgroundColor '#F3E3E8’浅酒红底 margin top3
  • 数据Column alignItems End:场次r.plays+’ 场’ fontSize12加粗textPrimary + 胜率’胜率 ‘+winRate(r.plays,r.wins)+’%’ fontSize9 textSecondary margin top2
  • 非末项后加Divider border色

10 个玩家:名侦探柯北(🕵️·486场·402胜·全能王·胜率83%)、推理女王(👑·452场·371胜·逻辑怪·胜率82%)、夜行者(🦇·438场·350胜·剧本通·胜率80%)、戏精本精(🎭·401场·309胜·影帝·胜率77%)、孤鹜(🦅·387场·298胜·还原派·胜率77%)、金算盘(🧮·365场·276胜·阵营鬼才·胜率76%)、冷锋(❄️·342场·254胜·硬核派·胜率74%)、小丑(🃏·321场·231胜·欢乐果·胜率72%)、瓷语(🏺·298场·210胜·情感流·胜率70%)、站长(🚉·276场·188胜·老玩家·胜率68%)。徽章涵盖全能王/逻辑怪/剧本通/影帝/还原派/阵营鬼才/硬核派/欢乐果/情感流/老玩家十种,对应不同玩家风格。

榜单Tab的领奖台布局是本应用最具视觉冲击力的设计——冠军居中更高更宽带金边框,二三名分列两侧稍矮,通过视觉层级自然呈现排名顺序。完整榜单则用紧凑的列表布局展示10个玩家的详细数据(排名/头像/名字/徽章/场次/胜率),徽章用浅酒红底酒红字标签区分玩家风格,胜率用winRate函数动态计算,是排行榜的标准信息架构。


六、我的Tab:个人档案与战绩记录

我的Tab以"个人中心"为核心,采用"个人信息卡+四列统计+最近战绩+创作者中心"的四层布局。

我的模块 - 个人档案信息卡与四列统计数据及最近战绩记录和创作者中心

页面顶部是mineTopBar标题行:"👤 我的档案"fontSize16加粗wineDeep深酒红 margin left16 + 弹性占位 + "推理等级 LV.23"fontSize11 gold金 margin right16。

第一部分是个人信息卡Row width92% padding16 borderRadius16 cardBg白底border margin top16:

  • 头像Text(‘🎭’) fontSize28 width64 height64 textAlign Center borderRadius32 backgroundColor cardBgDark(面具Emoji作为头像)
  • 信息Column layoutWeight1 margin left12 alignItems Start:"推理狂魔"fontSize18加粗wineDeep深酒红 + "LV.23 · 全能王 · 累计 326 场"fontSize11 textSecondary margin top4 + "本命类型:硬核推理 · 沉浸情感"fontSize10 gold金 margin top2
  • 编辑按钮Column:Text(‘编辑’) fontSize11 wine酒红字padding 12x6 borderRadius12 border wine酒红边(点击showSubmitModal=true弹出投稿新本弹窗——本应用中编辑按钮复用了投稿弹窗)

第二部分是四列统计Row width92% padding top14 bottom14 borderRadius14 cardBg白底border margin top12。四列均分layoutWeight1 alignItems Center:

  • 总场次:"326"fontSize18加粗wineDeep深酒红 + "总场次"fontSize10 textSecondary margin top2
  • 胜利场次:"241"fontSize18加粗wineDeep深酒红 + "胜利场次"fontSize10 textSecondary margin top2
  • 总胜率:"74%"fontSize18加粗gold金 + "总胜率"fontSize10 textSecondary margin top2
  • MVP次数:"12"fontSize18加粗gold金 + "MVP次数"fontSize10 textSecondary margin top2

四列统计用2x2的视觉分组——左侧两列wineDeep深酒红(场次类数据),右侧两列gold金(比率/荣誉类数据),通过颜色区分数据类型。总胜率74%和MVP次数12用金色高亮,是玩家最关心的荣誉数据。

第三部分是最近战绩Column width92% borderRadius14 cardBg白底border padding14 margin top12。标题"📜 最近战绩"fontSize14加粗wineDeep深酒红。ForEach遍历MY_RECORDS渲染6条战绩Row width100% padding top10 bottom10:

  • 信息Column layoutWeight1 alignItems Start:剧本名m.script fontSize13加粗textPrimary + “扮演:”+m.role fontSize10 textSecondary margin top3
  • 结果Column alignItems End:判决m.verdict fontSize10,含"凶手"时danger红字backgroundColor '#F9E3E3’浅红底否则success绿字backgroundColor '#E3F0EA’浅绿底padding 8x3 borderRadius8 + 日期m.date fontSize9 textHint margin top3
  • 后加Divider border色

6 条战绩:无人生还的孤岛(法官韦格拉夫·08-22·MVP·还原真相)、血色婚礼(伴娘苏茜·08-19·凶手·成功逃脱)、谁是内鬼(财务总监·08-17·好人·阵营胜利)、雾锁孤宅(二小姐·08-15·平民·情感沉浸)、月光马戏团(驯兽师·08-12·MVP·全场爆笑)、午夜列车(列车员·08-10·好人·惊险生还)。判决涵盖MVP/凶手/好人/平民四种,含"凶手"时用danger红底(凶手逃脱是负面结果但在剧本杀中是成就),否则用success绿底,通过颜色区分判决类型。

第四部分是创作者中心Column width92% borderRadius14 cardBg白底border padding14 margin top12。标题"⚙️ 创作者中心"fontSize14加粗wineDeep深酒红。三个菜单项Row width100% padding top12 bottom12:

  • "✍️ 投稿新剧本"fontSize12 textPrimary layoutWeight1 + "2 个已发布"fontSize10 textSecondary + ">"fontSize14 textHint margin left6(点击showSubmitModal=true)
  • Divider
  • "🏪 我的剧本店"fontSize12 textPrimary layoutWeight1 + "营业中"fontSize10 success绿 + ">"fontSize14 textHint margin left6
  • Divider
  • "🎫 优惠券"fontSize12 textPrimary layoutWeight1 + "3 张可用"fontSize10 gold金 + ">"fontSize14 textHint margin left6

我的Tab的四层布局(个人信息→统计数据→战绩记录→创作者中心)是个人中心的标准信息架构——从个人身份到数据统计到历史记录到功能入口,层层递进。战绩记录中的"凶手"判决用danger红底高亮,是剧本杀特色——在剧本杀中扮演凶手并成功逃脱是一种成就,用红色区分于好人胜利的绿色,符合剧本杀的游戏逻辑。


七、色彩系统:酒红暗金悬疑剧本风

QQ剧本杀推理馆的整体视觉基调建立在"酒红+暗金·悬疑剧本风"之上,这一配色通过一个interface与一个const常量共同定义。

interface ColorPalette {
  wine: string; wineLight: string; wineDeep: string;
  gold: string; goldLight: string;
  bg: string; cardBg: string; cardBgDark: string;
  textPrimary: string; textSecondary: string; textHint: string;
  white: string; border: string; danger: string; success: string;
}

const COLORS: ColorPalette = {
  wine: '#6D1F36', wineLight: '#96455C', wineDeep: '#4E1425',
  gold: '#C9A227', goldLight: '#E5C766',
  bg: '#F8F4EC', cardBg: '#FFFFFF', cardBgDark: '#F0E9DC',
  textPrimary: '#38272E', textSecondary: '#77616B', textHint: '#B5A6AD',
  white: '#FFFFFF', border: '#E8DECD',
  danger: '#CC4444', success: '#4C8A6E'
};

ColorPalette接口声明了16个颜色字段,使用interface而非直接使用对象字面量是ArkTS推荐的类型优先实践——编译器在编译期校验COLORS常量是否完整提供了每一个字段。从语义命名可以看出,颜色被划分为主题色(wine/wineLight/wineDeep三级酒红/gold/goldLight两级金)、背景色(bg/cardBg/cardBgDark三级)、文本色三级(textPrimary/textSecondary/textHint)、边框色(border)、语义色(danger/success)以及白色(white)。

COLORS常量是配色体系的实际载体:

  • wine #6D1F36:酒红主色,用于按钮背景、进度条、激活标签、难度烧脑标签、题材标签等,是剧本杀悬疑主题的核心色(酒红暗示血色、悬疑、戏剧张力)
  • wineLight #96455C:浅酒红,用于柱状图工作日柱体、投稿时长选中标签,比主色浅更柔和
  • wineDeep #4E1425:深酒红,用于标题强调、按钮文字、柱状图周末柱体高亮、排名前三高亮,比主色更深更稳重
  • gold #C9A227:暗金主色,用于组局按钮背景、评分文字、热度文字、冠军边框、徽章文字,暗金暗示复古、悬疑、侦探道具的金属质感
  • goldLight #E5C766:浅金,用于头部副标题文字、banner信息文字,比主金更亮更柔和
  • bg #F8F4EC:米白偏暖背景,营造旧纸张、泛黄剧本的质感
  • cardBg #FFFFFF:卡片背景纯白
  • cardBgDark #F0E9DC:深卡片背景,用于头像底、标签底、进度条剩余部分
  • textPrimary #38272E:文字主色深褐红(非纯黑,带酒红调),与酒红主题呼应
  • textSecondary #77616B:次要文字灰褐红
  • textHint #B5A6AD:提示文字浅灰紫红
  • border #E8DECD:边框色浅米黄
  • danger #CC4444:危险色朱红,用于解散按钮、凶手判决、难度地狱标签
  • success #4C8A6E:成功色墨绿,用于已满员状态、好人判决、难度入门标签

本应用采用"酒红+暗金"悬疑剧本风主题设计——wine #6D1F36酒红定主(血色悬疑戏剧张力),wineDeep #4E1425深酒红定强调,wineLight #96455C浅酒红定柔和,gold #C9A227暗金定点缀(复古侦探金属质感),goldLight #E5C766浅金定文字,bg #F8F4EC米白偏暖定底(旧纸张泛黄剧本质感)。主色体系采用"三级酒红+两级金+米白底"七色组合——酒红定主、深酒红定强调、浅酒红定柔和、暗金定点缀、浅金定文字、米白定底,七色各司其职形成"以酒红定悬疑、以暗金定复古、以米白定旧纸"的剧本杀语义着色体系。textPrimary #38272E深褐红(非纯黑带酒红调)是本应用文字色的特色——用带酒红调的深褐红代替纯黑,更符合剧本杀"泛黄旧纸+血色悬疑"的视觉氛围。

本应用另一个色彩处理特色是恐怖分类整体切换暗黑主题——恐怖Tab的背景从米白#F8F4EC切换为深紫黑#241B2E/#2E2338,文字从textPrimary #38272E切换为#E8E0F0浅紫白,辅助文字从textSecondary切换为#9B8FA8灰紫,通过颜色的整体反转营造恐怖悬疑氛围。这种"按题材切换主题色"的设计比所有分类用同一主题更有沉浸感。genreTagColor函数将6种题材映射为6种颜色(情感#D46A8A粉/恐怖#5A4A6E紫/推理#3D6B8E蓝/阵营#B85C38橙/欢乐#C9A227金),diffColor函数将4种难度映射为4种颜色(地狱danger红/烧脑wine酒红/进阶gold金/入门success绿),heatColor函数将热度映射为3种颜色(>9000wineDeep深酒红/>7000wine酒红/其他gold金),三个颜色映射函数配合实现了"数据即颜色"的动态着色体系。统一的色彩系统是整个应用的视觉基石,酒红暗金悬疑主题配合多维度动态着色,营造出既悬疑戏剧又复古雅致的剧本杀氛围。


八、四类@Observed数据模型与10个工具函数

8.1 四类@Observed数据模型

应用定义了四类@Observed数据模型,每类对应一个业务实体,字段语义清晰且都通过constructor完成初始化。

  • ScriptItem1:剧本实体(id/title/author/cover/genre/players/duration/difficulty/rating/heat/intro/tags,12字段),是字段最多的实体,涵盖剧本的完整信息——ID/标题/作者/封面Emoji/题材/人数/时长/难度/评分/热度/简介/标签。15个剧本通过buildScripts()函数构建为ALL_SCRIPTS常量数组。
  • PartyItem:车队实体(id/script/host/time/need/joined/level/status,8字段),涵盖车队的完整信息——ID/剧本名/队长(带Emoji)/时间/需要人数/已加入人数/局类型/状态。8个车队直接定义为ALL_PARTIES常量数组。
  • RankEntry:榜单实体(rank/name/avatar/plays/wins/badge,6字段),涵盖玩家排行的完整信息——排名/名字/头像Emoji/场次/胜场/徽章。10个玩家直接定义为RANK_ENTRIES常量数组。
  • RecordItem:战绩实体(id/script/role/date/verdict,5字段),涵盖个人战绩的完整信息——ID/剧本名/扮演角色/日期/判决。6条战绩直接定义为MY_RECORDS常量数组。

每个类都给了默认值并通过constructor初始化,@Observed装饰整个类使得属性变更可被框架自动监听。与之前折扇坊应用的8个@Observed类不同,本应用只有4个@Observed类但每个类字段更多(ScriptItem1有12字段),数据复杂度集中在剧本实体上。剧本数据通过buildScripts()函数集中构建,再通过leftScripts()/rightScripts()拆分为双列,这种"函数构建+常量拆分"的模式比直接在组件内定义数组更灵活。

8.2 十个工具函数

应用定义了10个工具函数,将数据到样式/位置/格式的映射逻辑从UI层抽离。

function heatColor(heat: number): string {
  if (heat > 9000) return COLORS.wineDeep;
  if (heat > 7000) return COLORS.wine;
  return COLORS.gold;
}
function diffColor(diff: string): string {
  if (diff === '地狱') return COLORS.danger;
  if (diff === '烧脑') return COLORS.wine;
  if (diff === '进阶') return COLORS.gold;
  return COLORS.success;
}
function genreTagColor(genre: string): string {
  if (genre === '情感') return '#D46A8A';
  if (genre === '恐怖') return '#5A4A6E';
  if (genre === '推理') return '#3D6B8E';
  if (genre === '阵营') return '#B85C38';
  return '#C9A227';
}
function barHeight(v: number): string {
  return (v * 1.1).toFixed(0) + 'vp';
}
function formatHeat(heat: number): string {
  return (heat / 1000).toFixed(1) + 'k';
}
function partyProgress(joined: number, need: number): number {
  if (need === 0) return 0;
  return Math.round(joined / need * 100);
}
function winRate(plays: number, wins: number): number {
  if (plays === 0) return 0;
  return Math.round(wins / plays * 100);
}
  • heatColor:热度映射颜色——>9000深酒红(爆款)、>7000酒红(热门)、其他金(普通),三级颜色区分热度等级
  • diffColor:难度映射颜色——地狱danger红、烧脑wine酒红、进阶gold金、入门success绿,四级颜色区分难度等级
  • genreTagColor:题材映射标签色——情感#D46A8A粉、恐怖#5A4A6E紫、推理#3D6B8E蓝、阵营#B85C38橙、欢乐#C9A227金,五种题材五种颜色(机制题材默认金)
  • barHeight:场次映射柱高——v*1.1后toFixed(0)加’vp’单位返回字符串,直接作为Column的height属性值
  • formatHeat:热度格式化——heat/1000后toFixed(1)加’k’,如9820→’9.8k’
  • partyProgress:车队进度——joined/need*100后Math.round,need=0时返回0防除零
  • winRate:胜率——wins/plays*100后Math.round,plays=0时返回0防除零
  • buildParticles:构建16个粒子初始位置——循环16次,x=(i41)%340+10,y=80+(i67)%560,size=10+(i*5)%14,opacity=0.2+(i%4)*0.1,icon=PARTICLE_ICONS[i%5](五种侦探道具循环)
  • driftParticles:粒子漂移——每个粒子y减4,x加sin(p.id+p.y/50)*3左右摆动,y<60时y=640回到底部形成循环
  • leftScripts/rightScripts:剧本拆列——leftScripts取偶数索引(0,2,4…),rightScripts取奇数索引(1,3,5…),用于双列瀑布流

10个工具函数中,heatColor/diffColor/genreTagColor三个是颜色映射函数,barHeight/formatHeat/partyProgress/winRate四个是数值/格式映射函数,buildParticles/driftParticles两个是粒子动画函数,leftScripts/rightScripts两个是数据拆分函数。颜色映射函数实现了"数据即颜色"的动态着色,数值映射函数实现了"数据即尺寸/格式"的动态可视化,粒子函数实现了自定义动画系统,拆分函数实现了双列布局的数据准备。partyProgress和winRate两个函数都有除零保护(need=0或plays=0时返回0),是防御性编程的体现。driftParticles中的sin函数让粒子x坐标随y变化左右摆动,形成自然的漂浮轨迹而非直线上升,是粒子动画的细节优化。


九、侦探道具粒子动画系统

侦探道具粒子动画系统是本应用最具创意的技术亮点,通过setInterval手动驱动+ForEach渲染+position绝对定位实现了ArkUI中难以通过animation属性实现的自定义粒子动画。

9.1 粒子数据结构与初始化

interface ParticleItem {
  id: number; x: number; y: number; size: number; opacity: number; icon: string;
}
const PARTICLE_ICONS: string[] = ['🔍', '🕯️', '🗝️', '✨', '💡'];

function buildParticles(): ParticleItem[] {
  const arr: ParticleItem[] = [];
  for (let i = 0; i < 16; i++) {
    arr.push({
      id: i,
      x: (i * 41) % 340 + 10,
      y: 80 + (i * 67) % 560,
      size: 10 + (i * 5) % 14,
      opacity: 0.2 + (i % 4) * 0.1,
      icon: PARTICLE_ICONS[i % PARTICLE_ICONS.length]
    });
  }
  return arr;
}

ParticleItem接口定义了粒子的6个属性——id(唯一标识)、x/y(位置坐标)、size(图标大小)、opacity(透明度)、icon(图标Emoji)。PARTICLE_ICONS常量定义了5种侦探道具图标——🔍放大镜(搜索线索)、🕯️蜡烛(照亮黑暗)、🗝️钥匙(开启真相)、✨闪光(灵感闪现)、💡灯泡(恍然大悟),这5种图标都是侦探推理主题的经典意象,与剧本杀"寻找真相"的核心玩法高度契合。

buildParticles()函数通过循环16次构建16个粒子的初始状态:

  • x坐标:(i*41)%340+10,用质数41作为步长取模340后加10,使16个粒子的x坐标在10-350范围内均匀分布(质数步长避免规律性聚集)
  • y坐标:80+(i*67)%560,用质数67作为步长取模560后加80,使y坐标在80-640范围内均匀分布
  • size:10+(i*5)%14,大小在10-23范围内变化
  • opacity:0.2+(i%4)*0.1,透明度在0.2-0.5范围内变化(i%4循环4级)
  • icon:PARTICLE_ICONS[i%5],5种图标循环分配

用质数作为步长取模是生成伪随机均匀分布的经典技巧——41和67都是质数,与16互质,取模后能保证16个粒子在x和y方向都均匀分布不会聚集。

9.2 粒子漂移算法

function driftParticles(list: ParticleItem[]): ParticleItem[] {
  const next: ParticleItem[] = [];
  for (let i = 0; i < list.length; i++) {
    const p = list[i];
    const ny = p.y - 4;
    next.push({
      id: p.id,
      x: p.x + Math.sin(p.id + p.y / 50) * 3,
      y: ny < 60 ? 640 : ny,
      size: p.size,
      opacity: p.opacity,
      icon: p.icon
    });
  }
  return next;
}

driftParticles()函数接收当前粒子数组,返回下一帧的粒子数组:

  • y坐标:p.y-4,每帧向上移动4像素
  • x坐标:p.x+Math.sin(p.id+p.y/50)*3,用sin函数计算水平偏移量——sin的参数是p.id+p.y/50(id加y坐标除以50),乘以3得到偏移量在-3到+3之间。由于每个粒子id不同且y在变化,sin值也在变化,使每个粒子的水平摆动轨迹不同,形成自然的漂浮效果而非整齐划一地上升
  • 循环边界:ny<60时y=640,当粒子飘到顶部(y<60)时回到底部(y=640),形成无限循环的粒子流
  • size/opacity/icon保持不变

sin函数中的p.y/50是为了让sin参数随y缓慢变化——y每变化50像素sin值才完成一个周期,这样粒子的水平摆动是缓慢的而非剧烈的,模拟空气中微粒自然漂浮的轨迹。如果直接用p.y作为sin参数,y每变化1像素sin值就剧烈变化,粒子会左右剧烈抖动不自然。

9.3 定时器驱动与生命周期管理

@Entry
struct Index {
  @State particles: ParticleItem[] = buildParticles();
  private timerId: number = -1;

  aboutToAppear() {
    this.timerId = setInterval(() => {
      this.particles = driftParticles(this.particles);
    }, 130);
  }

  aboutToDisappear() {
    if (this.timerId >= 0) {
      clearInterval(this.timerId);
    }
  }

particles是@State装饰的粒子数组,初始值为buildParticles()构建的16个粒子。timerId是私有变量保存定时器ID,初始-1表示未启动。

aboutToAppear生命周期回调在组件创建后启动setInterval——每130毫秒调用一次driftParticles(this.particles)更新粒子数组,并将返回值赋给this.particles。由于particles是@State,赋值后框架自动检测到数组引用变化,触发依赖particles的UI重新渲染(即ForEach粒子列表重新渲染)。130毫秒的间隔是经验值——太快(<100ms)会导致性能消耗大且动画过于流畅不自然,太慢(>200ms)会导致动画卡顿有跳帧感,130ms刚好让粒子以自然的速度向上漂浮。

aboutToDisappear生命周期回调在组件销毁前清除定时器——如果timerId>=0(定时器已启动),调用clearInterval(this.timerId)清除。这是必须的——如果不清除定时器,组件销毁后setInterval仍在后台运行,会导致内存泄漏和性能问题。aboutToAppear启动+aboutToDisappear清除的配对使用,是ArkUI中使用定时器的标准生命周期管理模式。

9.4 粒子渲染与key技巧

build() {
  Stack() {
    Column() { /* 主内容 */ }
    ForEach(this.particles, (p: ParticleItem) => {
      Text(p.icon)
        .fontSize(p.size)
        .opacity(p.opacity)
        .position({ x: p.x, y: p.y })
    }, (p: ParticleItem) => p.id.toString() + '_' + p.y.toFixed(0))
  }
}

粒子渲染在Stack根容器中,ForEach遍历this.particles渲染每个粒子为Text组件——Text显示p.icon(侦探道具Emoji),fontSize为p.size,opacity为p.opacity,position为{x:p.x, y:p.y}绝对定位。Stack的叠层特性使粒子能覆盖在主内容(Column)之上,形成悬浮粒子效果。

key技巧是粒子动画的核心——ForEach的第三个参数(键值生成器)是p.id.toString()+‘‘+p.y.toFixed(0),即粒子id加y坐标取整的字符串拼接。这个key包含了动态变化的y坐标,意味着每帧y变化时key也变化,框架会认为这是一个"新"的组件而非"更新"的组件,从而强制重新渲染该粒子。如果只用p.id作为key,框架会认为是同一个组件只更新属性,在某些情况下可能不会触发position的重新计算导致粒子不移动。用id+’’+y.toFixed(0)作为key保证了每帧粒子都被强制重新渲染,是实现逐帧动画的关键技巧。y.toFixed(0)将y取整为字符串,避免浮点数精度问题导致key不稳定。

侦探道具粒子动画系统是本应用最具技术深度的部分——它展示了如何在ArkUI中通过setInterval手动驱动状态更新+@State响应式+ForEach重新渲染+position绝对定位+动态key强制刷新,实现animation属性无法完成的自定义粒子动画。这种方案虽然比CSS动画或Canvas动画性能稍差,但在ArkUI声明式框架内是实现复杂自定义动画的标准且可靠的方案。16个粒子以130ms间隔更新,每个粒子有独立的sin摆动轨迹和循环边界,整体形成了侦探道具(🔍🕯️🗝️✨💡)在屏幕上缓缓向上漂浮的悬疑氛围,与剧本杀"寻找真相"的主题高度契合。


十、4个模态弹窗:组局/投稿/解散/详情

应用通过4个模态弹窗实现了增删改查交互闭环,每个弹窗都是独立@Builder,通过show*布尔状态条件渲染。

10.1 组局弹窗 buildPartyModal(新增)

组局弹窗是新增车队的交互入口,酒红渐变头部,包含选择剧本/局类型/开始时间/人数加减器四个表单项。

弹窗外层Column width100% height100% justifyContent Center alignItems Center backgroundColor ‘99000000’(60%透明黑遮罩),点击遮罩onClick关闭。内层Column width86% borderRadius16 white白底clip true(裁剪圆角)。

头部Column width100% padding16 alignItems Start linearGradient angle135从wine到wineDeep酒红渐变:"🚌 发起组局"fontSize17加粗白字 + "选好剧本,等队友上车"fontSize11 goldLight金字 margin top4。

表单区Scroll scrollable Vertical constraintSize maxHeight 60%,内Column width100% alignItems Start padding left18 right18:

  • 选择剧本:标签 + Flex wrap Wrap,ForEach遍历6个剧本名渲染标签Text,选中时(partyScript===idx)白字wine酒红底,未选中时textSecondary字cardBgDark底,点击partyScript=idx
  • 局类型:标签 + Row,ForEach遍历[‘新手局’,‘进阶局’,‘烧脑局’]渲染Column layoutWeight1,选中时白字wine酒红底+wine边框,未选中时textSecondary字透明底+border边框,点击partyLevel=idx
  • 开始时间:标签 + Flex wrap Wrap,ForEach遍历5个时间选项渲染标签Text,选中时白字gold金底,未选中时textSecondary字cardBgDark底,点击partyTime=idx
  • 需要人数:标签"需要人数:“+partySize+” 人" + Row,减号按钮Text(‘−’) fontSize20 wine酒红字width38 height38 textAlign Center borderRadius19 cardBgDark底(点击partySize>4时减1)+ 数字Text(partySize) fontSize18加粗layoutWeight1 textAlign Center + 加号按钮Text(‘+’)(点击partySize<9时加1)。人数范围4-9人,符合剧本杀常见人数范围
  • 操作按钮Row margin top20 bottom16:"取消"textSecondary字padding 20x10 borderRadius18 border边框(点击关闭)+ "🚌 开团"白字加粗padding 24x10 borderRadius18 wine酒红底margin left10(点击关闭)

10.2 投稿新本弹窗 submitScriptModal(编辑)

投稿新本弹窗是编辑/投稿剧本的交互入口,暗金编辑风头部(#F5EDD8浅金底),包含剧本名称TextInput/类型/难度/时长四个表单项。

头部Column width100% padding16 alignItems Start backgroundColor '#F5EDD8’浅金底borderRadius top16:"✍️ 投稿新剧本"fontSize17加粗wineDeep深酒红字 + "原创剧本经审核后上架,可获得分成"fontSize11 gold金字 margin top4。暗金头部与组局弹窗的酒红头部形成视觉区分——投稿是创作行为用暖金色,组局是社交行为用酒红色。

表单区Scroll constraintSize maxHeight 56%:

  • 剧本名称:标签 + TextInput placeholder’例如:午夜图书馆’ text绑定submitTitle fontSize13 placeholderColor textHint backgroundColor cardBgDark borderRadius10 padding 12x height42 margin top6,onChange回写submitTitle
  • 剧本类型:标签 + Flex wrap Wrap,ForEach遍历[‘情感’,‘恐怖’,‘推理’,‘阵营’,‘欢乐’,‘机制’]6种类型渲染标签,选中时白字gold金底,未选中时textSecondary字cardBgDark底,点击submitGenre=idx
  • 难度定位:标签 + Row,ForEach遍历[‘入门’,‘进阶’,‘烧脑’,‘地狱’]4种难度渲染Text layoutWeight1 textAlign Center padding 7x0 borderRadius10,选中时白字wine酒红底+wine边框,未选中时textSecondary字透明底+border边框,点击submitDiff=idx
  • 预计时长:标签 + Flex wrap Wrap,ForEach遍历[‘2小时’,‘3小时’,‘4小时’,‘5小时’]4种时长渲染标签,选中时白字wineLight浅酒红底,未选中时textSecondary字cardBgDark底,点击submitDuration=idx
  • 操作按钮Row:"存草稿"textSecondary字padding 18x10 borderRadius18 border边框(点击关闭)+ "提交审核"白字加粗padding 22x10 borderRadius18 gold金底margin left10(点击关闭)

10.3 解散车队弹窗 disbandPartyModal(删除)

解散车队弹窗是删除车队的确认型对话框,警告小卡风格,比其他弹窗更小更简洁。

外层Column width100% height100% justifyContent Center alignItems Center backgroundColor ‘99000000’,点击遮罩关闭。内层Column width78% borderRadius16 white白底alignItems Center padding top24:

  • ⚠️警告Emoji fontSize40
  • "解散这个车队?"fontSize16加粗textPrimary margin top10
  • "已上车的队友将收到解散通知,此操作不可撤销"fontSize11 textSecondary textAlign Center margin top8 left20 right20
  • 操作按钮Row margin top20 bottom18:"再想想"textSecondary字padding 20x10 borderRadius18 border边框(点击关闭)+ "确认解散"白字加粗padding 20x10 borderRadius18 danger朱红底margin left10(点击关闭)

解散弹窗width78%比其他弹窗(86%/88%)更小,位置居中,符合确认对话框"小巧居中"的视觉定位。danger朱红底"确认解散"按钮明确警示操作的不可逆性,配合"此操作不可撤销"的文案提示和"再想想"取消按钮的双重防护,避免了误操作导致的车队解散。

10.4 剧本详情弹窗 scriptDetailModal(查询)

剧本详情弹窗是查看剧本详细信息的查询入口,酒红渐变头部,包含剧本大卡信息+组局/收藏按钮+角色列表+玩家评价四个部分,是内容最丰富的弹窗。

头部Column width100% padding16 linearGradient angle135从wineDeep到wine酒红渐变:Row("📖 剧本详情"fontSize16加粗白字layoutWeight1 + selectedScript存在时显示标题+作者fontSize11 goldLight金字 margin top3 + "✕"关闭按钮白字padding8点击关闭)。

内容区Scroll constraintSize maxHeight 58%,selectedScript存在时渲染:

  • 大卡信息Row width100% padding 16x14:封面Emoji fontSize52 width88 height88 textAlign Center borderRadius14 backgroundColor '#241B2E’深紫黑底 + 信息Column layoutWeight1 margin left12(⭐评分fontSize20加粗gold金 + 🔥热度fontSize11 wine酒红 margin top4 + 👥人数·⏱时长fontSize11 textSecondary margin top4 + 🎯难度fontSize11 diffColor(difficulty) margin top4)
  • 简介Text selectedScript.intro fontSize12 textSecondary margin top12 left16 right16
  • 操作按钮Row margin top14:"组局开本"wineDeep深酒红字加粗padding 24x10 borderRadius18 gold金底(点击关闭详情+showPartyModal=true跳转到组局弹窗)+ "收藏本子"wine酒红字padding 24x10 borderRadius18 border wine酒红边margin left10(点击关闭)
  • 角色列表:标题"🎭 角色列表"fontSize14加粗wineDeep深酒红 margin top16 left16 + ForEach遍历SCRIPT_ROLES(5个角色:家主沈青山/大少爷沈亦/二小姐沈芷/管家福伯/私人医生林默)渲染Row(角色图标Emoji fontSize22 width40 height40 textAlign Center borderRadius20 cardBgDark底 + 信息Column layoutWeight1 margin left10:角色名fontSize12加粗textPrimary + 简介fontSize10 textSecondary margin top2)padding 8x16
  • 玩家评价:标题"💬 玩家评价"fontSize14加粗wineDeep深酒红 margin top14 left16 + ForEach遍历SCRIPT_REVIEWS(3条评价:戏精本精/名侦探柯北/小丑)渲染Column(评价者Row:头像Emoji+名字fontSize12加粗textPrimary margin left8+弹性占位+星级⭐fontSize11 gold金 + 评价内容fontSize11 textSecondary margin top6)width100% padding12 borderRadius12 cardBgDark底margin top8 left16 right16

剧本详情弹窗是4个弹窗中内容最丰富的——从剧本大卡信息到简介,从操作按钮到角色列表,从玩家评价到完整的剧本信息展示,形成了"查看→组局→收藏"的完整剧本详情交互闭环。角色列表展示了5个可选角色的姓名和简介(以雾锁孤宅剧本为例),玩家评价展示了3条真实玩家的评价和星级,这些信息帮助玩家在组局前充分了解剧本。点击"组局开本"按钮会关闭详情弹窗并自动打开组局弹窗,实现了"详情→组局"的流畅跳转。

四个弹窗各自独立@Builder,通过showPartyModal/showSubmitModal/showDisbandModal/showDetailModal四个布尔状态条件渲染,弹窗外层统一用Column width100% height100% justifyContent Center + backgroundColor '99000000’实现遮罩+居中,点击遮罩onClick关闭。四个弹窗覆盖了组局社交平台的核心交互——组局(新增车队)、投稿(编辑剧本)、解散(删除车队)、详情(查看剧本),形成了完整的增删改查交互闭环。


十一、特性对比表

技术维度 具体实现 作用与特点 适用场景
色彩管理 ColorPalette接口+COLORS 16色 interface类型约束编译期校验,酒红暗金悬疑剧本风 全局配色统一管理
酒红主色 wine #6D1F36 按钮/进度条/激活标签/烧脑难度,血色悬疑戏剧张力 剧本杀主题核心色
深酒红强调 wineDeep #4E1425 标题强调/按钮文字/周末柱体高亮/前三排名,更深稳重 强调色
浅酒红柔和 wineLight #96455C 柱状图工作日柱体/投稿时长选中,更柔和 柔和色
暗金点缀 gold #C9A227 组局按钮/评分文字/冠军边框/徽章文字,复古侦探金属质感 点缀色
浅金文字 goldLight #E5C766 头部副标题/banner信息文字,更亮柔和 辅助文字
米白偏暖底 bg #F8F4EC 旧纸张泛黄剧本质感 应用底色
深褐红文字 textPrimary #38272E 非纯黑带酒红调,与酒红主题呼应 文字主色
恐怖暗黑主题 #241B2E/#2E2338背景+#E8E0F0文字 恐怖Tab整体切换暗黑主题,颜色反转营造恐怖氛围 按题材切换主题
底部4Tab 剧本📖/车队🚌/榜单🏆/我的👤 一级导航切换大模块,激活态opacity1+wineDeep字,非激活态opacity0.5 应用一级导航
顶部6Tab 热门🔥/情感💔/恐怖👻/推理🕵️/阵营⚔️/欢乐🎉 剧本模块二级分类,横向Scroll可滑动,激活态wine字+金色Divider 剧本题材分类
双层导航架构 底部一级+顶部二级 底部切大模块,顶部在模块内细分分类,内容型应用标准架构 内容型应用导航
热门banner大卡 linearGradient酒红渐变+立即组局/查看详情按钮 编辑力荐位,酒红渐变背景+白字+金按钮,视觉冲击力强 推荐位
双列瀑布流 leftScripts()/rightScripts()奇偶拆列+双Column 偶数索引左列奇数右列,卡片高度不同形成自然瀑布流 剧本列表展示
情感横滑卡 Scroll Horizontal+100x120圆角卡片 横向滑动浏览封面卡,推荐位标准布局 情感本推荐
恐怖暗黑双列 #241B2E背景+#2E2338卡片+#E8E0F0文字 整体暗黑主题,颜色反转营造恐怖氛围 恐怖本氛围
推理柱状图 WEEK_DAYS 7天+WEEK_GAMES 7数值+barHeight(v*1.1) 周组局场次可视化,周末wine深酒红高亮,工作日wineLight 数据可视化
阵营红蓝对决 红方#FBE3DE底+#C0392B字/蓝方#DDE9F5底+#2E6DA4字+VS金 红蓝对抗视觉,阵营博弈意象 阵营本展示
欢乐标签云 Flex wrap Wrap+12个#标签 关键词找本快捷入口,自动换行排列 欢乐本导航
车队进度条 layoutWeight(partyProgress/100)+layoutWeight(1-progress/100) 用权重比例实现进度条,比固定宽度更灵活自适应 车队人数进度
领奖台布局 冠军layoutWeight1.2+padding14+2px金边+🥇46号,二三名layoutWeight1+padding12+1px边 底部对齐,冠军更高更宽带金边框,视觉层级突出排名 前三名展示
个人信息卡 头像64x64圆形+名字+等级+本命类型+编辑按钮 个人身份展示,圆形头像+等级徽章 个人中心
四列统计 总场次/胜利场次wineDeep+总胜率/MVP次数gold 2x2颜色分组,场次类深酒红/荣誉类金色 个人数据统计
战绩凶手判决 含"凶手"danger红底#F9E3E3/否则success绿底#E3F0EA 剧本杀特色,凶手逃脱是成就用红色区分 战绩记录
@Observed ScriptItem1 剧本实体12字段15个剧本 id/title/author/cover/genre/players/duration/difficulty/rating/heat/intro/tags 剧本数据
@Observed PartyItem 车队实体8字段8个车队 id/script/host/time/need/joined/level/status 车队数据
@Observed RankEntry 榜单实体6字段10个玩家 rank/name/avatar/plays/wins/badge 玩家排行数据
@Observed RecordItem 战绩实体5字段6条战绩 id/script/role/date/verdict 个人战绩数据
buildScripts() 函数构建15个剧本ALL_SCRIPTS 集中管理剧本数据,便于维护扩展 数据初始化
leftScripts/rightScripts 奇偶索引拆分数组 偶数左列奇数右列,双列瀑布流数据准备 双列布局
heatColor >9000wineDeep/>7000wine/其他gold 热度三级颜色映射,爆款深酒红/热门酒红/普通金 热度着色
diffColor 地狱danger/烧脑wine/进阶gold/入门success 难度四级颜色映射,地狱红/烧脑酒红/进阶金/入门绿 难度着色
genreTagColor 情感粉/恐怖紫/推理蓝/阵营橙/欢乐金 题材五种颜色映射,每种题材独立颜色 题材标签着色
barHeight v*1.1+‘vp’ 场次映射柱高字符串,直接作为height属性值 柱状图高度
formatHeat heat/1000+‘k’ 热度格式化,如9820→9.8k 热度显示
partyProgress joined/need*100 Math.round 车队进度百分比,need=0返回0防除零 车队进度条
winRate wins/plays*100 Math.round 胜率百分比,plays=0返回0防除零 玩家胜率
粒子接口 ParticleItem id/x/y/size/opacity/icon 6字段 粒子数据结构,位置/大小/透明度/图标 粒子动画
侦探道具图标 🔍🕯️🗝️✨💡五种 放大镜/蜡烛/钥匙/闪光/灯泡,侦探推理经典意象 粒子图标
buildParticles() 16粒子质数步长取模均匀分布 x=(i41)%340+10 y=80+(i67)%560,质数41/67避免聚集 粒子初始化
driftParticles() y-4 + x+sin(id+y/50)*3 + y<60→640循环 向上漂移+sin左右摆动+底部循环,自然漂浮轨迹 粒子逐帧更新
setInterval 130ms aboutToAppear启动+aboutToDisappear清除 每130ms更新粒子数组,@State赋值触发重渲染 动画驱动
粒子key技巧 id.toString()+‘_’+y.toFixed(0) key包含动态y,每帧y变化key变化强制重新渲染 逐帧动画关键
Stack粒子叠加 ForEach粒子在Stack中position绝对定位 粒子覆盖在主内容之上,悬浮效果 粒子渲染
组局弹窗 酒红渐变头+剧本/类型/时间/人数加减器 新增车队,4-9人范围,Flex/Rows选择器 组局交互
投稿弹窗 暗金#F5EDD8头+名称TextInput/类型/难度/时长 编辑投稿剧本,创作行为用暖金色 投稿交互
解散弹窗 width78%警告小卡+danger红底确认+不可撤销提示 删除车队,双重防护避免误操作 解散交互
详情弹窗 酒红渐变头+大卡信息+组局/收藏+角色列表+玩家评价 查看剧本,内容最丰富,详情→组局流畅跳转 详情交互
弹窗遮罩 Column width100% height100% + bg ‘99000000’ + onClick关闭 60%透明黑遮罩+居中,点击遮罩关闭 模态弹窗通用
TextInput受控 text绑定@State字符串+onChange回写 投稿剧本名称受控输入,提交时可读取 表单输入
人数加减器 −/+按钮+partySize状态+范围4-9 点击加减更新人数,边界检查防止越界 人数选择
linearGradient angle135 colors[[wine,0],[wineDeep,1]] 酒红渐变背景,用于头部/banner/弹窗头 渐变背景
Scroll横向 scrollable Horizontal+scrollBar Off 顶部6Tab/情感横滑卡横向滑动 横向滚动
Flex换行 Flex({wrap:FlexWrap.Wrap}) 标签云/剧本选择/时间选择自动换行排列 标签布局
aboutToAppear 组件创建后启动setInterval 生命周期启动定时器,粒子动画开始 生命周期
aboutToDisappear 组件销毁前clearInterval 生命周期清除定时器,防止内存泄漏 生命周期
ForEach键值生成器 (item)=>唯一标识字符串 列表高效diff更新,粒子key包含y强制刷新 所有列表渲染
layoutWeight 权重分配列均分/进度条比例 统计卡四列均分/车队进度条比例 布局分配
constraintSize maxHeight 限制弹窗表单最大高度 防止内容溢出弹窗,长表单可滚动 模态弹窗
borderRadius + clip true 弹窗圆角+裁剪内容 内层卡片圆角,clip true裁剪超出圆角的内容 弹窗圆角

完整代码

完整 1087.ets 源码已包含:配色系统(ColorPalette接口+COLORS 16色酒红暗金悬疑风)、Tab元数据(底部4Tab+顶部6Tab)、常量数据(WEEK_DAYS/WEEK_GAMES/ALL_SCRIPTS/ALL_PARTIES/RANK_ENTRIES/MY_RECORDS/SCRIPT_ROLES/SCRIPT_REVIEWS/PARTICLE_ICONS)、四类@Observed数据模型(ScriptItem1 12字段/PartyItem 8字段/RankEntry 6字段/RecordItem 5字段,均constructor初始化)、10个工具函数(heatColor/diffColor/genreTagColor/barHeight/formatHeat/partyProgress/winRate/buildParticles/driftParticles/leftScripts/rightScripts)、Index主组件(@Entry,@State状态变量含particles粒子数组+timerId定时器,aboutToAppear启动setInterval+aboutToDisappear清除)、20+个@Builder构建器(headerBuilder/scriptTopTabs/partyTopBar/rankTopBar/mineTopBar/scriptContent/hotScriptContent/emotionScriptContent/horrorScriptContent/reasoningScriptContent/campScriptContent/joyScriptContent/partyContent/rankContent/mineContent/bottomTabs/buildPartyModal/submitScriptModal/disbandPartyModal/scriptDetailModal)、侦探道具粒子动画系统(16粒子setInterval 130ms向上漂移+sin摆动+底部循环+动态key强制刷新)、6大题材差异化布局(热门banner+双列瀑布/情感横滑+榜单/恐怖暗黑双列/推理柱状图+硬核榜/阵营红蓝对决/欢乐标签云+紧凑列表)、4个模态弹窗(组局/投稿/解散/详情)、底部4Tab+顶部6Tab双层导航,共计约1100行代码。可直接复制到 entry/src/main/ets/pages/Index.ets 运行。

源码较长,建议在 DevEco Studio 中打开后配合预览器查看各 Tab 效果。预览器中可直接点击底部4Tab切换大模块,在剧本Tab点击顶部6Tab切换题材分类,观察侦探道具粒子(🔍🕯️🗝️✨💡)每130ms向上漂移的动画效果,点击剧本卡片弹出详情弹窗,点击"立即组局"/"申请上车"弹出组局弹窗,点击车队"🗑️"弹出解散确认弹窗,点击"我的"→"编辑"弹出投稿新本弹窗,在推理Tab观察周组局场次柱状图周末深酒红高亮效果。


开发环境搭建

安装 DevEco Studio

DevEco Studio 是 HarmonyOS 应用开发的官方 IDE。下载安装包后双击运行,进入安装欢迎界面,点击「下一步」继续。

DevEco Studio 安装欢迎界面

选择安装路径

选择目标安装目录,建议安装到非系统盘,确认所需空间后点击「下一步」。

选择安装位置

配置安装选项

设置环境变量,勾选创建桌面快捷方式、将 bin 文件夹添加到 PATH、添加上下文菜单,但是需要重启一下电脑使环境变量生效。

安装选项配置

选择项目模板

新建一个空白模板,在模板市场中选择 Empty Ability。

选择 Empty Ability 模板

配置项目信息

设置 API 为 24 的模板项目,项目名称 kfschool,Compatible SDK 6.1.1(24),设备类型勾选 Phone,点击完成后初始化项目,自动下载相关依赖。

配置项目信息


总结

综上所述,这款"QQ剧本杀·推理馆"应用是HarmonyOS 6.1.1下基于HarmonyOS API 24的ArkTS声明式范式的一次完整而精彩的实践。从架构层面看,它采用了"单组件多@Builder构建器+状态集中管理"的架构设计——Index入口组件作为根容器管理全部状态,将所有UI构建逻辑封装为20+个@Builder构建器,入口组件只负责状态管理和构建器调度,通过@Builder实现了逻辑的模块化拆分。与多组件架构不同,这种单组件多构建器的架构在中大型应用中既能保持逻辑的模块化,又能避免多组件间状态传递的复杂性,是ArkUI应用的另一种标准架构实践。应用采用"底部4Tab+顶部6Tab"的双层导航架构——底部Tab切换大模块(剧本/车队/榜单/我的),顶部Tab在剧本模块内细分题材分类(热门/情感/恐怖/推理/阵营/欢乐),既保证了模块间的快速切换,又实现了模块内的精细分类,是内容型应用的标准导航架构。

在数据建模上,应用定义了四类@Observed数据模型——ScriptItem1剧本实体(12字段15个剧本,涵盖ID/标题/作者/封面/题材/人数/时长/难度/评分/热度/简介/标签完整信息)、PartyItem车队实体(8字段8个车队)、RankEntry榜单实体(6字段10个玩家)、RecordItem战绩实体(5字段6条战绩)。每个类都通过constructor初始化,@Observed装饰整个类使得属性变更可被框架自动监听。剧本数据通过buildScripts()函数集中构建,再通过leftScripts()/rightScripts()拆分为双列,这种"函数构建+常量拆分"的模式比直接在组件内定义数组更灵活。15个剧本覆盖情感/推理/恐怖/阵营/欢乐五大题材,难度从入门到地狱四级,时长从2.5小时到5小时,人数从5人到8人,评分从8.4到9.6,热度从4760到9820,数据丰富度极高,完整呈现了剧本杀行业的题材多样性。

在工具函数层面,应用定义了10个工具函数——heatColor/diffColor/genreTagColor三个颜色映射函数实现了"数据即颜色"的动态着色(热度三级/难度四级/题材五种),barHeight/formatHeat/partyProgress/winRate四个数值格式函数实现了"数据即尺寸/格式"的动态可视化,buildParticles/driftParticles两个粒子函数实现了自定义动画系统,leftScripts/rightScripts两个拆分函数实现了双列布局的数据准备。颜色映射函数让题材标签、难度标签、热度文字的颜色随数据动态变化,数值函数让柱状图柱高、车队进度条、胜率百分比随数据动态计算,粒子函数让16个侦探道具粒子以独立的sin摆动轨迹向上漂浮,10个工具函数共同支撑了应用的数据可视化和动画系统。partyProgress和winRate两个函数都有除零保护,是防御性编程的体现。

在动画设计上,应用独创了侦探道具粒子动画系统——这是本应用最具技术深度和创意的部分。16个粒子(图标为🔍放大镜/🕯️蜡烛/🗝️钥匙/✨闪光/💡灯泡五种侦探道具循环),通过setInterval每130毫秒调用driftParticles()更新粒子位置(y向上减4,x用sin(p.id+p.y/50)*3左右摆动,y<60时回到底部640形成无限循环),粒子用ForEach渲染在Stack中position绝对定位,key用p.id.toString()+‘_’+p.y.toFixed(0)(包含动态y坐标保证每帧强制重新渲染)。aboutToAppear启动setInterval保存timerId,aboutToDisappear清除定时器防止内存泄漏。这种"setInterval手动驱动+@State响应式+ForEach重新渲染+position绝对定位+动态key强制刷新"的方案,实现了ArkUI中animation属性无法完成的自定义粒子动画。质数步长取模(41/67)保证粒子初始位置均匀分布,sin函数保证粒子摆动轨迹自然不机械,底部循环保证粒子流无限延续,动态key保证每帧强制刷新。16个侦探道具粒子在屏幕上缓缓向上漂浮,与剧本杀"寻找真相"的主题高度契合,营造出悬疑神秘的氛围。这是ArkUI中实现复杂自定义动画的标准且可靠的方案,对鸿蒙生态下的动画开发具有很高的参考价值。

在交互设计上,应用通过4个模态弹窗实现了增删改查交互闭环——buildPartyModal组局(新增,酒红渐变头,选择剧本/局类型/开始时间/人数加减器4-9人)、submitScriptModal投稿新本(编辑,暗金#F5EDD8头,剧本名称TextInput/类型/难度/时长)、disbandPartyModal解散车队(删除,width78%警告小卡,danger朱红底确认,不可撤销提示双重防护)、scriptDetailModal剧本详情(查询,酒红渐变头,大卡信息+组局/收藏按钮+5角色列表+3玩家评价,内容最丰富,详情→组局流畅跳转)。四个弹窗各自独立@Builder,通过show*布尔状态条件渲染,外层统一用Column+backgroundColor '99000000’实现遮罩+居中,点击遮罩关闭。组局弹窗的人数加减器(−/+按钮+范围检查4-9)、投稿弹窗的TextInput受控输入(text绑定@State+onChange回写)、解散弹窗的danger红底确认+不可撤销提示、详情弹窗的角色列表+玩家评价,每个弹窗都有针对其业务场景的精细交互设计。四个弹窗覆盖了组局社交平台的核心交互——组局(新增车队)、投稿(编辑剧本)、解散(删除车队)、详情(查看剧本),形成了完整的增删改查交互闭环。

在视觉设计上,"酒红+暗金·悬疑剧本风"的配色体系以interface类型约束、const常量承载,wine #6D1F36酒红定主(血色悬疑戏剧张力)、wineDeep #4E1425深酒红定强调、wineLight #96455C浅酒红定柔和、gold #C9A227暗金定点缀(复古侦探金属质感)、goldLight #E5C766浅金定文字、bg #F8F4EC米白偏暖定底(旧纸张泛黄剧本质感),三级酒红+两级金+米白底七色组合形成了"以酒红定悬疑、以暗金定复古、以米白定旧纸"的剧本杀语义着色体系。textPrimary #38272E深褐红(非纯黑带酒红调)是文字色的特色——用带酒红调的深褐红代替纯黑,更符合剧本杀"泛黄旧纸+血色悬疑"的视觉氛围。恐怖分类整体切换暗黑主题(#241B2E深紫黑背景+#E8E0F0浅紫白文字),通过颜色的整体反转营造恐怖氛围,这种"按题材切换主题色"的设计比所有分类用同一主题更有沉浸感。6大题材差异化布局(热门banner大卡+双列瀑布流/情感横滑圆角卡+竖排榜单/恐怖暗黑双列格子/推理周组局场次柱状图+硬核榜/阵营红蓝对决卡+阵营本列表/欢乐标签云+紧凑列表),每个题材都有完全不同的布局风格和视觉氛围,最大程度还原了不同题材剧本的沉浸感。推理Tab的周组局场次柱状图(barHeight v*1.1映射柱高+周末wine深酒红高亮+工作日wineLight浅酒红)是数据可视化的经典范式,车队Tab的layoutWeight比例进度条(用权重比例实现进度条比固定宽度更灵活)是布局技巧的巧妙应用,榜单Tab的前三名领奖台布局(冠军更高更宽带金边框+二三名分列两侧)是视觉层级的精细设计。总体而言,该应用在有限的代码篇幅内完整呈现了HarmonyOS ArkTS API 24声明式UI范式的核心能力——四类@Observed数据模型、10个工具函数数据可视化、侦探道具粒子动画系统(setInterval驱动自定义动画)、双层导航架构、6大题材差异化布局、4个模态弹窗增删改查闭环、aboutToAppear/aboutToDisappear生命周期管理定时器,是一份兼具技术深度与文化意蕴的优秀实践范例,对鸿蒙生态下的声明式应用开发和自定义动画实现具有很高的参考价值与学习意义。天黑请闭眼,真相浮现时——愿这款QQ剧本杀推理馆应用,能让更多人在代码中感受到剧本杀一场剧本三五好友几小时穿越时空寻找真相的悬疑魅力与社交乐趣。

QQ剧本杀推理馆应用整体运行效果 - 酒红暗金悬疑风主题与底部4Tab顶部6Tab双层导航及侦探道具粒子动画


标签: HarmonyOS · ArkTS · API 24 · 剧本杀 · 酒红暗金悬疑风 · 粒子动画 · setInterval · 双层导航 · 组局社交 · 移动端开发

Logo

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

更多推荐