#Bottom组件开发规范##概述本文档规范了IP互动项目中Bottom组件的设计模式、实现方式和最佳实践。Bottom组件作为主舞台的底部操作区域,承载着核心功能入口、积分显示、主要交互等关键作用,是用户参与游戏的主要接触点。##设计原则###1.功能聚焦-承载项目的核心操作功能(如抽奖、开盒、任务等)-提供重要信息展示(如积分、奖励状态等)-确保主要功能的易访问性和可见性###2.布局一致性-遵循固定的底部位置布局模式-🎯**主操作按钮必须始终绝对居中**,不受左右功能区域内容变化影响-保持合理的元素间距和视觉层级-适配不同屏幕尺寸和安全区域###3.交互友好-提供清晰的按钮状态反馈-支持动画效果和视觉引导-合理处理加载和禁用状态##组件结构###基础布局模式Bottom组件通常采用以下基础布局结构:```tsxinterfaceBottomProps{className?:string;mainStore:MainStore;//核心功能回调onMainActionBtnClick?
)=>
romise<boolean>;onMainActionBtnSuccess?
ret:any)=>Promise<void>;onMainActionBtnError?
error:any)=>Promise<void>;//状态控制isProcessingLockRef?:React.MutableRefObject<boolean>;loading?:boolean;//功能开关showPointDisplay?:boolean;showLotteryTrigger?:boolean;showTaskTrigger?:boolean;//动画控制palyBoxOpenTriggerClickAnimation?:boolean;showActionMotivateTipsBubble?:boolean;}exportfunctionBottom(props:BottomProps){return(<divclassName={`w-fullabsolutebottom-8${props.className}`}>{/*积分显示区域-居中上部*/}{props.showPointDisplay&&(<divclassName="w-fullflexjustify-centermb-4">{/*积分显示组件*/}</div>)}{/*功能按钮区域*/}<divclassName="w-fullbox-borderrelativeflexitems-centerpx-7">{/*左侧功能区*/}<divclassName="flexitems-center">{/*抽奖/盲盒等功能按钮*/}</div>{/*中央主操作区-绝对居中*/}<divclassName="absoluteleft-1/2top-1/2transform-translate-x-1/2-translate-y-1/2">{/*主要操作按钮(开盒、任务等)-始终居中显示*/}</div>{/*右侧功能区*/}<divclassName="flexitems-centerml-auto">{/*其他功能按钮*/}</div></div></div>);}```##核心功能区域###1.容器布局规范####**标准容器样式**```tsx//基础容器-固定底部位置<divclassName={`w-fullabsolutebottom-8${props.className}`}>{/*内容区域-水平布局*/}<divclassName="w-fullbox-borderflexitems-centerjustify-betweenpx-7">{/*功能区域分布*/}</div></div>```####**布局模式变体**```tsx//居中布局<divclassName="w-fullbox-borderflexitems-centerjustify-centerpx-7">//两端分布<divclassName="w-fullbox-borderflexitems-endjustify-betweenpx-7">//简单居中布局<divclassName="flexjustify-centeritems-centerp-4space-x-4">```**标准规范**:-**容器定位**:`absolutebottom-8`距离底部32px-**水平内边距**:`px-7`(28px)确保按钮不贴边-**Flexbox布局**:使用`flex`进行水平分布-**对齐方式**:`items-center`或`items-end`根据设计需求-**Z-index**:通过外部className控制,通常为`z-[20]`###2.左侧功能区域主要承载次要功能入口,如抽奖、盲盒等。####**抽奖功能按钮**```tsx{/*抽奖按钮-使用统一的LotteryTrigger组件*/}<LotteryTriggermainStageStore={props.mainStore}onPopClose={()=>{//抽奖弹窗关闭后的处理逻辑endCurrentGuideStep?.();}}renderContent={()=>(<ExposureTrackeronExposure={()=>{}}><divclassName="flex-colflexitems-centerjustify-center"onClick={()=>{}}><imgdata-id="mainstage-bottom-lottery-btn"src="[抽奖按钮图片URL]"className={`w-[80px]h-[105px]${props.showLotteryTrigger?'opacity-100':'opacity-0'}`}alt="抽奖"/></div></ExposureTracker>)}/>```####**盲盒功能按钮**```tsx{/*盲盒按钮-条件显示*/}{isBlindBoxAvailable&&(<BlindBoxPopTriggerlotteryParam={lotteryParam}className="flex-colflexitems-centerjustify-center"onClick={()=>{}}renderContent={()=>(<imgdata-id="mainstage-bottom-blindbox-btn"src="[盲盒按钮图片URL]"className="w-[104px]h-[104px]"alt="盲盒"/>)}/>)}```**功能区规范**:-**统一使用Shared组件**:`LotteryTrigger`、`BlindBoxPopTrigger`-**条件显示**:使用props控制功能按钮的显示/隐藏-**埋点集成**:必须包含曝光和点击埋点-**状态动画**:支持opacity过渡效果-**图标尺寸**:通常为80-104px正方形或特定比例###3.中央主操作区域承载项目的核心交互功能,通常是最重要的操作按钮。####**主操作按钮(开盒类)**```tsx{/*主操作按钮-核心功能*/}<divclassName="flex-1flexjustify-center"><BoxOpenTriggermainStageStore={props.mainStore}isProcessingLockRef={props.isProcessingLockRef}loading={props.loading}palyBoxOpenTriggerClickAnimation={props.palyBoxOpenTriggerClickAnimation}onMainActionBtnClick={props.onMainActionBtnClick}onMainActionBtnSuccess={props.onMainActionBtnSuccess}onMainActionBtnError={props.onMainActionBtnError}renderContent={renderMainActionBtnContent}showActionMotivateTipsBubble={props.showActionMotivateTipsBubble}/></div>```####**主操作按钮内容渲染**```tsxconstrenderMainActionBtnContent=()=>(<imgdata-id="mainstage-bottom-main-action-btn"src="[主操作按钮图片URL]"className="w-[200px]h-[80px]"alt="主要操作"/>);```####**简化版主操作(如刷新按钮)**```tsx{/*简单操作按钮*/}<buttondata-id="bottom-refresh-btn"onClick={handleRefreshData}className="bg-gray-700hover:bg-gray-600text-whitep-2rounded-lgborderborder-gray-500transformtransition-allduration-200hover:scale-105active:scale-95"disabled={!isInitialized}title="刷新数据"><spanclassName="text-lg">🔄</span></button>```**主操作区规范**:-**居中布局**:使用`flex-1flexjustify-center`确保居中-**统一组件**:优先使用`BoxOpenTrigger`等shared组件-**状态管理**:支持loading、disabled等状态-**回调处理**:提供完整的成功、失败回调-**动画支持**:集成点击动画和状态过渡###4.积分显示区域(上部居中)积分显示应放置在按钮区域上方,居中显示,作为重要信息的突出展示。####**标准积分显示组件(推荐)**```tsx{/*积分显示区域-上部居中*/}{props.showPointDisplay&&(<divclassName="w-fullflexjustify-centermb-4"><PointsDisplaymainStageStore={props.mainStore}onPointsClick={()=>{}}/></div>)}```####**自定义积分显示(仅在特殊需求时使用)**```tsx{/*自定义积分显示-上部居中布局*/}{props.showPointDisplay&&(<divclassName="w-fullflexjustify-centermb-4"><divdata-id="bottom-points-display"className="bg-black/70px-6py-3rounded-lgborderborder-yellow-400min-w-[140px]cursor-pointertransformtransition-allduration-200hover:scale-105"onClick={handlePointsClick}><divclassName="text-yellow-300text-center"><divclassName="text-xs">积分</div><divclassName="text-lgfont-bold">{pointsAmount||0}</div></div></div></div>)}```>**⚠️重要提醒**:优先使用`PointsDisplay`组件,它提供了统一的样式、交互逻辑和埋点功能。仅在有特殊定制需求时才考虑自定义实现。###5.右侧功能区域承载次要功能按钮,如任务、设置等。##核心组件集成###必需组件导入Bottom组件需要导入以下共享组件:```tsximport{PointsDisplay}from'@/ace/shared/component/pointsDisplay';import{BoxOpenTrigger}from'@/ace/shared/component/boxOpenTrigger';import{BlindBoxPopTrigger}from'@/ace/shared/component/blindBoxPopTrigger';import{LotteryTrigger}from'@/ace/shared/component/lotteryTrigger';import{TaskPopTrigger}from'@/ace/shared/component/taskPopTrigger';importExposureTrackerfrom'@/ace/shared/component/exposureTracker';```###PointsDisplay组件特性-**HeadlessUI设计**:支持完全自定义样式和布局-**模板占位符**:使用`$(points)`作为积分数值占位符-**自动数据绑定**:自动从`mainStageStore.data.subStorePointsData.pointsAmount`获取积分-**RenderProps支持**:可通过`render`prop完全自定义渲染逻辑####**任务入口按钮**```tsx{/*任务按钮*/}{props.showTaskTrigger&&(<TaskPopTriggermainStageStore={props.mainStore}renderContent={()=>(<divclassName="flexitems-centerjustify-center"><imgdata-id="mainstage-bottom-task-btn"src="[任务按钮图片URL]"className="w-[60px]h-[60px]"alt="任务"/></div>)}/>)}```**信息区规范**:-**统一组件**:优先使用`PointsDisplay`、`TaskPopTrigger`-**视觉层级**:使用适当的背景和边框突出重要信息-**交互反馈**:点击时提供视觉反馈和埋点-**条件显示**:通过props控制显示状态-**信息更新**:响应式更新数据变化##响应式设计与适配###1.屏幕尺寸适配```tsx//标准布局<divclassName="w-fullbox-borderflexitems-centerjustify-betweenpx-7">{/*统一使用px-7间距*/}</div>//按钮尺寸规范<imgclassName="w-[60px]h-[60px]"/>{/*小型功能按钮*/}<imgclassName="w-[80px]h-[105px]"/>{/*标准功能按钮*/}<imgclassName="w-[104px]h-[104px]"/>{/*大型功能按钮*/}```###2.环境适配```tsximport{isPhaH5}from'@/bus/tinyAppUtil';//根据环境调整样式<divclassName={`${isPhaH5?'bottom-6':'bottom-8'}`}>{/*小程序环境使用不同的底部间距*/}</div>```##状态管理与数据流###1.Store数据集成```tsx//响应式数据绑定constopenBox=useOnReactionInClient(props.mainStore,()=>{returnprops.mainStore.data.openBox;},true);constpointsAmount=useOnReactionInClient(props.mainStore,()=>{returnprops.mainStore.subStorePoints.data.pointsAmount;},0);constuserInfo=useOnReactionInClient(props.mainStore,()=>{returnprops.mainStore.data.userInfo;},null);```###2.状态控制```tsx//处理状态锁定consthandleMainAction=async()=>{if(props.isProcessingLockRef?.current){returnfalse;}try{props.isProcessingLockRef&&(props.isProcessingLockRef.current=true);constresult=awaitprops.onMainActionBtnClick?.();returnresult;}finally{props.isProcessingLockRef&&(props.isProcessingLockRef.current=false);}};```###3.配置驱动```tsx//从配置获取资源constlotteryButtonUrl=props.mainStore.data?.staticConfigData?.globalMaterials?.BottomMaterial?.lotteryButtonUrl;constmainActionUrl=props.mainStore.data?.staticConfigData?.globalMaterials?.BottomMaterial?.mainActionUrl;//功能开关constenableLottery=props.mainStore.data?.staticConfigData?.EnableLottery??true;constenableTask=props.mainStore.data?.staticConfigData?.EnableTask??true;```##动画与交互效果###1.点击动画```tsx//按钮点击缩放效果<buttonclassName="transformtransition-allduration-200hover:scale-105active:scale-95">{/*按钮内容*/}</button>//自定义动画类<divclassName={`transition-transformduration-300${isActive?'scale-110':'scale-100'}`}>{/*内容*/}</div>```###2.状态过渡```tsx//透明度过渡<imgclassName={`transition-opacityduration-300${visible?'opacity-100':'opacity-0'}`}/>//位置动画<divclassName={`transformtransition-transformduration-500${show?'translate-y-0':'translate-y-full'}`}>{/*内容*/}</div>```###3.加载状态```tsx//加载动画{props.loading&&(<divclassName="absoluteinset-0flexitems-centerjustify-centerbg-black/50rounded-lg"><divclassName="animate-spinw-6h-6border-2border-whiteborder-t-transparentrounded-full"/></div>)}//禁用状态<buttondisabled={props.loading||!isInitialized}className="disabled
pacity-50disabled:cursor-not-allowed">{/*按钮内容*/}</button>```##埋点规范###1.曝光埋点```tsx//组件挂载时埋点useEffect(()=>{Logger.ins.reportBiz('[业务标识]_home_bottom_exp',{from:props.mainStore.data.from,scene_code:props.mainStore.data.sceneCode,},{spmC:'main_area',});},[]);```###2.点击埋点```tsx//功能按钮点击埋点consthandleLotteryClick=()=>{Logger.ins.reportBiz('[业务标识]_home_lottery_clk',{from:props.mainStore.data.from,scene_code:props.mainStore.data.sceneCode,},{spmC:'main_area',});};```###3.ExposureTracker使用```tsx//精确曝光追踪<ExposureTrackeronExposure={()=>{Logger.ins.reportBiz('[业务标识]_main_action_exp',{action_type:'open_box',from:props.mainStore.data.from,},{spmC:'main_area',});}}><MainActionButton/></ExposureTracker>```##样式规范###1.布局样式```css/*容器布局*/.w-full.absolute.bottom-8/*标准底部容器*/.box-border.relative.flex.px-7/*内容区域布局-使用relative定位*/.items-center/*垂直居中对齐*//*积分显示区域*/.w-full.flex.justify-center/*积分区域居中布局*/.mb-4/*积分区域下间距*//*功能区域*/.absolute.left-1/2.top-1/2/*主按钮绝对居中定位*/.transform.-translate-x-1/2.-translate-y-1/2/*居中偏移调整*/.flex.items-center/*侧边功能区*/.ml-auto/*右侧功能区自动右对齐*/```###2.尺寸规范```css/*按钮尺寸*/.w-[60px].h-[60px]/*小型功能按钮*/.w-[80px].h-[105px]/*标准功能按钮*/.w-[104px].h-[104px]/*大型功能按钮*/.w-[200px].h-[80px]/*主操作按钮*//*积分显示尺寸*/.min-w-[140px]/*积分容器最小宽度*/.px-6.py-3/*积分区域内边距*//*容器高度规范*/.h-[80px]/*简单布局容器高度*/.h-[120px]/*标准布局容器高度*/.h-[160px]/*大型按钮布局容器高度*//*间距规范*/.px-7/*水平内边距28px*/.mb-4/*积分区域下间距16px*/.ml-4/*元素间距16px*/.space-x-4/*子元素间距16px*/.space-x-8/*较大子元素间距32px*/.bottom-8/*底部距离32px*/```###3.视觉效果```css/*背景效果*/.bg-black/70/*半透明黑色背景*/.bg-gradient-to-t.from-black/50.to-transparent/*渐变背景*/.rounded-lg/*圆角*/.border.border-yellow-400/*边框*//*交互效果*/.hover:scale-105/*悬停放大*/.active:scale-95/*点击缩小*/.transition-all.duration-200/*过渡动画*/.disabled
pacity-50/*禁用状态*/```###4.Z-index层级```css.z-[20]/*Bottom组件基础层级*/.z-[25]/*弹出内容层级*/.z-[30]/*引导提示层级*/```##高级功能###1.新用户引导集成```tsx//引导步骤控制import{showGuideStep,endCurrentGuideStep}from'../newPlayerGuide';consthandleGuideComplete=()=>{endCurrentGuideStep();//继续下一步引导或完成引导};//引导气泡显示{props.showActionMotivateTipsBubble&&(<divclassName="absolute-top-16left-1/2transform-translate-x-1/2bg-orange-100text-orange-800px-4py-2rounded-lgtext-smwhitespace-nowrapshadow-lgz-10">点击开始游戏<divclassName="absolutebottom-[-6px]left-1/2transform-translate-x-1/2w-0h-0border-l-[6px]border-r-[6px]border-t-[6px]border-l-transparentborder-r-transparentborder-t-orange-100"></div></div>)}```###2.条件渲染逻辑```tsx//基于用户状态的条件渲染constnewUser=useOnReactionInClient(props.mainStore,()=>{returnprops.mainStore.data.userInfo?.isNewUser;},false);//基于业务逻辑的功能显示constisBlindBoxAvailable=useOnReactionInClient(props.mainStore,()=>{return[true,'true'].includes(props.mainStore.data.blindBox?.isAvailable);},false);return(<div>{/*新用户不显示高级功能*/}{!newUser&&<LotteryTrigger/>}{/*根据业务配置显示功能*/}{isBlindBoxAvailable&&<BlindBoxPopTrigger/>}</div>);```###3.错误处理```tsxconsthandleActionWithErrorHandling=async()=>{try{constresult=awaitprops.onMainActionBtnClick?.();if(result){awaitprops.onMainActionBtnSuccess?.(result);}}catch(error){console.error('Bottomactionfailed:',error);awaitprops.onMainActionBtnError?.(error);//用户友好的错误提示showToastText('操作失败,请重试');}};```##最佳实践###1.组件复用-**优先使用Shared组件**:`LotteryTrigger`、`BoxOpenTrigger`、`TaskPopTrigger`、`PointsDisplay`-**统一组件接口**:保持相似功能组件的接口一致性-**配置化驱动**:通过props和配置控制组件行为###2.状态管理```tsx//集中处理异步状态const[bottomState,setBottomState]=useState({loading:false,error:null,data:null});//统一的状态更新方法constupdateBottomState=(updates
artial<typeofbottomState>)=>{setBottomState(prev=>({...prev,...updates}));};```##重要注意事项###⚠️**开发要求**1.**统一组件使用**:必须优先使用`LotteryTrigger`、`BoxOpenTrigger`、`TaskPopTrigger`、`PointsDisplay`等shared组件2.**响应式数据绑定**:使用`useOnReactionInClient`进行数据监听,避免直接访问store数据3.**埋点完整性**:所有用户交互都必须包含完整的埋点(曝光和点击)4.**错误处理**:提供完善的错误处理和用户反馈机制5.**性能优化**:合理使用React.memo、useCallback、useMemo等优化手段###🔧**技术规范**1.**样式规范**:使用TailwindCSS类名,保持样式的一致性和可维护性2.**状态管理**:通过props传递状态控制,避免组件内部复杂状态逻辑3.**组件解耦**:保持Bottom组件的纯展示性,业务逻辑通过回调函数处理4.**类型安全**:使用TypeScript接口定义明确的props类型5.**安全区域**:适配不同设备的安全区域,确保按钮可点击###📐**布局约束**1.**底部定位**:统一使用`absolutebottom-8`进行底部定位2.**水平间距**:使用`px-7`确保按钮不贴边显示3.**元素分布**:根据功能需求选择`justify-between`、`justify-center`等分布方式4.**Z-index层级**:通过外部className控制,避免层级冲突5.**响应式适配**:考虑不同屏幕尺寸的布局适配###🎯**功能要求**1.**核心功能**:必须包含项目的核心交互功能入口2.**信息展示**:提供重要的用户信息显示(积分、状态等)3.**视觉反馈**:所有交互都要有明确的视觉反馈4.**状态同步**:及时响应数据变化,更新UI状态5.**引导支持**:支持新用户引导和功能提示###🚫**禁止事项**1.**不得直接操作DOM**:使用React的声明式方式进行UI更新2.**不得跳过埋点**:所有用户交互必须包含对应的埋点代码3.**不得硬编码样式**:使用Tailwind类名而非内联样式4.**不得忽略错误处理**:必须为异步操作提供错误处理5.**不得破坏响应式**:确保组件在不同设备上的正常显示面向全生命周期的智能开发框架
"Code is cheap, Spec is valuable" —— 代码可以快速生成,但好的规范和设计模式才是真正的价值所在。
“检测到‘社交分享’未指定具体路径。当前项目常用方式包括微信唤端、复制链接、二维码海报,请确认是否支持多渠道?是否包含奖励激励?”
复用 SharePanel → 注入“二维码面板”类型和参数 → 调用 useRewardTrigger 控制曝光 → 适配活动主题样式
##3模块详细设计###3.1二维码分享功能(QRCodeSharing)|字段|内容||---|---||职责边界|实现基于二维码的分享能力,支持内容生成、容器注入与激励联动||目录位置|`src/modules/share/qrcode/`||关键依赖|`@utils/qrcode@^1.2`,`vue@3.x`,`pinia@2.x`,`useRewardTrigger`|####3.1.1外部接口*API```typescript//无独立接口,复用通用分享服务POST/api/v1/shareinterfaceSharePayload{type:'qrcode';content:string;timestamp:number}```####3.2.1关键算法#####流程图```mermaidflowchartTDA[用户点击分享按钮]-->B{选择渠道为二维码?}B-->|是|C["调用qrcode.generate(content)"]C-->D{生成成功?}D-->|是|E["将图像注入SharePanel"]D-->|否|F["展示占位图并记录错误"]E-->G{配置了激励策略?}G-->|是|H["调用useRewardTrigger.show()"]G-->|否|I[结束]H-->I```*说明:本流程描述从用户操作到最终反馈的完整路径。`qrcode.generate`为异步方法,需处理加载与失败状态。激励触发仅在业务规则允许时执行。#####代码片段```typescriptconsthandleQrShare=async(content:string)=>{try{constimageUrl=awaitqrCodeUtil.generate(content)sharePanel.open({type:'qrcode',image:imageUrl})if(shouldShowReward()){useRewardTrigger().show()}}catch(err){logger.warn('QRgeneratefailed',err)sharePanel.open({type:'qrcode',fallback:true})}}```####3.1.3状态机(如有)|**状态**|**迁移条件**|**副作用**||---|---|---||`idle`→`generating`|用户选择二维码分享|显示加载指示器||`generating`→`success`|图像生成完成|注入容器并隐藏骨架屏||`generating`→`error`|超时或网络异常|展示默认占位图,降级提示|####3.1.4视图结构```html<template><SharePanel:type="qrcode":image="qrImage":loading="loading"><FallbackMessagev-if="error"text="图片加载失败"/><LoadingSkeletonv-else-if="loading"/></SharePanel></template>```####3.1.5动画&手势*进入动画:`opacity0→1`,持续150ms,ease-in-out*退出动画:点击遮罩后`scale(1)→scale(0.95)`,透明度同步下降*可访问性:支持键盘`Esc`关闭,符合`prefers-reduced-motion`用户偏好####3.1.6异常与降级|**错误码/场景**|**用户提示**|**重试策略**|**监控指标**||---|---|---|---||网络异常导致生成失败|“二维码加载失败”|不自动重试,提供“重新生成”按钮|`qrcode_generate_error_total`||超时(>5s)|“生成较慢,已切换备用方式”|自动降级为“复制链接”选项|`qrcode_timeout_count`||容器不支持该类型|(静默)回退至默认分享页|无|`qrcode_unsupported_fallback`|##4跨模块通信总览###4.1A表:事件总线|**事件名**|**触发时机**|**触发方**|**负载结构(TypeScript)**|**监听方**|**消费时机**|**幂等/异常处理**|**备注**||---|---|---|---|---|---|---|---||`mx:share:triggered`|用户完成分享动作|QrShareHandler.ts|`{type:'qrcode',id:string,timestamp:number}`|AnalyticsService.ts|立即上报埋点|同一ID10s内去重|用于行为分析与转化统计|###4.2B表:响应式Store|**Store片段**|**导出方式**|**数据类型**|**触发时机**|**监听方**|**计算/派生规则**|**异常兜底**||---|---|---|---|---|---|---||`useShareConfig().channels`|`const{channels}=useShareConfig()`|`Ref<ShareChannel[]>`|应用初始化时加载配置|SharePanel.vue|从远端配置中心拉取,本地缓存|解析失败时使用默认渠道列表|###4.3C表:路由&URL|**Query参数**|**出现页面**|**数据类型**|**触发时机**|**消费时机**|**校验规则(zod)**|**同步副作用**||---|---|---|---|---|---|---||`share_mode`|/activity/detail|`'qrcode'\|'link'\|'poster'`|用户从外部链接进入|`onMounted`阶段读取|`z.enum(['qrcode','link','poster'])`|自动打开对应面板,若无效则默认'link'|###4.4D表:跨上下文通道|**通道名**|**上下文**|**数据类型**|**触发时机**|**接收方**|**冲突解决**|**降级策略**||---|---|---|---|---|---|---||`share:sync`|BroadcastChannel|`{type:'qrcode';content:string}`|用户在其他Tab触发分享|当前活跃Tab|时间戳最新者优先|无广播支持时使用`localStorage`+`storage`事件同步|未来展望
| 欢迎光临 链载Ai (http://www.lianzai.com/) | Powered by Discuz! X3.5 |