HarmonyOS 6 ArkUI - Scroll 滚动容器完整使用指南,横向瀑布流、无限横向滑动卡片实现技巧,讲解滚动偏移监听与性能优化要点
岂曰无衣?与子同袍。汉服,是中华民族传承数千年的传统服饰,从黄帝"垂衣裳而天下治"到周代深衣制度,从唐代齐胸襦裙的飘逸出尘到明代马面裙的端庄大气,汉服承载着华夏衣冠的文化记忆与审美追求。近年来,随着国潮文化的兴起和传统文化的复兴,汉服从小众爱好走向大众视野,越来越多的年轻人穿上汉服走上街头、走进景区、参加雅集,"同袍"成为汉服爱好者之间的亲切称呼。本文完整记录一个基于鸿蒙 HarmonyOS API 24 与 ArkTS 声明式框架构建的「QQ汉服·同袍社」汉服文化社区平台,从朱砂红+黛青古风宣纸的配色体系,到底部4Tab(首页/衣橱/集市/我的)+顶部6Tab(推荐/朝代/发型/配饰/活动/形制)的双层导航架构,到花瓣灯笼粒子动画系统(🌸❀🏮✨🌺十六粒子setInterval 130ms向上漂移)与朝代人气柱状图,到4个模态弹窗(发布穿搭新增/出租汉服编辑/移出衣橱删除/穿搭详情查询),到HanfuItem/HairTutItem/AccessoryItem/EventItem/ClosetItem/MarketItem六类@Observed数据模型与9个工具函数,附完整源码与运行效果截图。
一、引言:岂曰无衣,与子同袍
汉服,全称"汉民族传统服饰",是从黄帝即位到明末清初的四千多年中,以华夏礼仪文化为中心,通过自然演化而形成的具有独特汉民族风貌性格的传统服饰体系。汉服不是指"汉朝的衣服",而是指汉民族的传统服饰——它包括了从先秦到明末的各个朝代的服饰形制,从周代的深衣到汉代的曲裾直裾,从魏晋的大袖衫到唐代的齐胸襦裙圆领袍,从宋代的褙子旋裙到明代的马面裙袄裙,每个朝代都有其独特的服饰审美和形制特征。汉服的核心特征是"交领右衽、褒衣广袖、系带隐扣"——交领是衣襟相交于胸前,右衽是衣襟向右掩(左襟压右襟),褒衣广袖是宽衣大袖飘逸出尘,系带隐扣是用系带固定而非纽扣,这些特征共同构成了汉服区别于其他民族服饰的独特美学。
"岂曰无衣?与子同袍"出自《诗经·秦风·无衣》,原诗是秦国战士之间的战歌——“谁说没有战衣?与君同穿战袍”,表达了战友之间同生共死的深厚情谊。汉服复兴运动兴起后,这句话被汉服爱好者借用为"同袍"之间的口号——“谁说没有汉服?与君同穿汉服”,表达了汉服爱好者之间志同道合、互相扶持的情谊。“同袍"由此成为汉服爱好者之间的亲切称呼,汉服圈也被称为"同袍圈”。从2003年王乐天身穿汉服走上郑州街头被媒体报道,到2010年后汉服社团在高校遍地开花,到2018年国潮文化爆发汉服走向大众,到如今汉服成为年轻人日常穿搭的选择之一,汉服复兴运动已经走过了二十多年的历程。据统计,目前全国汉服爱好者已超过600万人,汉服市场规模超过百亿元,汉服已经从小众爱好发展成为一个庞大的文化产业。
然而,随着汉服爱好者群体的扩大,同袍们面临着越来越多的痛点——汉服形制复杂难辨、正版山寨难分、穿搭搭配困难、发型教程难找、配饰购买不便、线下活动信息分散、闲置汉服出租转卖困难、同袍社交圈子封闭。一个优秀的汉服文化社区平台,需要覆盖从"穿搭推荐"到"形制百科"再到"衣橱管理"“集市交易”"同袍社交"的完整生态闭环,还要在朝代分类、发型教程、配饰铺子、活动日历等维度做到精准而高效。它需要以首页穿搭推荐为核心入口,横向扩展出衣橱/集市/我的三大功能模块,首页模块再纵向细分为推荐/朝代/发型/配饰/活动/形制六大分类,每个分类采用完全不同的布局风格,形成一个完整闭环的汉服文化社区生态。
正是在这样的背景下,QQ汉服·同袍社类汉服文化社区平台应运而生,并迅速成为同袍们的社交入口。一个优秀的汉服应用,需要以首页穿搭推荐为核心入口,横向扩展出衣橱/集市/我的三大功能模块,首页模块再纵向细分为6大分类,每个分类采用完全不同的布局风格——推荐=双列瀑布穿搭卡片、朝代=横滑朝代大卡+人气柱状图+分朝代单品列表、发型=教程步骤时间轴、配饰=三列格子铺子、活动=日历风格+全年活动列表、形制=百科横条列表,形成了一个完整闭环的汉服文化社区生态。它不仅是一个功能完备的演示项目,更是一份典型的 ArkUI 工程实践范本,涵盖了6类@Observed数据模型+3类接口模型、9个工具函数、花瓣灯笼粒子动画系统(setInterval驱动16粒子向上漂移)、双层导航架构(底部4Tab+顶部6Tab)、4个模态弹窗(发布/出租/移出/详情)、aboutToAppear/aboutToDisappear生命周期管理定时器等关键技术点。

二、应用整体架构概览
整个应用采用了"单组件多@Builder构建器+状态集中管理"的架构设计。入口组件 Index 作为整个应用的根容器,负责管理当前底部Tab索引、顶部Tab索引、四个弹窗的显隐状态、选中汉服、粒子数组、发布穿搭表单状态、出租汉服表单状态等全部状态。与多组件架构不同,本应用将所有UI构建逻辑都封装为@Builder构建器(headerBuilder/homeTopTabs/closetTopBar/marketTopBar/mineTopBar/homeContent/recommendContent/dynastyContent/hairstyleContent/accessoryContent/eventContent/formContent/closetContent/marketContent/mineContent/bottomTabs/postOutfitModal/rentOutModal/removeClosetModal/outfitDetailModal),入口组件只负责状态管理和构建器调度,通过@Builder实现了逻辑的模块化拆分,是中大型 ArkUI 应用的标准架构实践。
应用采用双层导航架构——底部4Tab + 顶部6Tab。底部Tab(BOTTOM_TABS):首页🏠/衣橱👘/集市🛍️/我的👤,是应用的一级导航,决定渲染哪个大模块。顶部Tab(TOP_TABS):推荐✨/朝代🏛️/发型💇/配饰📿/活动🏮/形制📖,仅在底部Tab为"首页"时显示,是首页模块的二级分类导航,决定渲染哪个分类的内容。这种"底部一级+顶部二级"的双层导航是内容型应用的标准架构——底部Tab切换大模块,顶部Tab在当前模块内细分内容分类,既保证了模块间的快速切换,又实现了模块内的精细分类。顶部6Tab采用横向Scroll可滑动布局(6个Tab超出屏幕宽度时可左右滑动),激活态标签zhuSha朱砂红加粗+底部18x3描金miaoJin指示条,非激活态textSecondary灰字+透明Divider,是分类导航的标准视觉范式。
在数据层面,应用定义了六类@Observed数据模型 + 三类接口模型——HanfuItem(汉服实体,9字段,15件)、HairTutItem(发型教程实体,5字段,6个教程)、AccessoryItem(配饰实体,5字段,12个配饰)、EventItem(活动实体,6字段,8个活动)、ClosetItem(衣橱实体,5字段,6件)、MarketItem(集市实体,6字段,8件),以及FormIntroItem(形制介绍接口,4字段,8个形制)、CommentItem(评论接口,3字段,3条评论)、SingleItem(单品接口,2字段,4件单品)。每个@Observed类都通过constructor完成初始化(非对象字面量),@Observed装饰整个类使得属性变更可被框架自动监听。汉服数据直接定义为ALL_HANFUS常量数组,再通过leftHanfus()/rightHanfus()两个工具函数拆分为左右两列(偶数索引左列、奇数索引右列)用于双列布局。发型/配饰/活动/衣橱/集市数据直接以const常量数组定义。形制/评论/单品是接口类型的常量数组,不需要@Observed因为是静态配置数据。这种"6类@Observed动态数据+3类接口静态配置"的混合数据模式,既保证了动态数据的响应式更新,又避免了静态配置数据被不必要地监听,是目前所有应用中数据模型最丰富的。
在工具函数层面,应用定义了9个工具函数——dynastyColor(朝代映射颜色:唐zhuSha朱砂/宋daiQing黛青/明miaoJin描金/魏晋#6B5B95紫/汉#4A6FA5蓝)、tagBg(配饰标签映射背景:热卖#F7E3DF浅红/新品#E3EFEA浅绿/古典#F0E8DA浅黄/平价cardBgDark灰)、formatLikes(点赞数格式化:>=1000转k保留1位小数)、barHeight(人气映射柱高:v*1.3+‘vp’)、marketColor(集市模式映射颜色:日租daiQing黛青/转卖zhuSha朱砂)、leftHanfus/rightHanfus(汉服拆列:偶数左列/奇数右列)、buildParticles(构建16个粒子初始位置)、driftParticles(粒子漂移:y-3,x用sin摆动,y<60回到底部640)。这9个工具函数将数据到样式/位置/格式/判断的映射逻辑从UI层抽离,实现了数据与表现的解耦,是数据可视化和动画系统的核心支撑。dynastyColor函数将5个朝代映射为5种不同颜色(唐朱砂/宋黛青/明描金/魏晋紫/汉蓝),实现了"朝代即颜色"的动态着色,是本应用最具特色的工具函数——每个朝代都有其专属的代表色,用户能通过颜色快速识别朝代。tagBg函数将配饰标签(热卖/新品/古典/平价)映射为不同背景色,marketColor函数将集市模式(日租/转卖)映射为不同颜色,formatLikes函数实现点赞数的千进制格式化,barHeight函数实现"人气即柱高"的可视化,leftHanfus/rightHanfus实现双列布局的数据准备,buildParticles/driftParticles实现自定义动画系统。
在动画层面,应用独创了花瓣灯笼粒子动画系统——16个粒子(图标为🌸樱花/❀花朵/🏮灯笼/✨闪光/🌺扶桑五种汉服古风主题道具循环),每个粒子有id/x/y/size/opacity/icon六个属性,通过setInterval每130毫秒调用driftParticles()更新粒子位置(y向上减3,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清除定时器防止内存泄漏。与狼人杀应用的粒子系统相比,本应用的粒子图标更贴合汉服古风主题(🌸❀🏮✨🌺 vs 🌙⭐✦🐺✧),漂移速度相同(y-3),营造出"花瓣飘落、灯笼摇曳"的古风氛围。这种"setInterval手动驱动+@State响应式+ForEach重新渲染+position绝对定位+动态key强制刷新"的方案,实现了ArkUI中animation属性无法完成的自定义粒子动画,是本应用最具技术深度的部分。
在交互层面,应用通过4个模态弹窗实现了增删改查交互闭环:postOutfitModal发布穿搭(新增,朱砂渐变头zhuSha→#8A2B22,穿搭描述TextInput/朝代制式Flex选择/穿着场合Row三选一)、rentOutModal出租汉服(编辑,描金渐变头miaoJin→#A87E4F,日租金TextInput/押金档位Row/最长可租加减器/清洗说明Flex)、removeClosetModal移出衣橱(删除,警告小卡,danger红底确认,购买记录保留提示)、outfitDetailModal穿搭详情(查询,朱砂渐变头,cover+朝代形制+点赞+参考价/intro介绍/单品清单/同袍评论/喜欢+购买按钮)。四个弹窗各自独立@Builder,通过show*布尔状态条件渲染,弹窗外层统一用Column width100% height100% justifyContent Center + backgroundColor ‘99000000’(60%透明黑)实现遮罩+居中,点击遮罩onClick关闭弹窗。四个弹窗覆盖了汉服文化社区的核心交互——发布穿搭(新增)、出租汉服(编辑)、移出衣橱(删除)、穿搭详情(查询),形成了完整的增删改查交互闭环。出租弹窗的日租金TextInput用type(InputType.Number)限制数字输入,押金档位三选一,最长可租加减器1-15天,清洗说明四个标签,是表单交互最丰富的弹窗。

下面逐页展示应用的四大功能模块运行效果。
三、首页Tab:6大分类差异化布局
首页Tab是应用的核心模块,采用"顶部6Tab分类+差异化内容布局"的设计。6个分类每个都有完全不同的布局风格,最大程度还原了汉服文化社区的不同内容维度。
3.1 推荐:双列瀑布穿搭卡片
推荐分类采用"双列瀑布穿搭卡片"的布局,是首页的默认分类。
标题行Row width92% margin top14:"✨ 今日精选穿搭"fontSize15加粗textPrimary layoutWeight1 + "换一批"fontSize11 zhuSha朱砂红字。
双列瀑布Row width100% alignItems Top,通过leftHanfus()/rightHanfus()将15件汉服拆分为左右两列(偶数索引左列8件、奇数索引右列7件),每列Column layoutWeight1,ForEach遍历对应汉服数组渲染卡片。每张卡片Column width100% borderRadius12 cardBg白底border margin:
- 上半部分图片区Column width100% padding 14x0 borderRadius top12 backgroundColor cardBgDark灰底alignItems Center:汉服cover Emoji fontSize42 + 朝代标签Text(h.dynasty) fontSize10白字padding 8x2圆角4 backgroundColor dynastyColor(h.dynasty)(唐朱砂/宋黛青/明描金/魏晋紫/汉蓝)margin top6
- 下半部分信息区Column width100% alignItems Start padding10:汉服名h.name fontSize13加粗textPrimary + “@”+h.author+" · "+h.form fontSize10 textSecondary margin top3 + Row margin top6(点赞❤️ formatLikes(h.likes) fontSize10 zhuSha朱砂红 + 价格¥h.price fontSize10 daiQing黛青margin left10)
- 点击卡片selectedHanfu=h,showDetailModal=true弹出穿搭详情
15 件汉服:齐胸襦裙·牡丹亭(唐·齐胸襦裙·🌸·¥368·2412赞·月出·重工刺绣牡丹)、马面裙·云锦纹(明·马面裙·🐉·¥499·3105赞·青梧·云锦提花织金)、袄裙·缠枝莲(明·袄裙·🪷·¥459·1804赞·阿沅·缠枝莲暗纹竖领)、褙子·月白(宋·褙子·🌙·¥329·2230赞·临安·月白色素雅褙子)、齐腰襦裙·烟霞(汉·齐腰襦裙·🌫️·¥389·1521赞·子衿·渐变烟霞色裙摆)、圆领袍·墨麟(唐·圆领袍·🐎·¥559·982赞·折柳·墨色圆领缺胯袍)、大袖衫·惊鸿(魏晋·大袖衫·🕊️·¥629·2738赞·白鹭·广袖翩然如惊鸿照影)、旋裙·青绿(宋·旋裙·🏔️·¥299·1234赞·疏影·青绿山水配色旋裙)、直裾·玄端(汉·直裾·🖤·¥479·897赞·守拙·玄色直裾深衣)、比甲·海棠(明·比甲·🌺·¥269·1146赞·棠梨·海棠红无袖比甲)、诃子裙·霓裳(唐·诃子裙·🌈·¥529·2050赞·扶光·霓裳羽衣配色)、交领襦裙·竹影(魏晋·交领襦裙·🎋·¥349·1328赞·山鬼·竹影墨绘交领)、百迭裙·远山(宋·百迭裙·⛰️·¥319·1642赞·砚青·远山黛色百迭裙)、短袄·瑞雪(明·短袄·❄️·¥399·943赞·雪衣·瑞雪白短袄立领)、曲裾·青霜(汉·曲裾·💎·¥439·1120赞·未央·青霜色曲裾绕襟)。汉服名富有趣味(牡丹亭/云锦纹/缠枝莲/月白/烟霞/墨麟/惊鸿/青绿/玄端/海棠/霓裳/竹影/远山/瑞雪/青霜),作者名也各有古风特色(月出/青梧/阿沅/临安/子衿/折柳/白鹭/疏影/守拙/棠梨/扶光/山鬼/砚青/雪衣/未央),覆盖唐/宋/明/魏晋/汉五个朝代和齐胸襦裙/马面裙/袄裙/褙子/齐腰襦裙/圆领袍/大袖衫/旋裙/直裾/比甲/诃子裙/交领襦裙/百迭裙/短袄/曲裾15种形制,数据丰富度极高。
推荐分类的核心特色是双列瀑布+朝代颜色标签——通过leftHanfus()/rightHanfus()奇偶拆列实现双列瀑布布局,每张卡片的图片区用cardBgDark灰底模拟汉服展示区,朝代标签用dynastyColor函数映射为5种不同颜色(唐朱砂/宋黛青/明描金/魏晋紫/汉蓝),使用户能通过颜色快速识别汉服所属朝代。卡片信息包含汉服名/作者/形制/点赞/价格,是穿搭推荐的标准信息架构。formatLikes函数将>=1000的点赞数转为k格式(如2412→2.4k),避免大数字占用过多空间。
3.2 朝代:横滑大卡 + 人气柱状图 + 分朝代列表
朝代分类采用"横滑朝代大卡+朝代人气柱状图+分朝代看单品列表"的三层布局,是数据可视化最丰富的分类。

第一部分是横滑朝代大卡Scroll scrollable Horizontal scrollBar Off,Row内ForEach遍历DYNASTY_NAMES(唐/宋/明/魏晋/汉)渲染大卡Column width96 height110 justifyContent Center borderRadius14 linearGradient angle135从dynastyColor(d)到#8A2B22深红 margin left(idx===0?16:10)top12:朝代名Text(d) fontSize24加粗白字 + "制式"fontSize10 #F2DFC5浅金字margin top4。每个朝代大卡用dynastyColor函数映射的专属颜色作为渐变起始色(唐朱砂/宋黛青/明描金/魏晋紫/汉蓝),结束色统一#8A2B22深红,形成"朝代专属色+深红收尾"的统一渐变风格。点击大卡currentTopTab=1(留在朝代分类)。
第二部分是朝代人气柱状图Column width92% borderRadius14 cardBg白底border padding14 margin top14:
- 标题:"📊 朝代人气榜"fontSize14加粗daiQing黛青字alignSelf Start
- 副标题:"按同袍收藏量统计,唐制持续霸榜"fontSize10 textSecondary margin top4 alignSelf Start
- 柱状图Row width100% margin top12:ForEach遍历DYNASTY_NAMES渲染柱子Column layoutWeight1 alignItems Center:人气数值Text(DYNASTY_HEAT[idx].toString()) fontSize10 dynastyColor(d) margin bottom3 + 柱体Column width22 height barHeight(DYNASTY_HEAT[idx])(v*1.3+‘vp’,如唐92→119.6vp)borderRadius top4 backgroundColor dynastyColor(d) + 朝代标签Text(d) fontSize10 textSecondary margin top4
DYNASTY_NAMES = [‘唐’,‘宋’,‘明’,‘魏晋’,‘汉’],DYNASTY_HEAT = [92,78,85,60,70]。唐92最高(柱体最高,朱砂红),魏晋60最低(柱体最矮,紫色),每个朝代用dynastyColor专属颜色着色,通过颜色和柱高双重呈现朝代人气。barHeight函数将人气数值乘以1.3后加’vp’单位返回字符串,直接作为Column的height属性值,实现了"数值即柱高"的数据可视化。与狼人杀应用的柱状图(barHeight v*2.2,7天数据)相比,本应用的倍率更小(1.3)因为人气数值范围更大(60-92),且只有5个朝代柱子更宽(width22 vs 18)。
第三部分是分朝代看单品列表Column width92% borderRadius14 cardBg白底border padding14 margin top14:
- 标题:"🏛️ 分朝代看单品"fontSize14加粗daiQing黛青字
- ForEach遍历ALL_HANFUS渲染Row width100% padding 10x0:朝代标签Text(h.dynasty) fontSize11加粗白字width34 height24 textAlign Center borderRadius6 backgroundColor dynastyColor(h.dynasty) + 信息Column layoutWeight1 margin left10 alignItems Start(汉服名h.name fontSize13加粗textPrimary + h.form+" · "+h.intro fontSize10 textSecondary maxLines1 Ellipsis margin top2)+ 价格¥h.price fontSize12加粗zhuSha朱砂红
- 每项后加Divider border色,点击selectedHanfu=h,showDetailModal=true
15件汉服按朝代分布:唐4件(齐胸襦裙·牡丹亭/圆领袍·墨麟/诃子裙·霓裳/交领襦裙·竹影?不对,交领是魏晋)、宋3件(褙子·月白/旋裙·青绿/百迭裙·远山)、明4件(马面裙·云锦纹/袄裙·缠枝莲/比甲·海棠/短袄·瑞雪)、魏晋2件(大袖衫·惊鸿/交领襦裙·竹影)、汉2件(齐腰襦裙·烟霞/直裾·玄端/曲裾·青霜——汉3件)。每个朝代标签用dynastyColor专属颜色,用户能通过颜色快速识别汉服朝代。
朝代分类的三层布局(横滑大卡→人气柱状图→分朝代单品列表)层层递进——从朝代概览到人气数据到具体单品,完整呈现了汉服按朝代分类的内容维度。dynastyColor函数是整个分类的核心——5个朝代映射5种专属颜色,横滑大卡/柱状图/单品列表都用这个函数着色,形成了"朝代即颜色"的统一视觉语言。
3.3 发型:教程步骤时间轴
发型分类采用"古典发型教程时间轴"的布局。
标题:"💇 古典发型教程"fontSize15加粗textPrimary margin left16 top14 + "跟着视频学,手残党也能盘出古风髻"fontSize10 textSecondary margin left16 top4。
教程时间轴ForEach遍历HAIR_TUTS(6个发型教程)渲染Column width92% margin top6:
- Row width100% alignItems Top:
- 序号圆Column alignItems Center:Text((idx+1).toString()) fontSize13加粗白字width30 height30 textAlign Center borderRadius15 backgroundColor daiQing黛青
- 信息Column layoutWeight1 margin left12 alignItems Start:Row(发型名t.name fontSize14加粗textPrimary + 难度t.difficulty fontSize10 miaoJin描金字margin left10)+ 工具t.tool fontSize10 textSecondary margin top3 + 步数时长t.steps+" 步完成 · 视频时长 “+(t.steps*2)+” 分钟"fontSize10 textHint灰margin top2
- "学它"按钮Text fontSize11白字padding 12x6圆角12 backgroundColor zhuSha朱砂红
- 非末项时加连接线Row width100%:Column width2 height18 backgroundColor border色margin left14 + Column layoutWeight1
6 个发型教程:灵蛇髻(★★★·5步·发包·发网·U型夹)、堕马髻(★★·4步·垫发包·插梳)、双环望仙髻(★★★★·6步·双发包·发胶·步摇)、飞仙髻(★★★·5步·高发包·发带)、半翻髻(★★·3步·半发包·发簪)、垂挂髻(★·3步·皮筋·小发夹)。难度从★到★★★★,步数从3到6步,视频时长从6到12分钟(steps*2),覆盖从入门到高阶的发型教程。工具列表详细(发包/发网/U型夹/垫发包/插梳/双发包/发胶/步摇/高发包/发带/半发包/发簪/皮筋/小发夹),是汉服发型教程的标准工具。
发型分类的核心特色是时间轴布局——通过左侧的圆形序号图标(daiQing黛青底白字)+垂直连接线(width2 height18 border色),直观呈现了发型教程的步骤序列。每个教程包含发型名/难度星级/所需工具/步数时长/学它按钮,是发型教程的标准信息架构。难度用miaoJin描金字突出,序号用daiQing黛青底,学它按钮用zhuSha朱砂红,三种主题色(朱砂/黛青/描金)在一个卡片中各司其职。连接线用margin left14对齐序号圆中心(序号圆width30,中心在15,margin left14接近中心),是时间轴布局的对齐技巧。
3.4 配饰:三列格子铺子
配饰分类采用"三列格子铺子"的布局。

标题:"📿 配饰铺子"fontSize15加粗textPrimary margin left16 top14 + "簪钗步摇 · 点翠缠花 · 平价古典都有"fontSize10 textSecondary margin left16 top4。
三列格子Flex({wrap:FlexWrap.Wrap}) width96% margin top6,ForEach遍历ALL_ACCESSORIES(12个配饰)渲染Column width30% borderRadius12 cardBg白底border alignItems Center margin left1.6% top10:
- 配饰icon Emoji fontSize30 margin top12
- 配饰名a.name fontSize12加粗textPrimary margin top6
- 价格¥a.price fontSize11加粗zhuSha朱砂红margin top4
- 标签a.tag fontSize9 daiQing黛青字padding 8x2圆角6 backgroundColor tagBg(a.tag)(热卖#F7E3DF浅红/新品#E3EFEA浅绿/古典#F0E8DA浅黄/平价cardBgDark灰)margin top4 bottom12
- 点击showRentModal=true(弹出出租汉服弹窗——本应用中点击配饰复用了出租弹窗)
12 个配饰:鎏金发簪(🎀·¥68·热卖)、珍珠步摇(💫·¥128·新品)、玉禁步(🪶·¥158·古典)、宫绦(🧵·¥45·热卖)、璎珞项圈(📿·¥98·新品)、云纹发带(☁️·¥29·平价)、油纸伞(☂️·¥88·古典)、绣花团扇(🪭·¥58·热卖)、香囊(🧿·¥35·平价)、流苏耳坠(💧·¥49·新品)、护甲套(💅·¥76·古典)、缠花发簪(🌸·¥89·热卖)。配饰覆盖发饰(鎏金发簪/珍珠步摇/云纹发带/缠花发簪)、颈饰(璎珞项圈/宫绦)、腰饰(玉禁步)、手持(油纸伞/绣花团扇/香囊)、耳饰(流苏耳坠)、护甲(护甲套)六大类,价格从¥29到¥158,标签覆盖热卖/新品/古典/平价四类,数据丰富度高。
配饰分类的核心特色是三列Flex换行+标签背景色——用Flex({wrap:FlexWrap.Wrap})实现自动换行的三列布局,每个卡片width30%+margin left1.6%,三列总宽30%*3+1.6%*3=94.8%接近96%容器宽度,是三列网格的标准宽度计算。tagBg函数将配饰标签(热卖/新品/古典/平价)映射为4种不同背景色(浅红/浅绿/浅黄/灰),标签文字统一daiQing黛青字,通过背景色区分标签类型。价格用zhuSha朱砂红加粗突出,是电商配饰铺子的标准视觉。
3.5 活动:日历风格 + 全年活动列表
活动分类采用"八月活动日历+全年同袍活动列表"的两层布局。
标题部分是日历卡Column width92% borderRadius14 cardBg白底border padding14 margin top14:
- 标题:"🏮 八月活动日历"fontSize14加粗zhuSha朱砂红字alignSelf Start
- 星期表头Row width100%:ForEach遍历[‘日’,‘一’,‘二’,‘三’,‘四’,‘五’,‘六’]渲染Text(d) fontSize10 textSecondary layoutWeight1 textAlign Center padding top8
- 日期Flex({wrap:FlexWrap.Wrap}) width100%:ForEach遍历1-31渲染Column width12.5% padding 8x0 borderRadius8 backgroundColor d=24?zhuSha朱砂红:transparent margin top4 alignItems Center:日期Text(d.toString()) fontSize11 d=24?白字:textPrimary + d===24时显示🏮Emoji fontSize8
- 底部标注:"· 8月24日 中秋拜月雅集 ·"fontSize10 zhuSha朱砂红字margin top8 alignSelf Center
八月日历用Flex换行排列31天,width12.5%(1/8列,比7列稍宽留出间距),8月24日用zhuSha朱砂红高亮+🏮灯笼图标,标注"中秋拜月雅集",是活动日历的标准设计。星期表头从日到六(周日开头),符合中国日历习惯。
第二部分是全年活动列表标题"🏮 全年同袍活动"fontSize15加粗textPrimary margin left16 top14 + ForEach遍历ALL_EVENTS(8个活动)渲染Row width92% padding12 borderRadius12 cardBg白底border margin top8:
- 日期Column alignItems Center width48:日Text(e.date.split(‘-’)[1]) fontSize18加粗zhuSha朱砂红 + 月Text(e.date.split(‘-’)[0]+“月”) fontSize9 textSecondary
- 信息Column layoutWeight1 margin left10 alignItems Start:Row(活动名e.name fontSize13加粗textPrimary + 类型标签e.type fontSize9白字padding 6x1圆角4 backgroundColor daiQing黛青margin left8)+ 城市报名e.city+" · “+e.joined+” 人已报名"fontSize10 textSecondary margin top3
- "报名"按钮Text fontSize11白字padding 12x6圆角12 backgroundColor zhuSha朱砂红
8 个活动:花朝节汉服雅集(杭州·03-12·雅集·386人)、上巳节曲水流觞(武汉·04-03·踏青·298人)、西塘汉服文化周(嘉兴·10-28·展会·5200人)、中华礼乐大会(曲阜·09-16·礼仪·1800人)、长安上元灯会(西安·02-24·灯会·2600人)、洛阳牡丹汉服节(洛阳·04-18·游园·3400人)、国风音乐会(成都·05-20·演出·1500人)、同袍冬至雅集(南京·12-21·雅集·420人)。活动覆盖雅集/踏青/展会/礼仪/灯会/游园/演出七种类型,城市覆盖杭州/武汉/嘉兴/曲阜/西安/洛阳/成都/南京八大城市,时间覆盖全年12个月,报名人数从298到5200,是汉服线下活动的典型代表。西塘汉服文化周5200人是规模最大的活动,中华礼乐大会在曲阜(孔子故里)是最具文化意义的活动。
活动分类的核心特色是日历+列表双视图——上方日历视图直观呈现当月活动(8月24日中秋拜月雅集高亮),下方列表视图呈现全年活动详情(日期/名字/类型/城市/报名人数/报名按钮)。日期用split(‘-’)拆分月日,日用大号zhuSha朱砂红字,月用小号textSecondary灰字,是活动日期的标准展示方式。类型标签用daiQing黛青底白字,与日期的朱砂红形成对比。报名按钮用zhuSha朱砂红底,是活动报名的标准CTA。
3.6 形制:百科横条列表
形制分类采用"形制百科横条列表"的布局。
标题:"📖 形制百科"fontSize15加粗textPrimary margin left16 top14 + "认准正规形制,做有文化的袍子"fontSize10 textSecondary margin left16 top4。
形制列表ForEach遍历FORM_INTROS(8个形制)渲染Row width92% padding14 borderRadius12 backgroundColor idx%2===0?cardBg白:cardBgDark灰 border margin top8:
- 信息Column layoutWeight1 alignItems Start:形制名f.name fontSize14加粗daiQing黛青字 + 描述f.desc fontSize10 textSecondary maxLines2 Ellipsis margin top4
- 朝代特色Column alignItems Center:朝代标签Text(f.dynasty) fontSize13加粗白字padding 10x4圆角8 backgroundColor dynastyColor(f.dynasty) + 特色f.feature fontSize9 miaoJin描金字margin top4
- 点击currentTopTab=1(跳转到朝代分类查看该朝代单品)
8 个形制:齐胸襦裙(唐·裙腰束于胸上的襦裙,配大袖衫飘逸出尘·飘逸)、马面裙(明·前后共有四个裙门两两重合的马面结构·端庄)、袄裙(明·上袄下裙的典型明制穿法,冬暖夏凉·日常)、褙子(宋·直领对襟两侧开衩的长衣,宋人最爱·素雅)、直裾(汉·裾垂直而下不绕襟的深衣制式·庄重)、曲裾(汉·衣襟层层缠绕的深衣,线条流动·古典)、圆领袍(唐·圆领右衽的通裁袍服,男女皆可着·英气)、大袖衫(魏晋·宽袖广身的外衫,仙气飘飘的担当·仙气)。形制覆盖唐/宋/明/魏晋/汉五个朝代,特色用一个字概括(飘逸/端庄/日常/素雅/庄重/古典/英气/仙气),描述详细解释形制特征,是汉服形制百科的标准内容。
形制分类的核心特色是奇偶交替背景+朝代颜色标签——用idx%2===0判断奇偶,偶数行cardBg白底,奇数行cardBgDark灰底,形成斑马纹交替效果,提升长列表的可读性。朝代标签用dynastyColor专属颜色,特色用miaoJin描金字,三种主题色(黛青标题/朝代颜色标签/描金特色)各司其职。点击形制跳转到朝代分类(currentTopTab=1),实现了"形制→朝代单品"的内容跳转,是分类间联动的设计。
首页6大分类差异化布局(推荐双列瀑布/朝代横滑大卡+柱状图+列表/发型时间轴/配饰三列格子/活动日历+列表/形制百科横条),每个分类都有完全不同的布局风格和视觉氛围,最大程度还原了汉服文化社区的不同内容维度。dynastyColor函数贯穿推荐/朝代/形制三个分类,实现了"朝代即颜色"的统一视觉语言,是本应用数据可视化的核心。
四、衣橱Tab:黛青总览卡 + 衣橱列表
衣橱Tab以"个人汉服衣橱管理"为核心,采用"黛青渐变总览卡+衣橱列表"的两层布局。

页面顶部是closetTopBar标题行:"👘 我的衣橱"fontSize16加粗textPrimary margin left16 + 弹性占位 + "6 件 · 已穿 42 次"fontSize11 textSecondary margin right16。
第一部分是衣橱总览卡Row width92% padding14 borderRadius14 linearGradient angle135从daiQing黛青到#143B38深黛青 margin top14:
- 信息Column layoutWeight1 alignItems Start:"👘 衣橱总览"fontSize14加粗白字 + "汉服 6 · 配饰 12 · 总价值 ¥3,248"fontSize10 #F2DFC5浅金字margin top4
- "整理衣橱"按钮Text fontSize11 daiQing黛青字padding 12x7圆角12 backgroundColor white白底(点击showRemoveModal=true弹出移出衣橱确认弹窗)
黛青到深黛青的渐变背景+白色整理按钮,营造出衣橱管理的沉稳氛围。总览数据(汉服6件/配饰12件/总价值¥3,248)直观呈现衣橱规模。
第二部分是"挂在衣橱里的宝贝"标题fontSize15加粗textPrimary margin left16 top14 + 衣橱列表ForEach遍历MY_CLOSET(6件)渲染Row width92% padding12 borderRadius12 cardBg白底border margin top8:
- 朝代标签Text(c.dynasty) fontSize12加粗白字width36 height36 textAlign Center borderRadius10 backgroundColor dynastyColor(c.dynasty)(唐朱砂/宋黛青/明描金/魏晋紫/汉蓝)
- 信息Column layoutWeight1 margin left10 alignItems Start:汉服名c.name fontSize13加粗textPrimary + Row margin top3(成色标签c.condition fontSize9 success绿字padding 6x1圆角4 backgroundColor #E3EFEA浅绿底 + "上次穿着 "+c.lastWorn fontSize10 textSecondary margin left8)
- "📤"移出图标Text fontSize16 padding8(点击showRemoveModal=true弹出移出衣橱确认弹窗)
6 件衣橱汉服:马面裙·云锦纹(明·08-20·九成新)、褙子·月白(宋·08-15·全新带吊牌)、齐胸襦裙·牡丹亭(唐·07-30·轻微勾丝)、圆领袍·墨麟(唐·06-18·九成新)、大袖衫·惊鸿(魏晋·05-02·全新)、直裾·玄端(汉·04-11·八成新)。成色覆盖全新带吊牌/全新/九成新/八成新/轻微勾丝五级,上次穿着从04-11到08-20,数据真实还原个人衣橱状态。
衣橱Tab的核心特色是黛青主题+朝代颜色标签+成色标签——总览卡用daiQing黛青渐变(与首页的朱砂红主题区分,衣橱用黛青营造沉稳管理氛围),每件汉服的朝代标签用dynastyColor专属颜色,成色标签用success绿字+浅绿底(全新/九成新/八成新都用绿色,轻微勾丝也用绿色——本应用中成色标签统一绿色,没有按成色区分颜色),上次穿着日期用textSecondary灰字。📤移出图标点击弹出移出衣橱确认弹窗(removeClosetModal),是衣橱管理的删除操作入口。整理衣橱按钮也弹出移出弹窗,两个入口都指向同一个删除操作。
五、集市Tab:出租转卖列表
集市Tab以"同袍闲置交易"为核心,采用"今日集市列表"的单层布局。

页面顶部是marketTopBar标题行:"🛍️ 同袍集市"fontSize16加粗textPrimary margin left16 + 弹性占位 + "出租我的汉服"按钮Text fontSize11白字padding 10x5圆角12 backgroundColor zhuSha朱砂红margin right16(点击showRentModal=true弹出出租汉服弹窗)。
集市列表标题行Row width92% margin top14:"🛍️ 今日集市"fontSize15加粗textPrimary layoutWeight1 + "共 86 件"fontSize11 textSecondary。
ForEach遍历MARKET_ITEMS(8件)渲染Row width92% padding12 borderRadius12 cardBg白底border margin top8:
- 封面Text(m.cover) fontSize26 width52 height52 textAlign Center borderRadius12 backgroundColor cardBgDark灰底
- 信息Column layoutWeight1 margin left12 alignItems Start:汉服名m.name fontSize13加粗textPrimary + Row margin top3(模式标签m.mode fontSize9白字padding 6x1圆角4 backgroundColor marketColor(m.mode)——日租daiQing黛青/转卖zhuSha朱砂红 + “@”+m.seller fontSize10 textSecondary margin left8)
- 价格操作Column alignItems End:价格Text(m.mode===‘日租’?‘¥’+m.price+‘/天’:‘¥’+m.price) fontSize13加粗zhuSha朱砂红 + "聊一聊"按钮Text fontSize10白字padding 10x4圆角10 backgroundColor daiQing黛青margin top4
- 点击showRentModal=true(弹出出租汉服弹窗——本应用中点击集市 item复用了出租弹窗)
8 件集市商品:诃子裙·霓裳(扶光·¥30/天·日租·🌈)、大袖衫·惊鸿(白鹭·¥45/天·日租·🕊️)、马面裙·柿红(青梧·¥399·转卖·🍊)、圆领袍·墨麟(折柳·¥50/天·日租·🐎)、百迭裙·远山(砚青·¥260·转卖·⛰️)、袄裙·缠枝莲(阿沅·¥38/天·日租·🪷)、齐胸襦裙·雀蓝(月出·¥280·转卖·🐦)、曲裾·青霜(未央·¥42/天·日租·💎)。模式覆盖日租5件/转卖3件,日租价格从¥30到¥50/天,转卖价格从¥260到¥399,卖家都是ALL_HANFUS中的作者名(扶光/白鹭/青梧/折柳/砚青/阿沅/月出/未央),商品名与汉服名对应,数据真实还原同袍闲置交易场景。
集市Tab的核心特色是日租转卖双模式+marketColor颜色区分——marketColor函数将集市模式映射为不同颜色(日租daiQing黛青/转卖zhuSha朱砂红),模式标签用对应颜色底白字,使用户能通过颜色快速识别交易模式。价格根据模式动态显示(日租显示¥X/天,转卖显示¥X),用三元运算符m.mode===‘日租’?‘¥’+m.price+‘/天’:‘¥’+m.price实现。"聊一聊"按钮用daiQing黛青底(与价格的朱砂红形成对比),是集市交易的沟通入口。点击商品或"出租我的汉服"按钮都弹出出租汉服弹窗(rentOutModal),实现了"集市→出租"的操作跳转。
六、我的Tab:同袍主页 + 统计 + 活动足迹
我的Tab以"同袍个人主页"为核心,采用"个人信息卡+四列统计+活动足迹列表"的三层布局。

页面顶部是mineTopBar标题行:"👤 同袍主页"fontSize16加粗textPrimary margin left16 + 弹性占位 + "衣龄 3 年 · 袍子 LV.6"fontSize11 miaoJin描金字margin right16。
第一部分是个人信息卡Row width92% padding16 borderRadius16 cardBg白底border margin top16:
- 头像Text(‘🍂’) fontSize28 width64 height64 textAlign Center borderRadius32 backgroundColor cardBgDark灰底
- 信息Column layoutWeight1 margin left12 alignItems Start:"临安"fontSize18加粗textPrimary + "袍子 LV.6 · 衣龄 3 年 · 常穿宋制"fontSize11 textSecondary margin top4 + "心愿单:大袖衫·惊鸿"fontSize10 miaoJin描金字margin top2
- "发布"按钮Column:Text fontSize11白字padding 12x6圆角12 backgroundColor zhuSha朱砂红(点击showPostModal=true弹出发布穿搭弹窗)
个人信息卡用cardBg白底(与衣橱的黛青渐变、集市的列表区分,我的用简洁白底),头像用🍂落叶Emoji(古风意境),用户名"临安"(南宋都城,宋制爱好者),衣龄3年袍子LV.6常穿宋制,心愿单"大袖衫·惊鸿"(魏晋大袖衫,与常穿宋制形成对比),数据真实还原同袍个人主页。
第二部分是四列统计Row width92% padding 14x0 borderRadius14 cardBg白底border margin top12:
- 穿搭发布Column layoutWeight1 alignItems Center:"48"fontSize18加粗zhuSha朱砂红 + "穿搭发布"fontSize10 textSecondary margin top2
- 获赞Column layoutWeight1 alignItems Center:"1.2k"fontSize18加粗zhuSha朱砂红 + "获赞"fontSize10 textSecondary margin top2
- 活动参与Column layoutWeight1 alignItems Center:"6"fontSize18加粗daiQing黛青 + "活动参与"fontSize10 textSecondary margin top2
- 同袍关注Column layoutWeight1 alignItems Center:"86"fontSize18加粗daiQing黛青 + "同袍关注"fontSize10 textSecondary margin top2
四列统计用两种主题色区分——穿搭发布/获赞用zhuSha朱砂红(内容创作类数据),活动参与/同袍关注用daiQing黛青(社交活动类数据),通过颜色区分数据类型。获赞用1.2k格式(formatLikes格式化),其他用整数。
第三部分是活动足迹卡Column width92% borderRadius14 cardBg白底border padding14 margin top12:
- 标题:"📜 我的活动足迹"fontSize14加粗textPrimary
- ForEach遍历4个活动渲染Row width100% padding 10x0:🏮Emoji fontSize14 + 活动信息Text(a) fontSize11 textPrimary margin left8 layoutWeight1 + "已参加"fontSize9 success绿字
- 每项后加Divider border色
4 个活动足迹:花朝节汉服雅集 · 杭州、西塘汉服文化周 · 嘉兴、上巳节曲水流觞 · 武汉、洛阳牡丹汉服节 · 洛阳。与ALL_EVENTS中的活动对应,"已参加"用success绿字标注,是同袍活动历史的标准展示。
我的Tab的三层布局(个人信息→统计数据→活动足迹)是个人中心的标准信息架构——从个人身份到数据统计到活动历史,层层递进。个人信息卡用白底简洁风格,四列统计用朱砂/黛青双色区分数据类型,活动足迹用success绿字标注已参加,整体视觉简洁清晰。"发布"按钮用zhuSha朱砂红底,是个人主页的内容创作入口。
七、色彩系统:朱砂红+黛青古风宣纸风
QQ汉服·同袍社的整体视觉基调建立在"朱砂红+黛青·古风宣纸"之上,这一配色通过一个interface与一个const常量共同定义。
interface ColorPalette {
zhuSha: string; zhuShaLight: string;
daiQing: string; daiQingLight: string; miaoJin: string;
bg: string; cardBg: string; cardBgDark: string;
textPrimary: string; textSecondary: string; textHint: string;
white: string; border: string; danger: string; success: string;
}
const COLORS: ColorPalette = {
zhuSha: '#B03A2E', zhuShaLight: '#D96A5C',
daiQing: '#1F5F5B', daiQingLight: '#4A8A86', miaoJin: '#D4A373',
bg: '#F7F3EA', cardBg: '#FFFFFF', cardBgDark: '#EFE8DA',
textPrimary: '#3A2E28', textSecondary: '#7D6E62', textHint: '#B8AB9E',
white: '#FFFFFF', border: '#E5DCCC',
danger: '#C0392B', success: '#4C8A6E'
};
ColorPalette接口声明了17个颜色字段,使用interface而非直接使用对象字面量是ArkTS推荐的类型优先实践——编译器在编译期校验COLORS常量是否完整提供了每一个字段。从语义命名可以看出,颜色被划分为主题色(zhuSha/zhuShaLight两级朱砂红/daiQing/daiQingLight两级黛青/miaoJin描金)、背景色(bg/cardBg/cardBgDark三级)、文本色三级(textPrimary/textSecondary/textHint)、边框色(border)、语义色(danger/success)以及白色(white)。
COLORS常量是配色体系的实际载体:
- zhuSha #B03A2E:朱砂红主色,用于头部渐变背景/激活Tab/按钮背景/标签/价格/点赞/发布按钮,是汉服"朱红"的核心色(古代朱砂是珍贵颜料,朱红是汉服最经典的颜色之一)
- zhuShaLight #D96A5C:浅朱砂红,用于渐变/柔和强调,比主色稍亮
- daiQing #1F5F5B:黛青主色,用于衣橱总览/发型序号/活动类型标签/聊一聊按钮/形制标题/统计活动类,是汉服"黛青"的核心色(古代黛是青黑色颜料,黛青是汉服经典配色之一)
- daiQingLight #4A8A86:浅黛青,用于渐变/柔和强调,比主色稍亮
- miaoJin #D4A373:描金主色,用于顶部Tab指示条/难度星级/特色标签/心愿单/衣龄等级,是汉服"描金"的核心色(古代服饰常用金线描边,描金是汉服华丽感的来源)
- bg #F7F3EA:宣纸米白背景,营造古风宣纸的底色质感(比纯白稍黄,模拟宣纸的颜色)
- cardBg #FFFFFF:卡片背景纯白
- cardBgDark #EFE8DA:深卡片背景,用于头像底/标签底/配置标签底/汉服展示区,比bg稍深
- textPrimary #3A2E28:文字主色深棕(非纯黑带棕调),与古风宣纸主题呼应(古代墨色偏棕)
- textSecondary #7D6E62:次要文字灰棕
- textHint #B8AB9E:提示文字浅灰棕
- border #E5DCCC:边框色浅米黄
- danger #C0392B:危险色(比zhuSha稍深红),用于移出衣橱确认按钮
- success #4C8A6E:成功色墨绿,用于成色标签/已参加/在线状态
本应用采用"朱砂红+黛青+描金·古风宣纸"主题设计——zhuSha #B03A2E朱砂红定主(汉服朱红经典色)、zhuShaLight #D96A5C浅朱砂定渐变、daiQing #1F5F5B黛青定辅(汉服黛青经典色)、daiQingLight #4A8A86浅黛青定渐变、miaoJin #D4A373描金定点缀(汉服金线描边)、bg #F7F3EA宣纸米白定底。主色体系采用"两级朱砂+两级黛青+描金+宣纸米白"六色组合——朱砂红定朱红、黛青定青黑、描金定华丽、宣纸米白定底,四色各司其职形成"以朱砂定朱红、以黛青定青黑、以描金定华丽、以宣纸定古韵"的汉服语义着色体系。textPrimary #3A2E28深棕(非纯黑带棕调)是文字色的特色——用带棕调的深棕代替纯黑,更符合古风宣纸"墨色偏棕"的视觉氛围。bg #F7F3EA宣纸米白(比纯白稍黄)是背景色的特色——模拟宣纸的颜色,营造古风宣纸的质感。
本应用另一个色彩处理特色是dynastyColor朝代颜色映射函数——将5个朝代映射为5种专属颜色:唐zhuSha朱砂红(唐代以红为贵,朱砂红是唐制代表色)、宋daiQing黛青(宋代以素雅为美,黛青是宋制代表色)、明miaoJin描金(明代以华丽为尚,描金是明制代表色)、魏晋#6B5B95紫(魏晋以飘逸为风,紫色是魏晋风度代表色)、汉#4A6FA5蓝(汉代以庄重为制,蓝色是汉制代表色)。dynastyColor函数贯穿推荐/朝代/形制/衣橱四个分类,横滑大卡/柱状图/单品列表/朝代标签都用这个函数着色,形成了"朝代即颜色"的统一视觉语言,是本应用最具特色的色彩设计。每个朝代的代表色都有其文化依据——唐红/宋青/明金/魏晋紫/汉蓝,用户能通过颜色快速识别汉服所属朝代,同时感受不同朝代的审美风格。tagBg函数将配饰标签映射为4种背景色(热卖浅红/新品浅绿/古典浅黄/平价灰),marketColor函数将集市模式映射为2种颜色(日租黛青/转卖朱砂),三个颜色映射函数(dynastyColor/tagBg/marketColor)共同实现了多维度动态着色。
统一的色彩系统是整个应用的视觉基石,朱砂红黛青描金古风宣纸主题配合多维度动态着色,营造出既朱红华丽又黛青素雅、既描金点缀又宣纸古韵的汉服古风氛围。
八、六类@Observed数据模型与9个工具函数
8.1 六类@Observed数据模型 + 三类接口模型
应用定义了六类@Observed数据模型和三类接口模型,每类对应一个业务实体,是目前所有应用中数据模型最丰富的。
- HanfuItem:汉服实体(id/name/dynasty/form/cover/price/likes/author/intro,9字段),是字段最多的实体,涵盖汉服的完整信息——ID/汉服名/朝代/形制/封面Emoji/价格/点赞数/作者/介绍。15件汉服直接定义为ALL_HANFUS常量数组。
- HairTutItem:发型教程实体(id/name/difficulty/steps/tool,5字段),涵盖发型教程的完整信息——ID/发型名/难度星级/步数/所需工具。6个教程直接定义为HAIR_TUTS常量数组。
- AccessoryItem:配饰实体(id/name/icon/price/tag,5字段),涵盖配饰的完整信息——ID/配饰名/图标Emoji/价格/标签。12个配饰直接定义为ALL_ACCESSORIES常量数组。
- EventItem:活动实体(id/name/city/date/type/joined,6字段),涵盖活动的完整信息——ID/活动名/城市/日期/类型/报名人数。8个活动直接定义为ALL_EVENTS常量数组。
- ClosetItem:衣橱实体(id/name/dynasty/lastWorn/condition,5字段),涵盖衣橱汉服的完整信息——ID/汉服名/朝代/上次穿着日期/成色。6件直接定义为MY_CLOSET常量数组。
- MarketItem:集市实体(id/name/seller/price/mode/cover,6字段),涵盖集市商品的完整信息——ID/商品名/卖家/价格/模式/封面Emoji。8件直接定义为MARKET_ITEMS常量数组。
- FormIntroItem:形制介绍接口(name/dynasty/desc/feature,4字段),是接口类型而非@Observed类,因为形制介绍是静态数据不需要响应式更新。8个形制直接定义为FORM_INTROS常量数组。
- CommentItem:评论接口(name/avatar/content,3字段),接口类型静态数据。3条评论直接定义为OUTFIT_COMMENTS常量数组。
- SingleItem:单品接口(name/detail,2字段),接口类型静态数据。4件单品直接定义为OUTFIT_PIECES常量数组。
每个@Observed类都给了默认值并通过constructor初始化,@Observed装饰整个类使得属性变更可被框架自动监听。与之前狼人杀应用的4类@Observed(RoomItem/FriendItem/MomentItem/ReplayStep)相比,本应用的数据模型更丰富(6类@Observed+3类接口=9类数据模型),覆盖汉服/发型/配饰/活动/衣橱/集市/形制/评论/单品九个业务维度,是汉服文化社区完整生态的数据支撑。HanfuItem的9个字段是所有实体中最多的——涵盖从基本信息(名字/朝代/形制)到商业信息(价格/点赞/作者)再到内容信息(介绍),是汉服文化社区的核心实体。ClosetItem的condition成色字段和lastWorn上次穿着字段是衣橱管理的特色数据,MarketItem的mode模式字段(日租/转卖)和seller卖家字段是集市交易的特色数据,HairTutItem的difficulty难度/steps步数/tool工具字段是发型教程的特色数据,EventItem的city城市/date日期/type类型/joined报名人数字段是线下活动的特色数据,每个实体的字段都精准对应业务场景的需求。
8.2 九个工具函数
应用定义了9个工具函数,将数据到样式/位置/格式/判断的映射逻辑从UI层抽离。
function dynastyColor(dynasty: string): string {
if (dynasty === '唐') return COLORS.zhuSha;
if (dynasty === '宋') return COLORS.daiQing;
if (dynasty === '明') return COLORS.miaoJin;
if (dynasty === '魏晋') return '#6B5B95';
return '#4A6FA5';
}
function tagBg(tag: string): string {
if (tag === '热卖') return '#F7E3DF';
if (tag === '新品') return '#E3EFEA';
if (tag === '古典') return '#F0E8DA';
return COLORS.cardBgDark;
}
function formatLikes(likes: number): string {
if (likes >= 1000) return (likes / 1000).toFixed(1) + 'k';
return likes.toString();
}
function barHeight(v: number): string {
return (v * 1.3).toFixed(0) + 'vp';
}
function marketColor(mode: string): string {
if (mode === '日租') return COLORS.daiQing;
return COLORS.zhuSha;
}
- dynastyColor:朝代映射颜色——唐zhuSha朱砂/宋daiQing黛青/明miaoJin描金/魏晋#6B5B95紫/汉#4A6FA5蓝,五级颜色区分朝代,是本应用最具特色的工具函数
- tagBg:配饰标签映射背景——热卖#F7E3DF浅红/新品#E3EFEA浅绿/古典#F0E8DA浅黄/平价cardBgDark灰,四级背景区分标签类型
- formatLikes:点赞数格式化——>=1000转k保留1位小数(如2412→2.4k),<1000返回原数字符串,避免大数字占用空间
- barHeight:人气映射柱高——v*1.3后toFixed(0)加’vp’单位返回字符串,直接作为Column的height属性值
- marketColor:集市模式映射颜色——日租daiQing黛青/转卖zhuSha朱砂红,两级颜色区分交易模式
- leftHanfus/rightHanfus:汉服拆列——leftHanfus取偶数索引(0,2,4…),rightHanfus取奇数索引(1,3,5…),用于双列布局
- 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减3,x加sin(p.id+p.y/50)*3左右摆动,y<60时y=640回到底部形成循环
9个工具函数中,dynastyColor/tagBg/marketColor三个是颜色映射函数,formatLikes是数值格式化函数,barHeight是数值格式函数,leftHanfus/rightHanfus是数据拆分函数,buildParticles/driftParticles是粒子动画函数。dynastyColor函数是整个应用的核心工具函数——5个朝代映射5种专属颜色,贯穿推荐/朝代/形制/衣橱四个分类,横滑大卡/柱状图/单品列表/朝代标签都用这个函数着色,形成了"朝代即颜色"的统一视觉语言。tagBg函数将配饰标签映射为4种不同背景色(浅红/浅绿/浅黄/灰),marketColor函数将集市模式映射为2种颜色(黛青/朱砂),三个颜色映射函数共同实现了多维度动态着色。formatLikes函数的千进制格式化(>=1000转k)是社交应用的标准数据格式化,barHeight函数的倍率1.3比狼人杀的2.2更小因为人气数值范围更大(60-92),leftHanfus/rightHanfus用奇偶索引拆列是双列布局的标准数据准备,buildParticles用质数41/67步长取模保证粒子初始位置均匀分布,driftParticles用sin函数保证粒子摆动轨迹自然不机械,两个粒子函数共同实现了自定义动画系统。
九、花瓣灯笼粒子动画系统
花瓣灯笼粒子动画系统是本应用最具创意和技术深度的部分,通过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 - 3;
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-3,每帧向上移动3像素(与狼人杀相同y-3,营造悠闲的漂浮感)
- 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值才完成一个周期,这样粒子的水平摆动是缓慢的而非剧烈的,模拟花瓣飘落和灯笼摇曳的自然轨迹。
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毫秒的间隔是经验值——太快会导致性能消耗大,太慢会导致动画卡顿,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(樱花花朵灯笼闪光扶桑等图标),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属性无法完成的自定义粒子动画。与狼人杀应用的粒子系统相比,本应用的粒子图标更贴合汉服古风主题(🌸❀🏮✨🌺 vs 🌙⭐✦🐺✧),漂移速度相同(y-3),营造出"花瓣飘落、灯笼摇曳、闪光点缀"的古风氛围。16个粒子以130ms间隔更新,每个粒子有独立的sin摆动轨迹和循环边界,整体形成了樱花花朵灯笼闪光扶桑在古风宣纸背景上缓缓向上漂浮的效果,与汉服"岂曰无衣与子同袍"的主题高度契合。这种方案虽然比CSS动画或Canvas动画性能稍差,但在ArkUI声明式框架内是实现复杂自定义动画的标准且可靠的方案。
十、4个模态弹窗:发布/出租/移出/详情
应用通过4个模态弹窗实现了增删改查交互闭环,每个弹窗都是独立@Builder,通过show*布尔状态条件渲染。
10.1 发布穿搭弹窗 postOutfitModal(新增)
发布穿搭弹窗是新增穿搭的交互入口,朱砂渐变头部(zhuSha→#8A2B22),包含穿搭描述TextInput/朝代制式Flex选择/穿着场合Row三选一三个表单项。
弹窗外层Column width100% height100% justifyContent Center alignItems Center backgroundColor ‘99000000’(60%透明黑遮罩),点击遮罩onClick关闭。内层Column width86% borderRadius16 white白底clip true。
头部Column width100% padding16 alignItems Start linearGradient angle135从zhuSha朱砂红到#8A2B22深红:"📷 发布穿搭"fontSize17加粗白字 + “记录今日份的古风美丽"fontSize11 #F2DFC5浅金字margin top4。朱砂到深红的渐变背景营造出"发布穿搭的喜庆氛围”。
表单区Scroll scrollable Vertical constraintSize maxHeight 58%,内Column width100% alignItems Start padding left18 right18:
- 穿搭描述:标签 + TextInput placeholder’例如:今日份宋制通勤,月白褙子配远山百迭裙’ text绑定postDesc fontSize13 placeholderColor textHint backgroundColor cardBgDark borderRadius10 padding 12x height42 margin top6,onChange回写postDesc
- 朝代制式:标签 + Flex wrap Wrap,ForEach遍历DYNASTY_NAMES(唐/宋/明/魏晋/汉)渲染标签Text,选中时(postDynasty===idx)白字dynastyColor(d)底(朝代专属颜色),未选中时textSecondary字cardBgDark底,点击postDynasty=idx
- 穿着场合:标签 + Row,三列Column layoutWeight1 padding 9x0 borderRadius10,选中时(postScene===idx)白字daiQing黛青底+daiQing边,未选中时textSecondary字透明底+border边:日常通勤/节日雅集/旅行拍照
- 操作按钮Row margin top20 bottom16:"存草稿"textSecondary字padding 18x10 borderRadius18 border边(点击关闭)+ "🏮 发布"白字加粗padding 24x10 borderRadius18 zhuSha朱砂红底margin left10(点击关闭)
发布穿搭弹窗的特色是朱砂渐变头+朝代专属颜色选择——朝代制式选择用dynastyColor函数映射为5种专属颜色(唐朱砂/宋黛青/明描金/魏晋紫/汉蓝),选中时用对应朝代颜色底,与应用"朝代即颜色"的统一视觉语言一致。穿着场合三选一用daiQing黛青底(与朱砂红主题形成对比),日常通勤/节日雅集/旅行拍照三种场合覆盖汉服穿搭的主要场景。"🏮 发布"按钮用zhuSha朱砂红底+灯笼图标,是发布穿搭的标准CTA。
10.2 出租汉服弹窗 rentOutModal(编辑)
出租汉服弹窗是编辑/出租的交互入口,描金渐变头部(miaoJin→#A87E4F),包含日租金TextInput/押金档位Row/最长可租加减器/清洗说明Flex四个表单项,是表单交互最丰富的弹窗。
头部Column width100% padding16 alignItems Start linearGradient angle135从miaoJin描金到#A87E4F深描金:"🧧 出租我的汉服"fontSize17加粗白字 + “闲置生灰,不如租给同袍穿"fontSize11 #F2E3CE浅金字margin top4。描金到深描金的渐变背景营造出"出租交易的富贵氛围”。
表单区Scroll constraintSize maxHeight 58%:
- 日租金(元/天):标签 + TextInput placeholder’例如:35’ text绑定rentPrice fontSize13 backgroundColor cardBgDark borderRadius10 padding 12x height42 margin top6,onChange回写rentPrice,type(InputType.Number)限制数字输入
- 押金档位:标签 + Row,ForEach遍历[‘¥100’,‘¥200’,‘¥300’]渲染Text layoutWeight1 textAlign Center padding 8x0 borderRadius10,选中时(rentDeposit===idx)白字miaoJin描金底+miaoJin边,未选中时textSecondary字透明底+border边,点击rentDeposit=idx
- 最长可租:标签"最长可租:“+rentDays+” 天" + Row加减器:减号Text(‘−’) fontSize20 daiQing黛青字width38 height38 textAlign Center borderRadius19 backgroundColor cardBgDark(点击rentDays>1时减1)+ 数字Text(rentDays) fontSize18加粗textPrimary layoutWeight1 textAlign Center + 加号Text(‘+’) fontSize20 daiQing黛青字width38 height38 textAlign Center borderRadius19 backgroundColor cardBgDark(点击rentDays<15时加1)
- 清洗说明:标签 + Flex wrap Wrap,ForEach遍历[‘手洗’,‘不可机洗’,‘阴干’,‘熨烫低温’]渲染Text fontSize11 daiQing黛青字padding 10x6圆角10 backgroundColor #E3EFEA浅绿底margin right8 top6
- 操作按钮Row margin top20 bottom16:"取消"textSecondary字padding 20x10 borderRadius18 border边(点击关闭)+ "🧧 上架集市"白字加粗padding 22x10 borderRadius18 miaoJin描金底margin left10(点击关闭)
出租汉服弹窗的特色是描金渐变头+丰富表单交互——整体用miaoJin描金作为主题色(头部渐变/押金档位选中/上架按钮),与发布弹窗的朱砂红主题区分——发布是内容创作用朱砂,出租是交易用描金。日租金TextInput用type(InputType.Number)限制只能输入数字,是价格输入的标准处理。押金档位三选一(¥100/¥200/¥300)用描金底选中。最长可租加减器1-15天,减号加号用daiQing黛青字(与描金主题形成对比),rentDays初始3天,范围1-15天。清洗说明四个标签(手洗/不可机洗/阴干/熨烫低温)用daiQing黛青字+浅绿底,是汉服清洗的标准注意事项。"🧧 上架集市"按钮用miaoJin描金底+红包图标,是出租上架的标准CTA。
10.3 移出衣橱弹窗 removeClosetModal(删除)
移出衣橱弹窗是删除/移出的确认型对话框,警告小卡风格,比其他弹窗更小更简洁。
外层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红底确认按钮的双重防护,避免了误操作导致的汉服移出。danger #C0392B比zhuSha #B03A2E稍深红,用于删除确认按钮的危险色,与主题朱砂红形成区分。
10.4 穿搭详情弹窗 outfitDetailModal(查询)
穿搭详情弹窗是查看穿搭详细信息的查询入口,朱砂渐变头部,包含cover+朝代形制+点赞+参考价/intro介绍/单品清单/同袍评论/喜欢+购买按钮,是内容最丰富的弹窗。
头部Column width100% padding16 linearGradient angle135从zhuSha朱砂红到#8A2B22深红:Row(“👗 穿搭详情"fontSize16加粗白字layoutWeight1 + selectedHanfu存在时显示汉服名+” · @"+作者fontSize11 #F2DFC5浅金字margin top3 + "✕"关闭按钮白字padding8点击关闭)。
内容区Scroll constraintSize maxHeight 58%,selectedHanfu存在时渲染:
- 头部信息Row width100% padding 16x14:cover Text fontSize48 width84 height84 textAlign Center borderRadius14 backgroundColor cardBgDark灰底 + 信息Column layoutWeight1 margin left12 alignItems Start(朝代形制selectedHanfu.dynasty+"制 · “+form fontSize14加粗daiQing黛青 + 点赞❤️ formatLikes(likes)+” 人喜欢"fontSize11 zhuSha朱砂红margin top4 + 参考价¥price fontSize11 textSecondary margin top4)
- 介绍intro Text fontSize12 textSecondary margin top12 left16 right16
- 单品清单标题"🧵 单品清单"fontSize14加粗daiQing黛青margin top16 left16 + ForEach遍历OUTFIT_PIECES(4件单品)渲染Row width100% padding 8x16:单品名p.name fontSize12加粗textPrimary width52 + 单品详情p.detail fontSize11 textSecondary layoutWeight1 + Divider border色
- 同袍评论标题"💬 同袍评论"fontSize14加粗daiQing黛青margin top14 left16 + ForEach遍历OUTFIT_COMMENTS(3条评论)渲染Column width100% padding12 borderRadius12 backgroundColor cardBgDark灰底margin top8 left16 right16:Row(头像c.avatar fontSize20 + 评论者c.name fontSize12加粗textPrimary margin left8)+ 评论内容c.content fontSize11 textSecondary margin top6
- 操作按钮Row margin top16 bottom18 left16:"❤️ 喜欢"fontSize13 zhuSha朱砂红字padding 20x10 borderRadius18 border zhuSha边 + "去购买"白字加粗fontSize13 padding 20x10 borderRadius18 backgroundColor zhuSha朱砂红margin left10(点击关闭)
4 件单品:上襦(交领上襦 · 藕荷色 · M码)、下裙(六米摆齐胸裙 · 雾蓝渐变)、大袖衫(月白纱质 · 刺绣云纹)、配饰(珍珠步摇 · 玉禁步)。3条评论:青梧(🌿·这套配色绝了,已下单同款马面,蹲一个上身图!)、子衿(🍂·妆造也好用心,发簪和裙子是同色系的吧?)、临安(🏮·拍出画卷感了,请问是在哪个园子取的景?)。单品清单和评论都是静态接口数据,不需要响应式更新。
穿搭详情弹窗是4个弹窗中内容最丰富的——从cover封面+朝代形制+点赞+参考价,到intro介绍,到单品清单(4件上襦/下裙/大袖衫/配饰),到同袍评论(3条),到喜欢+购买操作按钮,完整呈现了一套汉服穿搭的详细信息。单品清单用Divider分隔每件单品,评论用cardBgDark灰底卡片区分,是内容详情的标准布局。"❤️ 喜欢"按钮用朱砂红边+朱砂红字(描边按钮),"去购买"按钮用朱砂红底(实心按钮),两个按钮用描边vs实心区分操作类型,是详情页操作按钮的标准设计。
四个弹窗各自独立@Builder,通过showPostModal/showRentModal/showRemoveModal/showDetailModal四个布尔状态条件渲染,弹窗外层统一用Column width100% height100% justifyContent Center + backgroundColor '99000000’实现遮罩+居中,点击遮罩onClick关闭。四个弹窗覆盖了汉服文化社区的核心交互——发布穿搭(新增)、出租汉服(编辑)、移出衣橱(删除)、穿搭详情(查询),形成了完整的增删改查交互闭环。发布弹窗用朱砂红主题(内容创作)、出租弹窗用描金主题(交易)、移出弹窗用danger红(删除警告)、详情弹窗用朱砂红主题(内容查询),四个弹窗用不同主题色区分交互类型,是模态弹窗交互设计的优秀实践。
特性对比表
| 技术维度 | 具体实现 | 作用与特点 | 适用场景 |
|---|---|---|---|
| 色彩管理 | ColorPalette接口+COLORS 17色 | interface类型约束编译期校验,朱砂红黛青描金古风宣纸风 | 全局配色统一管理 |
| 朱砂红主色 | zhuSha #B03A2E | 头部渐变/激活Tab/按钮/标签/价格/点赞,汉服朱红经典色 | 主题核心色 |
| 黛青辅色 | daiQing #1F5F5B | 衣橱总览/发型序号/活动类型/聊一聊/形制标题/统计,汉服黛青经典色 | 辅助色 |
| 描金点缀 | miaoJin #D4A373 | 顶部Tab指示条/难度星级/特色标签/心愿单/衣龄,汉服金线描边 | 点缀色 |
| 宣纸米白底 | bg #F7F3EA | 全局背景,比纯白稍黄模拟宣纸颜色,古风宣纸质感 | 背景色 |
| 朝代颜色映射 | dynastyColor唐zhuSha/宋daiQing/明miaoJin/魏晋#6B5B95/汉#4A6FA5 | 5朝代5专属颜色,贯穿推荐/朝代/形制/衣橱,朝代即颜色 | 朝代着色 |
| 底部4Tab | 首页🏠/衣橱👘/集市🛍️/我的👤 | 一级导航切换大模块,激活态zhuSha朱砂红 | 应用一级导航 |
| 顶部6Tab | 推荐✨/朝代🏛️/发型💇/配饰📿/活动🏮/形制📖 | 首页模块二级分类,横向Scroll可滑动,激活态朱砂+描金指示条 | 首页分类 |
| 双层导航架构 | 底部一级+顶部二级 | 底部切大模块,顶部在模块内细分分类,内容型应用标准架构 | 内容型应用导航 |
| 推荐双列瀑布 | leftHanfus/rightHanfus奇偶拆列+cover+朝代标签+名字+作者+点赞+价格 | 双列瀑布穿搭卡片,dynastyColor朝代颜色标签 | 推荐分类 |
| 朝代横滑大卡 | Scroll Horizontal+5朝代大卡dynastyColor渐变 | 每个朝代专属颜色渐变,width96 height110 | 朝代分类 |
| 朝代人气柱状图 | DYNASTY_NAMES 5朝+DYNASTY_HEAT [92,78,85,60,70]+barHeight(v*1.3) | 朝代人气可视化,每朝dynastyColor专属颜色 | 朝代分类 |
| 发型时间轴 | 圆形序号daiQing底+垂直连接线+名字+难度+工具+步数+学它按钮 | 教程步骤时间轴,6个发型教程从★到★★★★ | 发型分类 |
| 配饰三列格子 | Flex wrap+width30%+icon+名字+价格+tagBg标签 | 三列自动换行,12个配饰,tagBg四种背景色 | 配饰分类 |
| 活动日历 | 星期表头+31天Flex+8月24日zhuSha高亮🏮 | 当月活动日历视图,Flex换行排列日期 | 活动分类 |
| 全年活动列表 | 日期split月日+名字+类型标签daiQing+城市+报名人数+报名按钮 | 8个活动覆盖全年,雅集/踏青/展会/礼仪/灯会/游园/演出 | 活动分类 |
| 形制百科横条 | 名字daiQing+描述maxLines2+朝代标签dynastyColor+特色miaoJin+奇偶交替背景 | 8个形制,斑马纹交替背景提升可读性 | 形制分类 |
| 衣橱黛青总览 | daiQing→#143B38渐变+汉服6配饰12总价值¥3248+整理衣橱 | 黛青主题衣橱管理,与首页朱砂主题区分 | 衣橱Tab |
| 衣橱列表 | 朝代标签dynastyColor+名字+成色success标签+上次穿着+📤移出 | 6件衣橱汉服,成色五级,移出触发removeModal | 衣橱Tab |
| 集市列表 | cover+名字+模式标签marketColor+卖家+价格+聊一聊daiQing按钮 | 8件商品日租5转卖3,marketColor日租daiQing/转卖zhuSha | 集市Tab |
| 我的个人信息 | 🍂头像+临安+袍子LV6衣龄3年常穿宋制+心愿单+发布按钮 | 白底简洁个人主页,心愿单miaoJin描金字 | 我的Tab |
| 我的四列统计 | 48穿搭发布zhuSha/1.2k获赞zhuSha/6活动参与daiQing/86同袍关注daiQing | 双色区分数据类型,内容类朱砂/社交类黛青 | 我的Tab |
| 我的活动足迹 | 4个已参加活动+🏮图标+success绿字已参加 | 同袍活动历史,与ALL_EVENTS对应 | 我的Tab |
| @Observed HanfuItem | 汉服实体9字段15件 | id/name/dynasty/form/cover/price/likes/author/intro,字段最多实体 | 汉服数据 |
| @Observed HairTutItem | 发型教程实体5字段6个 | id/name/difficulty/steps/tool,难度星级步数工具 | 发型数据 |
| @Observed AccessoryItem | 配饰实体5字段12个 | id/name/icon/price/tag,热卖新品古典平价标签 | 配饰数据 |
| @Observed EventItem | 活动实体6字段8个 | id/name/city/date/type/joined,城市日期类型报名人数 | 活动数据 |
| @Observed ClosetItem | 衣橱实体5字段6件 | id/name/dynasty/lastWorn/condition,上次穿着成色 | 衣橱数据 |
| @Observed MarketItem | 集市实体6字段8件 | id/name/seller/price/mode/cover,卖家模式日租转卖 | 集市数据 |
| FormIntroItem接口 | 形制介绍4字段8个 | name/dynasty/desc/feature,静态配置不需@Observed | 形制数据 |
| CommentItem接口 | 评论3字段3条 | name/avatar/content,静态配置 | 评论数据 |
| SingleItem接口 | 单品2字段4件 | name/detail,静态配置 | 单品清单 |
| dynastyColor | 唐zhuSha/宋daiQing/明miaoJin/魏晋紫/汉蓝 | 朝代五级颜色映射,最具特色工具函数 | 朝代着色 |
| tagBg | 热卖#F7E3DF/新品#E3EFEA/古典#F0E8DA/平价cardBgDark | 配饰标签四级背景映射 | 配饰标签 |
| formatLikes | >=1000转k保留1位小数 | 点赞数千进制格式化,避免大数字占空间 | 数据格式化 |
| barHeight | v*1.3+‘vp’ | 人气映射柱高字符串,倍率1.3适配大数值范围 | 柱状图高度 |
| marketColor | 日租daiQing/转卖zhuSha | 集市模式两级颜色映射 | 交易模式着色 |
| leftHanfus/rightHanfus | 偶数/奇数索引拆分数组 | 双列布局数据准备 | 双列瀑布 |
| 粒子接口 | ParticleItem id/x/y/size/opacity/icon 6字段 | 粒子数据结构 | 粒子动画 |
| 花瓣灯笼图标 | 🌸❀🏮✨🌺五种 | 樱花/花朵/灯笼/闪光/扶桑,汉服古风主题 | 粒子图标 |
| buildParticles() | 16粒子质数41/67步长取模均匀分布 | x=(i41)%340+10 y=80+(i67)%560,质数避免聚集 | 粒子初始化 |
| driftParticles() | y-3+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绝对定位 | 粒子覆盖在主内容之上,悬浮效果 | 粒子渲染 |
| 发布穿搭弹窗 | zhuSha→#8A2B22渐变头+描述TextInput+朝代Flex 5个+场合Row三选一 | 新增穿搭,朝代专属颜色选择,朱砂主题内容创作 | 发布交互 |
| 出租汉服弹窗 | miaoJin→#A87E4F渐变头+日租金TextInput Number+押金Row+可租加减器+清洗Flex | 编辑出租,表单最丰富,描金主题交易,日租1-15天 | 出租交互 |
| 移出衣橱弹窗 | width78%警告小卡+📤图标+购买记录保留提示+再想想/移出衣橱danger | 删除移出,双重防护避免误操作,购买记录保留消除顾虑 | 移出交互 |
| 穿搭详情弹窗 | zhuSha渐变头+cover+朝代形制+点赞+价格+intro+单品清单+评论+喜欢/购买 | 查询详情,内容最丰富,4件单品3条评论,描边vs实心按钮 | 详情交互 |
| 弹窗遮罩 | Column width100% height100%+bg ‘99000000’+onClick关闭 | 60%透明黑遮罩+居中,点击遮罩关闭 | 模态弹窗通用 |
| TextInput受控 | text绑定@State字符串+onChange回写 | 穿搭描述/日租金受控输入,日租金type Number限制 | 表单输入 |
| Flex换行 | Flex({wrap:FlexWrap.Wrap}) | 配饰三列/活动日历/朝代选择/清洗说明自动换行 | 标签布局 |
| 加减器 | −/+按钮+数字layoutWeight1+范围限制 | 最长可租1-15天,减号加号daiQing字 | 数值输入 |
| aboutToAppear | 组件创建后启动setInterval | 生命周期启动定时器,粒子动画开始 | 生命周期 |
| aboutToDisappear | 组件销毁前clearInterval | 生命周期清除定时器,防止内存泄漏 | 生命周期 |
| ForEach键值生成器 | (item)=>唯一标识字符串 | 列表高效diff更新,粒子key包含y强制刷新 | 所有列表渲染 |
| linearGradient | angle135 colors[[start,0],[end,1]] | 朱砂/黛青/描金渐变背景,用于头部/大卡/总览 | 渐变背景 |
| Scroll横向 | scrollable Horizontal+scrollBar Off | 顶部6Tab/朝代大卡横向滑动 | 横向滚动 |
| constraintSize maxHeight | 限制弹窗表单最大高度 | 防止内容溢出弹窗,长表单可滚动 | 模态弹窗 |
| borderRadius+clip true | 弹窗圆角+裁剪内容 | 内层卡片圆角,clip true裁剪超出圆角的内容 | 弹窗圆角 |
| position绝对定位 | 粒子用position x y | 粒子悬浮效果,position绝对定位 | 精确定位 |
| 奇偶交替背景 | idx%2===0?cardBg:cardBgDark | 形制列表斑马纹交替,提升长列表可读性 | 长列表 |
| split日期拆分 | e.date.split(‘-’)[0]月/[1]日 | 活动日期月日拆分,日大号朱砂月小号灰 | 日期展示 |
| 三元运算符 | m.mode===‘日租’?‘¥’+price+‘/天’:‘¥’+price | 集市价格按模式动态显示,日租/天转卖总价 | 条件渲染 |
完整代码
// ============================================================
// 1089.ets —— QQ汉服·同袍社(汉服文化社区)
// 风格:朱砂红 + 黛青 · 古风宣纸
// 底部4tab:首页 / 衣橱 / 集市 / 我的
// 顶部6tab:推荐 / 朝代 / 发型 / 配饰 / 活动 / 形制
// 弹框:发布穿搭(新增) / 出租汉服(编辑) / 移出衣橱(删除) / 穿搭详情
// 特效:花瓣灯笼粒子 + 朝代人气柱状图
// ============================================================
interface ColorPalette {
zhuSha: string;
zhuShaLight: string;
daiQing: string;
daiQingLight: string;
miaoJin: string;
bg: string;
cardBg: string;
cardBgDark: string;
textPrimary: string;
textSecondary: string;
textHint: string;
white: string;
border: string;
danger: string;
success: string;
}
const COLORS: ColorPalette = {
zhuSha: '#B03A2E',
zhuShaLight: '#D96A5C',
daiQing: '#1F5F5B',
daiQingLight: '#4A8A86',
miaoJin: '#D4A373',
bg: '#F7F3EA',
cardBg: '#FFFFFF',
cardBgDark: '#EFE8DA',
textPrimary: '#3A2E28',
textSecondary: '#7D6E62',
textHint: '#B8AB9E',
white: '#FFFFFF',
border: '#E5DCCC',
danger: '#C0392B',
success: '#4C8A6E'
};
interface TabItem {
label: string;
icon: string;
}
const BOTTOM_TABS: TabItem[] = [
{ label: '首页', icon: '🏠' },
{ label: '衣橱', icon: '👘' },
{ label: '集市', icon: '🛍️' },
{ label: '我的', icon: '👤' }
];
const TOP_TABS: TabItem[] = [
{ label: '推荐', icon: '✨' },
{ label: '朝代', icon: '🏛️' },
{ label: '发型', icon: '💇' },
{ label: '配饰', icon: '📿' },
{ label: '活动', icon: '🏮' },
{ label: '形制', icon: '📖' }
];
const DYNASTY_NAMES: string[] = ['唐', '宋', '明', '魏晋', '汉'];
const DYNASTY_HEAT: number[] = [92, 78, 85, 60, 70];
@Observed
class HanfuItem {
id: number;
name: string;
dynasty: string;
form: string;
cover: string;
price: number;
likes: number;
author: string;
intro: string;
constructor(id: number, name: string, dynasty: string, form: string,
cover: string, price: number, likes: number, author: string, intro: string) {
this.id = id;
this.name = name;
this.dynasty = dynasty;
this.form = form;
this.cover = cover;
this.price = price;
this.likes = likes;
this.author = author;
this.intro = intro;
}
}
function buildHanfus(): HanfuItem[] {
return [
new HanfuItem(1, '齐胸襦裙·牡丹亭', '唐', '齐胸襦裙', '🌸', 368, 2412, '月出', '重工刺绣牡丹,走动间流光溢彩的唐制经典'),
new HanfuItem(2, '马面裙·云锦纹', '明', '马面裙', '🐉', 499, 3105, '青梧', '云锦提花织金马面,明制端庄大气的顶流单品'),
new HanfuItem(3, '袄裙·缠枝莲', '明', '袄裙', '🪷', 459, 1804, '阿沅', '缠枝莲暗纹竖领斜襟,冬日里的温柔铠甲'),
new HanfuItem(4, '褙子·月白', '宋', '褙子', '🌙', 329, 2230, '临安', '月白色素雅褙子,宋人极简美学的日常演绎'),
new HanfuItem(5, '齐腰襦裙·烟霞', '汉', '齐腰襦裙', '🌫️', 389, 1521, '子衿', '渐变烟霞色裙摆,层层叠出山河远阔'),
new HanfuItem(6, '圆领袍·墨麟', '唐', '圆领袍', '🐎', 559, 982, '折柳', '墨色圆领缺胯袍,一秒入长安的侠气选择'),
new HanfuItem(7, '大袖衫·惊鸿', '魏晋', '大袖衫', '🕊️', 629, 2738, '白鹭', '广袖翩然如惊鸿照影,魏晋风骨的天花板'),
new HanfuItem(8, '旋裙·青绿', '宋', '旋裙', '🏔️', 299, 1234, '疏影', '青绿山水配色旋裙,只此青绿的平价替代'),
new HanfuItem(9, '直裾·玄端', '汉', '直裾', '🖤', 479, 897, '守拙', '玄色直裾深衣,最接近汉原本貌的庄重选择'),
new HanfuItem(10, '比甲·海棠', '明', '比甲', '🌺', 269, 1146, '棠梨', '海棠红无袖比甲,叠穿层次感的点睛之笔'),
new HanfuItem(11, '诃子裙·霓裳', '唐', '诃子裙', '🌈', 529, 2050, '扶光', '霓裳羽衣配色,唐风宴会的全场焦点'),
new HanfuItem(12, '交领襦裙·竹影', '魏晋', '交领襦裙', '🎋', 349, 1328, '山鬼', '竹影墨绘交领,山中何事松花酿酒'),
new HanfuItem(13, '百迭裙·远山', '宋', '百迭裙', '⛰️', 319, 1642, '砚青', '远山黛色百迭裙,行走时裙褶如山峦起伏'),
new HanfuItem(14, '短袄·瑞雪', '明', '短袄', '❄️', 399, 943, '雪衣', '瑞雪白短袄立领,配马面裙的冬日限定'),
new HanfuItem(15, '曲裾·青霜', '汉', '曲裾', '💎', 439, 1120, '未央', '青霜色曲裾绕襟,深衣制度的流动诗篇')
];
}
const ALL_HANFUS: HanfuItem[] = buildHanfus();
function leftHanfus(): HanfuItem[] {
const res: HanfuItem[] = [];
for (let i = 0; i < ALL_HANFUS.length; i += 2) {
res.push(ALL_HANFUS[i]);
}
return res;
}
function rightHanfus(): HanfuItem[] {
const res: HanfuItem[] = [];
for (let i = 1; i < ALL_HANFUS.length; i += 2) {
res.push(ALL_HANFUS[i]);
}
return res;
}
@Observed
class HairTutItem {
id: number;
name: string;
difficulty: string;
steps: number;
tool: string;
constructor(id: number, name: string, difficulty: string, steps: number, tool: string) {
this.id = id;
this.name = name;
this.difficulty = difficulty;
this.steps = steps;
this.tool = tool;
}
}
const HAIR_TUTS: HairTutItem[] = [
new HairTutItem(1, '灵蛇髻', '★★★', 5, '发包 · 发网 · U型夹'),
new HairTutItem(2, '堕马髻', '★★', 4, '垫发包 · 插梳'),
new HairTutItem(3, '双环望仙髻', '★★★★', 6, '双发包 · 发胶 · 步摇'),
new HairTutItem(4, '飞仙髻', '★★★', 5, '高发包 · 发带'),
new HairTutItem(5, '半翻髻', '★★', 3, '半发包 · 发簪'),
new HairTutItem(6, '垂挂髻', '★', 3, '皮筋 · 小发夹')
];
@Observed
class AccessoryItem {
id: number;
name: string;
icon: string;
price: number;
tag: string;
constructor(id: number, name: string, icon: string, price: number, tag: string) {
this.id = id;
this.name = name;
this.icon = icon;
this.price = price;
this.tag = tag;
}
}
const ALL_ACCESSORIES: AccessoryItem[] = [
new AccessoryItem(1, '鎏金发簪', '🎀', 68, '热卖'),
new AccessoryItem(2, '珍珠步摇', '💫', 128, '新品'),
new AccessoryItem(3, '玉禁步', '🪶', 158, '古典'),
new AccessoryItem(4, '宫绦', '🧵', 45, '热卖'),
new AccessoryItem(5, '璎珞项圈', '📿', 98, '新品'),
new AccessoryItem(6, '云纹发带', '☁️', 29, '平价'),
new AccessoryItem(7, '油纸伞', '☂️', 88, '古典'),
new AccessoryItem(8, '绣花团扇', '🪭', 58, '热卖'),
new AccessoryItem(9, '香囊', '🧿', 35, '平价'),
new AccessoryItem(10, '流苏耳坠', '💧', 49, '新品'),
new AccessoryItem(11, '护甲套', '💅', 76, '古典'),
new AccessoryItem(12, '缠花发簪', '🌸', 89, '热卖')
];
@Observed
class EventItem {
id: number;
name: string;
city: string;
date: string;
type: string;
joined: number;
constructor(id: number, name: string, city: string, date: string, type: string, joined: number) {
this.id = id;
this.name = name;
this.city = city;
this.date = date;
this.type = type;
this.joined = joined;
}
}
const ALL_EVENTS: EventItem[] = [
new EventItem(1, '花朝节汉服雅集', '杭州', '03-12', '雅集', 386),
new EventItem(2, '上巳节曲水流觞', '武汉', '04-03', '踏青', 298),
new EventItem(3, '西塘汉服文化周', '嘉兴', '10-28', '展会', 5200),
new EventItem(4, '中华礼乐大会', '曲阜', '09-16', '礼仪', 1800),
new EventItem(5, '长安上元灯会', '西安', '02-24', '灯会', 2600),
new EventItem(6, '洛阳牡丹汉服节', '洛阳', '04-18', '游园', 3400),
new EventItem(7, '国风音乐会', '成都', '05-20', '演出', 1500),
new EventItem(8, '同袍冬至雅集', '南京', '12-21', '雅集', 420)
];
interface FormIntroItem {
name: string;
dynasty: string;
desc: string;
feature: string;
}
const FORM_INTROS: FormIntroItem[] = [
{ name: '齐胸襦裙', dynasty: '唐', desc: '裙腰束于胸上的襦裙,配大袖衫飘逸出尘', feature: '飘逸' },
{ name: '马面裙', dynasty: '明', desc: '前后共有四个裙门两两重合的马面结构', feature: '端庄' },
{ name: '袄裙', dynasty: '明', desc: '上袄下裙的典型明制穿法,冬暖夏凉', feature: '日常' },
{ name: '褙子', dynasty: '宋', desc: '直领对襟两侧开衩的长衣,宋人最爱', feature: '素雅' },
{ name: '直裾', dynasty: '汉', desc: '裾垂直而下不绕襟的深衣制式', feature: '庄重' },
{ name: '曲裾', dynasty: '汉', desc: '衣襟层层缠绕的深衣,线条流动', feature: '古典' },
{ name: '圆领袍', dynasty: '唐', desc: '圆领右衽的通裁袍服,男女皆可着', feature: '英气' },
{ name: '大袖衫', dynasty: '魏晋', desc: '宽袖广身的外衫,仙气飘飘的担当', feature: '仙气' }
];
@Observed
class ClosetItem {
id: number;
name: string;
dynasty: string;
lastWorn: string;
condition: string;
constructor(id: number, name: string, dynasty: string, lastWorn: string, condition: string) {
this.id = id;
this.name = name;
this.dynasty = dynasty;
this.lastWorn = lastWorn;
this.condition = condition;
}
}
const MY_CLOSET: ClosetItem[] = [
new ClosetItem(1, '马面裙·云锦纹', '明', '08-20', '九成新'),
new ClosetItem(2, '褙子·月白', '宋', '08-15', '全新带吊牌'),
new ClosetItem(3, '齐胸襦裙·牡丹亭', '唐', '07-30', '轻微勾丝'),
new ClosetItem(4, '圆领袍·墨麟', '唐', '06-18', '九成新'),
new ClosetItem(5, '大袖衫·惊鸿', '魏晋', '05-02', '全新'),
new ClosetItem(6, '直裾·玄端', '汉', '04-11', '八成新')
];
@Observed
class MarketItem {
id: number;
name: string;
seller: string;
price: number;
mode: string;
cover: string;
constructor(id: number, name: string, seller: string, price: number, mode: string, cover: string) {
this.id = id;
this.name = name;
this.seller = seller;
this.price = price;
this.mode = mode;
this.cover = cover;
}
}
const MARKET_ITEMS: MarketItem[] = [
new MarketItem(1, '诃子裙·霓裳', '扶光', 30, '日租', '🌈'),
new MarketItem(2, '大袖衫·惊鸿', '白鹭', 45, '日租', '🕊️'),
new MarketItem(3, '马面裙·柿红', '青梧', 399, '转卖', '🍊'),
new MarketItem(4, '圆领袍·墨麟', '折柳', 50, '日租', '🐎'),
new MarketItem(5, '百迭裙·远山', '砚青', 260, '转卖', '⛰️'),
new MarketItem(6, '袄裙·缠枝莲', '阿沅', 38, '日租', '🪷'),
new MarketItem(7, '齐胸襦裙·雀蓝', '月出', 280, '转卖', '🐦'),
new MarketItem(8, '曲裾·青霜', '未央', 42, '日租', '💎')
];
interface CommentItem {
name: string;
avatar: string;
content: string;
}
const OUTFIT_COMMENTS: CommentItem[] = [
{ name: '青梧', avatar: '🌿', content: '这套配色绝了,已下单同款马面,蹲一个上身图!' },
{ name: '子衿', avatar: '🍂', content: '妆造也好用心,发簪和裙子是同色系的吧?' },
{ name: '临安', avatar: '🏮', content: '拍出画卷感了,请问是在哪个园子取的景?' }
];
interface SingleItem {
name: string;
detail: string;
}
const OUTFIT_PIECES: SingleItem[] = [
{ name: '上襦', detail: '交领上襦 · 藕荷色 · M码' },
{ name: '下裙', detail: '六米摆齐胸裙 · 雾蓝渐变' },
{ name: '大袖衫', detail: '月白纱质 · 刺绣云纹' },
{ name: '配饰', detail: '珍珠步摇 · 玉禁步' }
];
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;
}
function driftParticles(list: ParticleItem[]): ParticleItem[] {
const next: ParticleItem[] = [];
for (let i = 0; i < list.length; i++) {
const p = list[i];
const ny = p.y - 3;
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;
}
function dynastyColor(dynasty: string): string {
if (dynasty === '唐') {
return COLORS.zhuSha;
}
if (dynasty === '宋') {
return COLORS.daiQing;
}
if (dynasty === '明') {
return COLORS.miaoJin;
}
if (dynasty === '魏晋') {
return '#6B5B95';
}
return '#4A6FA5';
}
function tagBg(tag: string): string {
if (tag === '热卖') {
return '#F7E3DF';
}
if (tag === '新品') {
return '#E3EFEA';
}
if (tag === '古典') {
return '#F0E8DA';
}
return COLORS.cardBgDark;
}
function formatLikes(likes: number): string {
if (likes >= 1000) {
return (likes / 1000).toFixed(1) + 'k';
}
return likes.toString();
}
function barHeight(v: number): string {
return (v * 1.3).toFixed(0) + 'vp';
}
function marketColor(mode: string): string {
if (mode === '日租') {
return COLORS.daiQing;
}
return COLORS.zhuSha;
}
@Entry
struct Index {
@State currentBottomTab: number = 0;
@State currentTopTab: number = 0;
@State showPostModal: boolean = false;
@State showRentModal: boolean = false;
@State showRemoveModal: boolean = false;
@State showDetailModal: boolean = false;
@State selectedHanfu: HanfuItem | null = null;
@State particles: ParticleItem[] = buildParticles();
@State postDesc: string = '';
@State postDynasty: number = 0;
@State postScene: number = 0;
@State rentPrice: string = '';
@State rentDeposit: number = 0;
@State rentDays: number = 3;
private timerId: number = -1;
aboutToAppear() {
this.timerId = setInterval(() => {
this.particles = driftParticles(this.particles);
}, 130);
}
aboutToDisappear() {
if (this.timerId >= 0) {
clearInterval(this.timerId);
}
}
build() {
Stack() {
Column() {
this.headerBuilder()
if (this.currentBottomTab === 0) {
this.homeTopTabs()
} else if (this.currentBottomTab === 1) {
this.closetTopBar()
} else if (this.currentBottomTab === 2) {
this.marketTopBar()
} else {
this.mineTopBar()
}
Scroll() {
Column() {
if (this.currentBottomTab === 0) {
this.homeContent()
} else if (this.currentBottomTab === 1) {
this.closetContent()
} else if (this.currentBottomTab === 2) {
this.marketContent()
} else {
this.mineContent()
}
}
.width('100%')
.alignItems(HorizontalAlign.Start)
}
.layoutWeight(1)
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
this.bottomTabs()
}
.width('100%')
.height('100%')
.backgroundColor(COLORS.bg)
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))
if (this.showPostModal) {
this.postOutfitModal()
}
if (this.showRentModal) {
this.rentOutModal()
}
if (this.showRemoveModal) {
this.removeClosetModal()
}
if (this.showDetailModal) {
this.outfitDetailModal()
}
}
.width('100%')
.height('100%')
}
// ================= 头部(古风宣纸朱砂) =================
@Builder
headerBuilder() {
Column() {
Row() {
Column() {
Text('🏮 QQ汉服')
.fontSize(21)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.white)
Text('与子同袍 · 今日有 1.2万 同袍在线')
.fontSize(11)
.fontColor('#F2DFC5')
.margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Column() {
Text('🧧')
.fontSize(20)
Text('签到')
.fontSize(10)
.fontColor('#F2DFC5')
.margin({ top: 2 })
}
.alignItems(HorizontalAlign.Center)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(12)
.backgroundColor('33FFFFFF')
.margin({ right: 8 })
.onClick(() => {
this.currentBottomTab = 3;
})
Column() {
Text('📷')
.fontSize(20)
Text('发布')
.fontSize(10)
.fontColor(COLORS.white)
.margin({ top: 2 })
}
.alignItems(HorizontalAlign.Center)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(12)
.backgroundColor(COLORS.zhuSha)
.onClick(() => {
this.showPostModal = true;
})
}
.width('100%')
.padding({ left: 16, right: 16, top: 10 })
Row() {
Text('🔍')
.fontSize(14)
.margin({ left: 12 })
Text('搜索汉服 / 形制 / 同袍')
.fontSize(12)
.fontColor('#F2DFC5')
.margin({ left: 8 })
Column().layoutWeight(1)
Text('🏮 花朝节活动报名中')
.fontSize(10)
.fontColor(COLORS.miaoJin)
.margin({ right: 12 })
}
.width('92%')
.height(34)
.borderRadius(17)
.backgroundColor('44FFFFFF')
.margin({ top: 10, bottom: 12 })
}
.width('100%')
.linearGradient({
angle: 135,
colors: [[COLORS.zhuSha, 0], ['#8A2B22', 1]]
})
}
// ================= 顶部tab =================
@Builder
homeTopTabs() {
Row() {
Scroll() {
Row() {
ForEach(TOP_TABS, (t: TabItem, idx: number) => {
Column() {
Text(t.label)
.fontSize(13)
.fontWeight(this.currentTopTab === idx ? FontWeight.Bold : FontWeight.Normal)
.fontColor(this.currentTopTab === idx ? COLORS.zhuSha : COLORS.textSecondary)
if (this.currentTopTab === idx) {
Divider()
.width(18)
.strokeWidth(3)
.color(COLORS.miaoJin)
.margin({ top: 4 })
} else {
Divider()
.width(18)
.strokeWidth(3)
.color('transparent')
.margin({ top: 4 })
}
}
.padding({ left: 14, right: 14, top: 8, bottom: 6 })
.onClick(() => {
this.currentTopTab = idx;
})
}, (t: TabItem) => t.label)
}
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.layoutWeight(1)
}
.width('100%')
.backgroundColor(COLORS.cardBg)
.border({ width: 1, color: COLORS.border })
}
@Builder
closetTopBar() {
Row() {
Text('👘 我的衣橱')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
.margin({ left: 16 })
Column().layoutWeight(1)
Text('6 件 · 已穿 42 次')
.fontSize(11)
.fontColor(COLORS.textSecondary)
.margin({ right: 16 })
}
.width('100%')
.padding({ top: 10, bottom: 10 })
.backgroundColor(COLORS.cardBg)
.border({ width: 1, color: COLORS.border })
}
@Builder
marketTopBar() {
Row() {
Text('🛍️ 同袍集市')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
.margin({ left: 16 })
Column().layoutWeight(1)
Text('出租我的汉服')
.fontSize(11)
.fontColor(COLORS.white)
.padding({ left: 10, right: 10, top: 5, bottom: 5 })
.borderRadius(12)
.backgroundColor(COLORS.zhuSha)
.margin({ right: 16 })
.onClick(() => {
this.showRentModal = true;
})
}
.width('100%')
.padding({ top: 10, bottom: 10 })
.backgroundColor(COLORS.cardBg)
.border({ width: 1, color: COLORS.border })
}
@Builder
mineTopBar() {
Row() {
Text('👤 同袍主页')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
.margin({ left: 16 })
Column().layoutWeight(1)
Text('衣龄 3 年 · 袍子 LV.6')
.fontSize(11)
.fontColor(COLORS.miaoJin)
.margin({ right: 16 })
}
.width('100%')
.padding({ top: 10, bottom: 10 })
.backgroundColor(COLORS.cardBg)
.border({ width: 1, color: COLORS.border })
}
// ================= 首页内容(6个顶部tab差异化) =================
@Builder
homeContent() {
Column() {
if (this.currentTopTab === 0) {
this.recommendContent()
} else if (this.currentTopTab === 1) {
this.dynastyContent()
} else if (this.currentTopTab === 2) {
this.hairstyleContent()
} else if (this.currentTopTab === 3) {
this.accessoryContent()
} else if (this.currentTopTab === 4) {
this.eventContent()
} else {
this.formContent()
}
}
.width('100%')
.padding({ bottom: 12 })
}
// ---- 推荐:双列瀑布穿搭 ----
@Builder
recommendContent() {
Column() {
Row() {
Text('✨ 今日精选穿搭')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
.layoutWeight(1)
Text('换一批')
.fontSize(11)
.fontColor(COLORS.zhuSha)
}
.width('92%')
.margin({ top: 14 })
Row() {
Column() {
ForEach(leftHanfus(), (h: HanfuItem) => {
Column() {
Column() {
Text(h.cover)
.fontSize(42)
Text(h.dynasty)
.fontSize(10)
.fontColor(COLORS.white)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
.borderRadius(4)
.backgroundColor(dynastyColor(h.dynasty))
.margin({ top: 6 })
}
.width('100%')
.padding({ top: 14, bottom: 14 })
.borderRadius({ topLeft: 12, topRight: 12 })
.backgroundColor(COLORS.cardBgDark)
.alignItems(HorizontalAlign.Center)
Column() {
Text(h.name)
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
Text('@' + h.author + ' · ' + h.form)
.fontSize(10)
.fontColor(COLORS.textSecondary)
.margin({ top: 3 })
Row() {
Text('❤️ ' + formatLikes(h.likes))
.fontSize(10)
.fontColor(COLORS.zhuSha)
Text('¥' + h.price.toString())
.fontSize(10)
.fontColor(COLORS.daiQing)
.margin({ left: 10 })
}
.margin({ top: 6 })
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.padding(10)
}
.width('100%')
.borderRadius(12)
.backgroundColor(COLORS.cardBg)
.border({ width: 1, color: COLORS.border })
.margin({ left: 16, right: 6, top: 10 })
.onClick(() => {
this.selectedHanfu = h;
this.showDetailModal = true;
})
}, (h: HanfuItem) => 'l' + h.id.toString())
}
.layoutWeight(1)
Column() {
ForEach(rightHanfus(), (h: HanfuItem) => {
Column() {
Column() {
Text(h.cover)
.fontSize(42)
Text(h.dynasty)
.fontSize(10)
.fontColor(COLORS.white)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
.borderRadius(4)
.backgroundColor(dynastyColor(h.dynasty))
.margin({ top: 6 })
}
.width('100%')
.padding({ top: 14, bottom: 14 })
.borderRadius({ topLeft: 12, topRight: 12 })
.backgroundColor(COLORS.cardBgDark)
.alignItems(HorizontalAlign.Center)
Column() {
Text(h.name)
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
Text('@' + h.author + ' · ' + h.form)
.fontSize(10)
.fontColor(COLORS.textSecondary)
.margin({ top: 3 })
Row() {
Text('❤️ ' + formatLikes(h.likes))
.fontSize(10)
.fontColor(COLORS.zhuSha)
Text('¥' + h.price.toString())
.fontSize(10)
.fontColor(COLORS.daiQing)
.margin({ left: 10 })
}
.margin({ top: 6 })
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.padding(10)
}
.width('100%')
.borderRadius(12)
.backgroundColor(COLORS.cardBg)
.border({ width: 1, color: COLORS.border })
.margin({ left: 6, right: 16, top: 10 })
.onClick(() => {
this.selectedHanfu = h;
this.showDetailModal = true;
})
}, (h: HanfuItem) => 'r' + h.id.toString())
}
.layoutWeight(1)
}
.width('100%')
.alignItems(VerticalAlign.Top)
}
.width('100%')
}
// ---- 朝代:横滑大卡 + 人气柱状图 + 朝代列表 ----
@Builder
dynastyContent() {
Column() {
Scroll() {
Row() {
ForEach(DYNASTY_NAMES, (d: string, idx: number) => {
Column() {
Text(d)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.white)
Text('制式')
.fontSize(10)
.fontColor('#F2DFC5')
.margin({ top: 4 })
}
.width(96)
.height(110)
.justifyContent(FlexAlign.Center)
.borderRadius(14)
.linearGradient({
angle: 135,
colors: [[dynastyColor(d), 0], ['#8A2B22', 1]]
})
.margin({ left: idx === 0 ? 16 : 10, top: 12 })
.onClick(() => {
this.currentTopTab = 1;
})
}, (d: string) => 'dy' + d)
}
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%')
Column() {
Text('📊 朝代人气榜')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.daiQing)
.alignSelf(ItemAlign.Start)
Text('按同袍收藏量统计,唐制持续霸榜')
.fontSize(10)
.fontColor(COLORS.textSecondary)
.margin({ top: 4 })
.alignSelf(ItemAlign.Start)
Row() {
ForEach(DYNASTY_NAMES, (d: string, idx: number) => {
Column() {
Text(DYNASTY_HEAT[idx].toString())
.fontSize(10)
.fontColor(dynastyColor(d))
.margin({ bottom: 3 })
Column()
.width(22)
.height(barHeight(DYNASTY_HEAT[idx]))
.borderRadius({ topLeft: 4, topRight: 4 })
.backgroundColor(dynastyColor(d))
Text(d)
.fontSize(10)
.fontColor(COLORS.textSecondary)
.margin({ top: 4 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}, (d: string) => 'b' + d)
}
.width('100%')
.margin({ top: 12 })
}
.width('92%')
.borderRadius(14)
.backgroundColor(COLORS.cardBg)
.border({ width: 1, color: COLORS.border })
.padding(14)
.margin({ top: 14 })
Column() {
Text('🏛️ 分朝代看单品')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.daiQing)
ForEach(ALL_HANFUS, (h: HanfuItem) => {
Row() {
Text(h.dynasty)
.fontSize(11)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.white)
.width(34)
.height(24)
.textAlign(TextAlign.Center)
.borderRadius(6)
.backgroundColor(dynastyColor(h.dynasty))
Column() {
Text(h.name)
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
Text(h.form + ' · ' + h.intro)
.fontSize(10)
.fontColor(COLORS.textSecondary)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 10 })
Text('¥' + h.price.toString())
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.zhuSha)
}
.width('100%')
.padding({ top: 10, bottom: 10 })
.onClick(() => {
this.selectedHanfu = h;
this.showDetailModal = true;
})
Divider().color(COLORS.border)
}, (h: HanfuItem) => 'dl' + h.id.toString())
}
.width('92%')
.borderRadius(14)
.backgroundColor(COLORS.cardBg)
.border({ width: 1, color: COLORS.border })
.padding(14)
.margin({ top: 14 })
}
.width('100%')
}
// ---- 发型:教程步骤时间轴 ----
@Builder
hairstyleContent() {
Column() {
Text('💇 古典发型教程')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
.margin({ left: 16, top: 14 })
Text('跟着视频学,手残党也能盘出古风髻')
.fontSize(10)
.fontColor(COLORS.textSecondary)
.margin({ left: 16, top: 4 })
ForEach(HAIR_TUTS, (t: HairTutItem, idx: number) => {
Column() {
Row() {
Column() {
Text((idx + 1).toString())
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.white)
.width(30)
.height(30)
.textAlign(TextAlign.Center)
.borderRadius(15)
.backgroundColor(COLORS.daiQing)
}
.alignItems(HorizontalAlign.Center)
Column() {
Row() {
Text(t.name)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
Text(t.difficulty)
.fontSize(10)
.fontColor(COLORS.miaoJin)
.margin({ left: 10 })
}
Text(t.tool)
.fontSize(10)
.fontColor(COLORS.textSecondary)
.margin({ top: 3 })
Text(t.steps.toString() + ' 步完成 · 视频时长 ' + (t.steps * 2).toString() + ' 分钟')
.fontSize(10)
.fontColor(COLORS.textHint)
.margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 12 })
Text('学它')
.fontSize(11)
.fontColor(COLORS.white)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(12)
.backgroundColor(COLORS.zhuSha)
}
.width('100%')
.alignItems(VerticalAlign.Top)
if (idx < HAIR_TUTS.length - 1) {
Row() {
Column()
.width(2)
.height(18)
.backgroundColor(COLORS.border)
.margin({ left: 14 })
Column().layoutWeight(1)
}
.width('100%')
}
}
.width('92%')
.margin({ top: 6 })
}, (t: HairTutItem) => 'ht' + t.id.toString())
}
.width('100%')
}
// ---- 配饰:三列格子 ----
@Builder
accessoryContent() {
Column() {
Text('📿 配饰铺子')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
.margin({ left: 16, top: 14 })
Text('簪钗步摇 · 点翠缠花 · 平价古典都有')
.fontSize(10)
.fontColor(COLORS.textSecondary)
.margin({ left: 16, top: 4 })
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(ALL_ACCESSORIES, (a: AccessoryItem) => {
Column() {
Text(a.icon)
.fontSize(30)
.margin({ top: 12 })
Text(a.name)
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
.margin({ top: 6 })
Text('¥' + a.price.toString())
.fontSize(11)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.zhuSha)
.margin({ top: 4 })
Text(a.tag)
.fontSize(9)
.fontColor(COLORS.daiQing)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
.borderRadius(6)
.backgroundColor(tagBg(a.tag))
.margin({ top: 4, bottom: 12 })
}
.width('30%')
.borderRadius(12)
.backgroundColor(COLORS.cardBg)
.border({ width: 1, color: COLORS.border })
.alignItems(HorizontalAlign.Center)
.margin({ left: '1.6%', top: 10 })
.onClick(() => {
this.showRentModal = true;
})
}, (a: AccessoryItem) => 'ac' + a.id.toString())
}
.width('96%')
.margin({ top: 6 })
}
.width('100%')
}
// ---- 活动:日历风格 + 活动列表 ----
@Builder
eventContent() {
Column() {
Column() {
Text('🏮 八月活动日历')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.zhuSha)
.alignSelf(ItemAlign.Start)
Row() {
ForEach(['日', '一', '二', '三', '四', '五', '六'], (d: string) => {
Text(d)
.fontSize(10)
.fontColor(COLORS.textSecondary)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 8 })
}, (d: string) => 'w' + d)
}
.width('100%')
Flex({ wrap: FlexWrap.Wrap }) {
ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31], (d: number) => {
Column() {
Text(d.toString())
.fontSize(11)
.fontColor(d === 24 ? COLORS.white : COLORS.textPrimary)
if (d === 24) {
Text('🏮')
.fontSize(8)
}
}
.width('12.5%')
.padding({ top: 8, bottom: 8 })
.borderRadius(8)
.backgroundColor(d === 24 ? COLORS.zhuSha : 'transparent')
.margin({ top: 4 })
.alignItems(HorizontalAlign.Center)
}, (d: number) => 'day' + d.toString())
}
.width('100%')
Text('· 8月24日 中秋拜月雅集 ·')
.fontSize(10)
.fontColor(COLORS.zhuSha)
.margin({ top: 8 })
.alignSelf(ItemAlign.Center)
}
.width('92%')
.borderRadius(14)
.backgroundColor(COLORS.cardBg)
.border({ width: 1, color: COLORS.border })
.padding(14)
.margin({ top: 14 })
Text('🏮 全年同袍活动')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
.margin({ left: 16, top: 14 })
ForEach(ALL_EVENTS, (e: EventItem) => {
Row() {
Column() {
Text(e.date.split('-')[1])
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.zhuSha)
Text(e.date.split('-')[0] + '月')
.fontSize(9)
.fontColor(COLORS.textSecondary)
}
.alignItems(HorizontalAlign.Center)
.width(48)
Column() {
Row() {
Text(e.name)
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
Text(e.type)
.fontSize(9)
.fontColor(COLORS.white)
.padding({ left: 6, right: 6, top: 1, bottom: 1 })
.borderRadius(4)
.backgroundColor(COLORS.daiQing)
.margin({ left: 8 })
}
Text(e.city + ' · ' + e.joined.toString() + ' 人已报名')
.fontSize(10)
.fontColor(COLORS.textSecondary)
.margin({ top: 3 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 10 })
Text('报名')
.fontSize(11)
.fontColor(COLORS.white)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(12)
.backgroundColor(COLORS.zhuSha)
}
.width('92%')
.padding(12)
.borderRadius(12)
.backgroundColor(COLORS.cardBg)
.border({ width: 1, color: COLORS.border })
.margin({ top: 8 })
}, (e: EventItem) => 'ev' + e.id.toString())
}
.width('100%')
}
// ---- 形制:百科横条列表 ----
@Builder
formContent() {
Column() {
Text('📖 形制百科')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
.margin({ left: 16, top: 14 })
Text('认准正规形制,做有文化的袍子')
.fontSize(10)
.fontColor(COLORS.textSecondary)
.margin({ left: 16, top: 4 })
ForEach(FORM_INTROS, (f: FormIntroItem, idx: number) => {
Row() {
Column() {
Text(f.name)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.daiQing)
Text(f.desc)
.fontSize(10)
.fontColor(COLORS.textSecondary)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Column() {
Text(f.dynasty)
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.white)
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
.borderRadius(8)
.backgroundColor(dynastyColor(f.dynasty))
Text(f.feature)
.fontSize(9)
.fontColor(COLORS.miaoJin)
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Center)
}
.width('92%')
.padding(14)
.borderRadius(12)
.backgroundColor(idx % 2 === 0 ? COLORS.cardBg : COLORS.cardBgDark)
.border({ width: 1, color: COLORS.border })
.margin({ top: 8 })
.onClick(() => {
this.currentTopTab = 1;
})
}, (f: FormIntroItem) => 'fi' + f.name)
}
.width('100%')
}
// ================= 衣橱tab =================
@Builder
closetContent() {
Column() {
Row() {
Column() {
Text('👘 衣橱总览')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.white)
Text('汉服 6 · 配饰 12 · 总价值 ¥3,248')
.fontSize(10)
.fontColor('#F2DFC5')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Text('整理衣橱')
.fontSize(11)
.fontColor(COLORS.daiQing)
.padding({ left: 12, right: 12, top: 7, bottom: 7 })
.borderRadius(12)
.backgroundColor(COLORS.white)
.onClick(() => {
this.showRemoveModal = true;
})
}
.width('92%')
.padding(14)
.borderRadius(14)
.linearGradient({
angle: 135,
colors: [[COLORS.daiQing, 0], ['#143B38', 1]]
})
.margin({ top: 14 })
Text('挂在衣橱里的宝贝')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
.margin({ left: 16, top: 14 })
ForEach(MY_CLOSET, (c: ClosetItem) => {
Row() {
Text(c.dynasty)
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.white)
.width(36)
.height(36)
.textAlign(TextAlign.Center)
.borderRadius(10)
.backgroundColor(dynastyColor(c.dynasty))
Column() {
Text(c.name)
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
Row() {
Text(c.condition)
.fontSize(9)
.fontColor(COLORS.success)
.padding({ left: 6, right: 6, top: 1, bottom: 1 })
.borderRadius(4)
.backgroundColor('#E3EFEA')
Text('上次穿着 ' + c.lastWorn)
.fontSize(10)
.fontColor(COLORS.textSecondary)
.margin({ left: 8 })
}
.margin({ top: 3 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 10 })
Text('📤')
.fontSize(16)
.padding(8)
.onClick(() => {
this.showRemoveModal = true;
})
}
.width('92%')
.padding(12)
.borderRadius(12)
.backgroundColor(COLORS.cardBg)
.border({ width: 1, color: COLORS.border })
.margin({ top: 8 })
}, (c: ClosetItem) => 'cl' + c.id.toString())
}
.width('100%')
.padding({ bottom: 12 })
}
// ================= 集市tab =================
@Builder
marketContent() {
Column() {
Row() {
Text('🛍️ 今日集市')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
.layoutWeight(1)
Text('共 86 件')
.fontSize(11)
.fontColor(COLORS.textSecondary)
}
.width('92%')
.margin({ top: 14 })
ForEach(MARKET_ITEMS, (m: MarketItem) => {
Row() {
Text(m.cover)
.fontSize(26)
.width(52)
.height(52)
.textAlign(TextAlign.Center)
.borderRadius(12)
.backgroundColor(COLORS.cardBgDark)
Column() {
Text(m.name)
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
Row() {
Text(m.mode)
.fontSize(9)
.fontColor(COLORS.white)
.padding({ left: 6, right: 6, top: 1, bottom: 1 })
.borderRadius(4)
.backgroundColor(marketColor(m.mode))
Text('@' + m.seller)
.fontSize(10)
.fontColor(COLORS.textSecondary)
.margin({ left: 8 })
}
.margin({ top: 3 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 12 })
Column() {
Text(m.mode === '日租' ? '¥' + m.price.toString() + '/天' : '¥' + m.price.toString())
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.zhuSha)
Text('聊一聊')
.fontSize(10)
.fontColor(COLORS.white)
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
.borderRadius(10)
.backgroundColor(COLORS.daiQing)
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.End)
}
.width('92%')
.padding(12)
.borderRadius(12)
.backgroundColor(COLORS.cardBg)
.border({ width: 1, color: COLORS.border })
.margin({ top: 8 })
.onClick(() => {
this.showRentModal = true;
})
}, (m: MarketItem) => 'mk' + m.id.toString())
}
.width('100%')
.padding({ bottom: 12 })
}
// ================= 我的tab =================
@Builder
mineContent() {
Column() {
Row() {
Text('🍂')
.fontSize(28)
.width(64)
.height(64)
.textAlign(TextAlign.Center)
.borderRadius(32)
.backgroundColor(COLORS.cardBgDark)
Column() {
Text('临安')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
Text('袍子 LV.6 · 衣龄 3 年 · 常穿宋制')
.fontSize(11)
.fontColor(COLORS.textSecondary)
.margin({ top: 4 })
Text('心愿单:大袖衫·惊鸿')
.fontSize(10)
.fontColor(COLORS.miaoJin)
.margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 12 })
Column() {
Text('发布')
.fontSize(11)
.fontColor(COLORS.white)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(12)
.backgroundColor(COLORS.zhuSha)
.onClick(() => {
this.showPostModal = true;
})
}
}
.width('92%')
.padding(16)
.borderRadius(16)
.backgroundColor(COLORS.cardBg)
.border({ width: 1, color: COLORS.border })
.margin({ top: 16 })
Row() {
Column() {
Text('48')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.zhuSha)
Text('穿搭发布')
.fontSize(10)
.fontColor(COLORS.textSecondary)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('1.2k')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.zhuSha)
Text('获赞')
.fontSize(10)
.fontColor(COLORS.textSecondary)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('6')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.daiQing)
Text('活动参与')
.fontSize(10)
.fontColor(COLORS.textSecondary)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('86')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.daiQing)
Text('同袍关注')
.fontSize(10)
.fontColor(COLORS.textSecondary)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}
.width('92%')
.padding({ top: 14, bottom: 14 })
.borderRadius(14)
.backgroundColor(COLORS.cardBg)
.border({ width: 1, color: COLORS.border })
.margin({ top: 12 })
Column() {
Text('📜 我的活动足迹')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
ForEach(['花朝节汉服雅集 · 杭州', '西塘汉服文化周 · 嘉兴', '上巳节曲水流觞 · 武汉', '洛阳牡丹汉服节 · 洛阳'], (a: string) => {
Row() {
Text('🏮')
.fontSize(14)
Text(a)
.fontSize(11)
.fontColor(COLORS.textPrimary)
.margin({ left: 8 })
.layoutWeight(1)
Text('已参加')
.fontSize(9)
.fontColor(COLORS.success)
}
.width('100%')
.padding({ top: 10, bottom: 10 })
Divider().color(COLORS.border)
}, (a: string) => 'af' + a)
}
.width('92%')
.borderRadius(14)
.backgroundColor(COLORS.cardBg)
.border({ width: 1, color: COLORS.border })
.padding(14)
.margin({ top: 12 })
}
.width('100%')
.padding({ bottom: 12 })
}
// ================= 底部tab =================
@Builder
bottomTabs() {
Row() {
ForEach(BOTTOM_TABS, (t: TabItem, idx: number) => {
Column() {
Text(t.icon)
.fontSize(20)
.opacity(this.currentBottomTab === idx ? 1 : 0.5)
Text(t.label)
.fontSize(10)
.fontColor(this.currentBottomTab === idx ? COLORS.zhuSha : COLORS.textHint)
.margin({ top: 3 })
}
.layoutWeight(1)
.padding({ top: 8, bottom: 8 })
.onClick(() => {
this.currentBottomTab = idx;
this.currentTopTab = 0;
})
}, (t: TabItem) => t.label)
}
.width('100%')
.backgroundColor(COLORS.white)
.border({ width: 1, color: COLORS.border })
}
// ========== 弹框1:发布穿搭(朱砂渐变头) ==========
@Builder
postOutfitModal() {
Column() {
Column() {
Column() {
Text('📷 发布穿搭')
.fontSize(17)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.white)
Text('记录今日份的古风美丽')
.fontSize(11)
.fontColor('#F2DFC5')
.margin({ top: 4 })
}
.width('100%')
.padding(16)
.alignItems(HorizontalAlign.Start)
.linearGradient({
angle: 135,
colors: [[COLORS.zhuSha, 0], ['#8A2B22', 1]]
})
Scroll() {
Column() {
Text('穿搭描述')
.fontSize(13)
.fontColor(COLORS.textSecondary)
.margin({ top: 14 })
TextInput({ placeholder: '例如:今日份宋制通勤,月白褙子配远山百迭裙', text: this.postDesc })
.fontSize(13)
.fontColor(COLORS.textPrimary)
.placeholderColor(COLORS.textHint)
.backgroundColor(COLORS.cardBgDark)
.borderRadius(10)
.padding({ left: 12, right: 12 })
.height(42)
.margin({ top: 6 })
.onChange((v: string) => {
this.postDesc = v;
})
Text('朝代制式')
.fontSize(13)
.fontColor(COLORS.textSecondary)
.margin({ top: 14 })
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(DYNASTY_NAMES, (d: string, idx: number) => {
Text(d)
.fontSize(12)
.fontColor(this.postDynasty === idx ? COLORS.white : COLORS.textSecondary)
.padding({ left: 16, right: 16, top: 7, bottom: 7 })
.borderRadius(16)
.backgroundColor(this.postDynasty === idx ? dynastyColor(d) : COLORS.cardBgDark)
.margin({ right: 8, top: 6 })
.onClick(() => {
this.postDynasty = idx;
})
}, (d: string) => 'pd' + d)
}
.width('100%')
Text('穿着场合')
.fontSize(13)
.fontColor(COLORS.textSecondary)
.margin({ top: 14 })
Row() {
ForEach(['日常通勤', '节日雅集', '旅行拍照'], (s: string, idx: number) => {
Column() {
Text(s)
.fontSize(12)
.fontColor(this.postScene === idx ? COLORS.white : COLORS.textSecondary)
}
.layoutWeight(1)
.padding({ top: 9, bottom: 9 })
.borderRadius(10)
.backgroundColor(this.postScene === idx ? COLORS.daiQing : 'transparent')
.border({ width: 1, color: this.postScene === idx ? COLORS.daiQing : COLORS.border })
.margin({ left: idx === 0 ? 0 : 6 })
.onClick(() => {
this.postScene = idx;
})
}, (s: string) => 'ps' + s)
}
.width('100%')
Row() {
Text('存草稿')
.fontSize(13)
.fontColor(COLORS.textSecondary)
.padding({ left: 18, right: 18, top: 10, bottom: 10 })
.borderRadius(18)
.border({ width: 1, color: COLORS.border })
.onClick(() => {
this.showPostModal = false;
})
Text('🏮 发布')
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.white)
.padding({ left: 24, right: 24, top: 10, bottom: 10 })
.borderRadius(18)
.backgroundColor(COLORS.zhuSha)
.margin({ left: 10 })
.onClick(() => {
this.showPostModal = false;
})
}
.margin({ top: 20, bottom: 16 })
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.padding({ left: 18, right: 18 })
}
.scrollable(ScrollDirection.Vertical)
.constraintSize({ maxHeight: '58%' })
}
.width('86%')
.borderRadius(16)
.backgroundColor(COLORS.white)
.clip(true)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('99000000')
.onClick(() => {
this.showPostModal = false;
})
}
// ========== 弹框2:出租汉服(描金编辑风) ==========
@Builder
rentOutModal() {
Column() {
Column() {
Column() {
Text('🧧 出租我的汉服')
.fontSize(17)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.white)
Text('闲置生灰,不如租给同袍穿')
.fontSize(11)
.fontColor('#F2E3CE')
.margin({ top: 4 })
}
.width('100%')
.padding(16)
.alignItems(HorizontalAlign.Start)
.linearGradient({
angle: 135,
colors: [[COLORS.miaoJin, 0], ['#A87E4F', 1]]
})
Scroll() {
Column() {
Text('日租金(元/天)')
.fontSize(13)
.fontColor(COLORS.textSecondary)
.margin({ top: 14 })
TextInput({ placeholder: '例如:35', text: this.rentPrice })
.fontSize(13)
.fontColor(COLORS.textPrimary)
.placeholderColor(COLORS.textHint)
.backgroundColor(COLORS.cardBgDark)
.borderRadius(10)
.padding({ left: 12, right: 12 })
.height(42)
.margin({ top: 6 })
.onChange((v: string) => {
this.rentPrice = v;
})
.type(InputType.Number)
Text('押金档位')
.fontSize(13)
.fontColor(COLORS.textSecondary)
.margin({ top: 14 })
Row() {
ForEach(['¥100', '¥200', '¥300'], (d: string, idx: number) => {
Text(d)
.fontSize(12)
.fontColor(this.rentDeposit === idx ? COLORS.white : COLORS.textSecondary)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.padding({ top: 8, bottom: 8 })
.borderRadius(10)
.backgroundColor(this.rentDeposit === idx ? COLORS.miaoJin : 'transparent')
.border({ width: 1, color: this.rentDeposit === idx ? COLORS.miaoJin : COLORS.border })
.margin({ left: idx === 0 ? 0 : 6 })
.onClick(() => {
this.rentDeposit = idx;
})
}, (d: string) => 'rd' + d)
}
.width('100%')
Text('最长可租:' + this.rentDays.toString() + ' 天')
.fontSize(13)
.fontColor(COLORS.textSecondary)
.margin({ top: 16 })
Row() {
Text('−')
.fontSize(20)
.fontColor(COLORS.daiQing)
.width(38)
.height(38)
.textAlign(TextAlign.Center)
.borderRadius(19)
.backgroundColor(COLORS.cardBgDark)
.onClick(() => {
if (this.rentDays > 1) {
this.rentDays = this.rentDays - 1;
}
})
Text(this.rentDays.toString())
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
.layoutWeight(1)
.textAlign(TextAlign.Center)
Text('+')
.fontSize(20)
.fontColor(COLORS.daiQing)
.width(38)
.height(38)
.textAlign(TextAlign.Center)
.borderRadius(19)
.backgroundColor(COLORS.cardBgDark)
.onClick(() => {
if (this.rentDays < 15) {
this.rentDays = this.rentDays + 1;
}
})
}
.width('100%')
.margin({ top: 8 })
Text('清洗说明')
.fontSize(13)
.fontColor(COLORS.textSecondary)
.margin({ top: 14 })
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(['手洗', '不可机洗', '阴干', '熨烫低温'], (w: string) => {
Text(w)
.fontSize(11)
.fontColor(COLORS.daiQing)
.padding({ left: 10, right: 10, top: 6, bottom: 6 })
.borderRadius(10)
.backgroundColor('#E3EFEA')
.margin({ right: 8, top: 6 })
}, (w: string) => 'rw' + w)
}
.width('100%')
Row() {
Text('取消')
.fontSize(13)
.fontColor(COLORS.textSecondary)
.padding({ left: 20, right: 20, top: 10, bottom: 10 })
.borderRadius(18)
.border({ width: 1, color: COLORS.border })
.onClick(() => {
this.showRentModal = false;
})
Text('🧧 上架集市')
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.white)
.padding({ left: 22, right: 22, top: 10, bottom: 10 })
.borderRadius(18)
.backgroundColor(COLORS.miaoJin)
.margin({ left: 10 })
.onClick(() => {
this.showRentModal = false;
})
}
.margin({ top: 20, bottom: 16 })
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.padding({ left: 18, right: 18 })
}
.scrollable(ScrollDirection.Vertical)
.constraintSize({ maxHeight: '58%' })
}
.width('86%')
.borderRadius(16)
.backgroundColor(COLORS.white)
.clip(true)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('99000000')
.onClick(() => {
this.showRentModal = false;
})
}
// ========== 弹框3:移出衣橱(删除小警告卡) ==========
@Builder
removeClosetModal() {
Column() {
Column() {
Text('📤')
.fontSize(40)
Text('把这件汉服移出衣橱?')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
.margin({ top: 10 })
Text('移出后将从衣橱列表删除,购买记录仍会保留')
.fontSize(11)
.fontColor(COLORS.textSecondary)
.textAlign(TextAlign.Center)
.margin({ top: 8, left: 20, right: 20 })
Row() {
Text('再想想')
.fontSize(13)
.fontColor(COLORS.textSecondary)
.padding({ left: 20, right: 20, top: 10, bottom: 10 })
.borderRadius(18)
.border({ width: 1, color: COLORS.border })
.onClick(() => {
this.showRemoveModal = false;
})
Text('移出衣橱')
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.white)
.padding({ left: 20, right: 20, top: 10, bottom: 10 })
.borderRadius(18)
.backgroundColor(COLORS.danger)
.margin({ left: 10 })
.onClick(() => {
this.showRemoveModal = false;
})
}
.margin({ top: 20, bottom: 18 })
}
.width('78%')
.borderRadius(16)
.backgroundColor(COLORS.white)
.alignItems(HorizontalAlign.Center)
.padding({ top: 24 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('99000000')
.onClick(() => {
this.showRemoveModal = false;
})
}
// ========== 弹框4:穿搭详情(单品+评论) ==========
@Builder
outfitDetailModal() {
Column() {
Column() {
Column() {
Row() {
Column() {
Text('👗 穿搭详情')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.white)
Text(this.selectedHanfu!.name + ' · @' + this.selectedHanfu!.author)
.fontSize(11)
.fontColor('#F2DFC5')
.margin({ top: 3 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Text('✕')
.fontSize(18)
.fontColor(COLORS.white)
.padding(8)
.onClick(() => {
this.showDetailModal = false;
})
}
.width('100%')
}
.width('100%')
.padding(16)
.linearGradient({
angle: 135,
colors: [[COLORS.zhuSha, 0], ['#8A2B22', 1]]
})
Scroll() {
Column() {
Row() {
Text(this.selectedHanfu!.cover)
.fontSize(48)
.width(84)
.height(84)
.textAlign(TextAlign.Center)
.borderRadius(14)
.backgroundColor(COLORS.cardBgDark)
Column() {
Text(this.selectedHanfu!.dynasty + '制 · ' + this.selectedHanfu!.form)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.daiQing)
Text('❤️ ' + formatLikes(this.selectedHanfu!.likes) + ' 人喜欢')
.fontSize(11)
.fontColor(COLORS.zhuSha)
.margin({ top: 4 })
Text('参考价 ¥' + this.selectedHanfu!.price.toString())
.fontSize(11)
.fontColor(COLORS.textSecondary)
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 12 })
}
.width('100%')
.padding({ left: 16, right: 16, top: 14 })
Text(this.selectedHanfu!.intro)
.fontSize(12)
.fontColor(COLORS.textSecondary)
.margin({ top: 12, left: 16, right: 16 })
Text('🧵 单品清单')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.daiQing)
.margin({ top: 16, left: 16 })
ForEach(OUTFIT_PIECES, (p: SingleItem) => {
Row() {
Text(p.name)
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
.width(52)
Text(p.detail)
.fontSize(11)
.fontColor(COLORS.textSecondary)
.layoutWeight(1)
}
.width('100%')
.padding({ top: 8, bottom: 8, left: 16, right: 16 })
Divider().color(COLORS.border)
}, (p: SingleItem) => 'op' + p.name)
Text('💬 同袍评论')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.daiQing)
.margin({ top: 14, left: 16 })
ForEach(OUTFIT_COMMENTS, (c: CommentItem) => {
Column() {
Row() {
Text(c.avatar)
.fontSize(20)
Text(c.name)
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textPrimary)
.margin({ left: 8 })
}
.width('100%')
Text(c.content)
.fontSize(11)
.fontColor(COLORS.textSecondary)
.margin({ top: 6 })
}
.width('100%')
.padding(12)
.borderRadius(12)
.backgroundColor(COLORS.cardBgDark)
.margin({ top: 8, left: 16, right: 16 })
}, (c: CommentItem) => 'oc' + c.name)
Row() {
Text('❤️ 喜欢')
.fontSize(13)
.fontColor(COLORS.zhuSha)
.padding({ left: 20, right: 20, top: 10, bottom: 10 })
.borderRadius(18)
.border({ width: 1, color: COLORS.zhuSha })
Text('去购买')
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.white)
.padding({ left: 20, right: 20, top: 10, bottom: 10 })
.borderRadius(18)
.backgroundColor(COLORS.zhuSha)
.margin({ left: 10 })
.onClick(() => {
this.showDetailModal = false;
})
}
.margin({ top: 16, bottom: 18, left: 16 })
}
.width('100%')
.alignItems(HorizontalAlign.Start)
}
.scrollable(ScrollDirection.Vertical)
.constraintSize({ maxHeight: '58%' })
}
.width('88%')
.borderRadius(16)
.backgroundColor(COLORS.white)
.clip(true)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('99000000')
.onClick(() => {
this.showDetailModal = false;
})
}
}
开发环境搭建
安装 DevEco Studio
DevEco Studio 是 HarmonyOS 应用开发的官方 IDE。下载安装包后双击运行,进入安装欢迎界面,点击「下一步」继续。

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

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

选择项目模板
新建一个空白模板,在模板市场中选择 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实现了逻辑的模块化拆分。应用采用"底部4Tab+顶部6Tab"的双层导航架构——底部Tab切换大模块(首页/衣橱/集市/我的),顶部Tab在首页模块内细分分类(推荐/朝代/发型/配饰/活动/形制),既保证了模块间的快速切换,又实现了模块内的精细分类,是内容型应用的标准导航架构。与之前狼人杀应用的双层导航相比,本应用的顶部6Tab更贴合汉服文化社区的内容维度(推荐/朝代/发型/配饰/活动/形制),每个分类的布局差异更大。
在数据建模上,应用定义了六类@Observed数据模型和三类接口模型——HanfuItem汉服实体(9字段15件,涵盖名字/朝代/形制/封面/价格/点赞/作者/介绍,是字段最多的实体)、HairTutItem发型教程实体(5字段6个,涵盖名字/难度/步数/工具)、AccessoryItem配饰实体(5字段12个,涵盖名字/图标/价格/标签)、EventItem活动实体(6字段8个,涵盖名字/城市/日期/类型/报名人数)、ClosetItem衣橱实体(5字段6件,涵盖名字/朝代/上次穿着/成色)、MarketItem集市实体(6字段8件,涵盖名字/卖家/价格/模式/封面),以及FormIntroItem形制介绍接口(4字段8个)、CommentItem评论接口(3字段3条)、SingleItem单品接口(2字段4件)。每个@Observed类都通过constructor初始化,@Observed装饰整个类使得属性变更可被框架自动监听。形制/评论/单品用接口类型而非@Observed类,因为是静态配置数据不需要响应式更新,这种"6类@Observed动态数据+3类接口静态配置"的混合数据模式既保证了动态数据的响应式更新,又避免了静态配置被不必要地监听,是目前所有应用中数据模型最丰富的(9类数据模型)。15件汉服覆盖唐/宋/明/魏晋/汉五个朝代和15种形制,12个配饰覆盖六大类,8个活动覆盖全年12个月和七种类型,数据丰富度极高。
在工具函数层面,应用定义了9个工具函数——dynastyColor/tagBg/marketColor三个颜色映射函数实现了"数据即颜色"的动态着色,formatLikes实现点赞数的千进制格式化,barHeight实现"人气即柱高"的可视化,leftHanfus/rightHanfus实现双列布局的数据准备,buildParticles/driftParticles实现自定义动画系统。dynastyColor函数是整个应用的核心工具函数——将5个朝代映射为5种专属颜色(唐zhuSha朱砂红/宋daiQing黛青/明miaoJin描金/魏晋#6B5B95紫/汉#4A6FA5蓝),每个朝代的代表色都有其文化依据(唐红/宋青/明金/魏晋紫/汉蓝),贯穿推荐/朝代/形制/衣橱四个分类,横滑大卡/柱状图/单品列表/朝代标签都用这个函数着色,形成了"朝代即颜色"的统一视觉语言,是本应用最具特色的色彩设计。tagBg函数将配饰标签(热卖/新品/古典/平价)映射为4种背景色,marketColor函数将集市模式(日租/转卖)映射为2种颜色,三个颜色映射函数共同实现了多维度动态着色。barHeight的倍率1.3比狼人杀的2.2更小因为人气数值范围更大(60-92),formatLikes的千进制格式化是社交应用的标准数据格式化,leftHanfus/rightHanfus用奇偶索引拆列是双列布局的标准数据准备。
在动画设计上,应用独创了花瓣灯笼粒子动画系统——这是本应用最具技术深度和创意的部分。16个粒子(图标为🌸樱花/❀花朵/🏮灯笼/✨闪光/🌺扶桑五种汉服古风主题道具循环),通过setInterval每130毫秒调用driftParticles()更新粒子位置(y向上减3,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清除定时器防止内存泄漏。与狼人杀应用的粒子系统相比,本应用的粒子图标更贴合汉服古风主题(🌸❀🏮✨🌺 vs 🌙⭐✦🐺✧),漂移速度相同(y-3),营造出"花瓣飘落、灯笼摇曳、闪光点缀"的古风氛围。这种"setInterval手动驱动+@State响应式+ForEach重新渲染+position绝对定位+动态key强制刷新"的方案,实现了ArkUI中animation属性无法完成的自定义粒子动画。质数41/67步长取模保证粒子初始位置均匀分布,sin函数保证粒子摆动轨迹自然不机械,动态key保证每帧强制刷新,三个技巧共同实现了流畅自然的自定义粒子动画。这是ArkUI中实现复杂自定义动画的标准且可靠的方案,对鸿蒙生态下的动画开发具有很高的参考价值。
在交互设计上,应用通过4个模态弹窗实现了增删改查交互闭环——postOutfitModal发布穿搭(新增,朱砂渐变头zhuSha→#8A2B22,穿搭描述TextInput/朝代制式Flex 5个dynastyColor选择/穿着场合Row三选一)、rentOutModal出租汉服(编辑,描金渐变头miaoJin→#A87E4F,日租金TextInput type Number/押金档位Row三档/最长可租加减器1-15天/清洗说明Flex四个标签,是表单交互最丰富的弹窗)、removeClosetModal移出衣橱(删除,width78%警告小卡,danger红底确认,"移出后将从衣橱列表删除,购买记录仍会保留"明确告知删除后果消除顾虑)、outfitDetailModal穿搭详情(查询,朱砂渐变头,cover+朝代形制+点赞+参考价/intro介绍/单品清单4件/同袍评论3条/喜欢描边按钮+购买实心按钮)。四个弹窗各自独立@Builder,通过show*布尔状态条件渲染,外层统一用Column+backgroundColor '99000000’实现遮罩+居中。四个弹窗覆盖了汉服文化社区的核心交互——发布穿搭(新增)、出租汉服(编辑)、移出衣橱(删除)、穿搭详情(查询),形成了完整的增删改查交互闭环。发布弹窗用朱砂红主题(内容创作)、出租弹窗用描金主题(交易)、移出弹窗用danger红(删除警告)、详情弹窗用朱砂红主题(内容查询),四个弹窗用不同主题色区分交互类型,是模态弹窗交互设计的优秀实践。出租弹窗的日租金TextInput用type(InputType.Number)限制数字输入,最长可租加减器1-15天,清洗说明四个标签,是表单交互最丰富的弹窗。移出弹窗的"购买记录仍会保留"提示消除了用户删除顾虑,是删除确认的标准文案设计。
在视觉设计上,"朱砂红+黛青+描金·古风宣纸"的配色体系以interface类型约束、const常量承载,zhuSha #B03A2E朱砂红定主(汉服朱红经典色,古代朱砂是珍贵颜料)、zhuShaLight #D96A5C浅朱砂定渐变、daiQing #1F5F5B黛青定辅(汉服黛青经典色,古代黛是青黑色颜料)、daiQingLight #4A8A86浅黛青定渐变、miaoJin #D4A373描金定点缀(汉服金线描边,古代服饰常用金线)、bg #F7F3EA宣纸米白定底(比纯白稍黄模拟宣纸颜色)。主色体系采用"两级朱砂+两级黛青+描金+宣纸米白"六色组合——朱砂红定朱红、黛青定青黑、描金定华丽、宣纸米白定底,四色各司其职形成"以朱砂定朱红、以黛青定青黑、以描金定华丽、以宣纸定古韵"的汉服语义着色体系。textPrimary #3A2E28深棕(非纯黑带棕调)是文字色的特色——用带棕调的深棕代替纯黑,更符合古风宣纸"墨色偏棕"的视觉氛围。bg #F7F3EA宣纸米白是背景色的特色——模拟宣纸的颜色,营造古风宣纸的质感。6大分类差异化布局(推荐=双列瀑布穿搭卡片/朝代=横滑朝代大卡+人气柱状图+分朝代单品列表/发型=教程步骤时间轴/配饰=三列格子铺子/活动=日历风格+全年活动列表/形制=百科横条列表奇偶交替背景),每个分类都有完全不同的布局风格和视觉氛围,最大程度还原了汉服文化社区的不同内容维度。dynastyColor朝代颜色映射贯穿四个分类,形成"朝代即颜色"的统一视觉语言。衣橱Tab用黛青主题(与首页朱砂主题区分,营造沉稳管理氛围),集市Tab用日租转卖双模式marketColor颜色区分,我的Tab用白底简洁风格+朱砂黛青双色统计,四个底部Tab各有其主题色和布局风格,整体视觉既统一又多样。
总体而言,该应用在有限的代码篇幅内完整呈现了HarmonyOS ArkTS API 24声明式UI范式的核心能力——六类@Observed数据模型+三类接口静态配置(目前所有应用中数据模型最丰富)、9个工具函数数据可视化(dynastyColor朝代颜色映射为最具特色)、花瓣灯笼粒子动画系统(setInterval驱动自定义动画,汉服古风主题图标)、双层导航架构(底部4Tab+顶部6Tab内容分类)、6大分类差异化布局(双列瀑布/横滑大卡+柱状图/时间轴/三列格子/日历+列表/百科横条)、4个模态弹窗增删改查闭环(发布朱砂/出租描金/移出danger/详情朱砂,出租弹窗表单最丰富)、aboutToAppear/aboutToDisappear生命周期管理定时器,是一份兼具技术深度与古风韵味的优秀实践范例,对鸿蒙生态下的声明式应用开发和自定义动画实现具有很高的参考价值与学习意义。岂曰无衣?与子同袍——愿这款QQ汉服·同袍社应用,能让更多人在代码中感受到汉服朱红黛青描金古韵的衣冠之美与同袍之情。

标签: HarmonyOS · ArkTS · API 24 · 汉服 · 朱砂红黛青 · 古风宣纸 · 粒子动画 · setInterval · 双层导航 · 同袍社 · 移动端开发
更多推荐



所有评论(0)