feat(古尔邦节): 对接接口
This commit is contained in:
parent
ca1cd246a0
commit
49d5345743
@ -1,9 +1,16 @@
|
||||
import { nextTick } from 'vue'
|
||||
import { imageCacheManager } from '@/utils/image/imageCacheManager'
|
||||
import { isLowEndDevice } from '@/utils/performanceMode.js'
|
||||
import { resolveProtectedAssetUrl } from '@/utils/protectedAssets.js'
|
||||
import { isProtectedAssetUrl, resolveProtectedAssetUrl } from '@/utils/protectedAssets.js'
|
||||
|
||||
const TRANSPARENT_PLACEHOLDER =
|
||||
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='
|
||||
|
||||
export const smartImage = {
|
||||
beforeMount(el) {
|
||||
preventNativeBrokenImage(el)
|
||||
},
|
||||
|
||||
async mounted(el, binding) {
|
||||
const skipFade = binding.modifiers.noFade || binding.arg === 'noFade' || isLowEndDevice()
|
||||
const keepVisible = binding.modifiers.noFade || binding.arg === 'noFade'
|
||||
@ -35,6 +42,7 @@ export const smartImage = {
|
||||
}
|
||||
|
||||
if (currentUrl && currentUrl !== el._smartImageCurrentUrl) {
|
||||
preventNativeBrokenImage(el)
|
||||
prepareImageLoadingState(el, skipFade, keepVisible)
|
||||
await processImage(el, currentUrl)
|
||||
el._smartImageCurrentUrl = currentUrl
|
||||
@ -47,21 +55,25 @@ export const smartImage = {
|
||||
el.removeEventListener('error', el._smartImageErrorHandler)
|
||||
delete el._smartImageCurrentUrl
|
||||
delete el._smartImageOriginalBindingValue
|
||||
delete el._smartImageOriginalSrcBeforePlaceholder
|
||||
delete el._smartImageRequestToken
|
||||
delete el._smartImageLoadHandler
|
||||
delete el._smartImageErrorHandler
|
||||
},
|
||||
}
|
||||
|
||||
function getValidUrl(bindingValue, el) {
|
||||
const candidates = [bindingValue, el.getAttribute('src'), el.src]
|
||||
const candidates = [bindingValue, el._smartImageOriginalSrcBeforePlaceholder, el.getAttribute('src'), el.src]
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (
|
||||
typeof candidate === 'string' &&
|
||||
candidate &&
|
||||
candidate !== 'null' &&
|
||||
candidate !== 'undefined' &&
|
||||
candidate !== TRANSPARENT_PLACEHOLDER &&
|
||||
!candidate.includes('blob:') &&
|
||||
typeof candidate === 'string'
|
||||
!candidate.includes('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw')
|
||||
) {
|
||||
return candidate
|
||||
}
|
||||
@ -70,6 +82,18 @@ function getValidUrl(bindingValue, el) {
|
||||
return null
|
||||
}
|
||||
|
||||
function preventNativeBrokenImage(el) {
|
||||
const rawSrc = el.getAttribute('src') || ''
|
||||
if (!rawSrc || rawSrc === TRANSPARENT_PLACEHOLDER) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isProtectedAssetUrl(rawSrc)) {
|
||||
el._smartImageOriginalSrcBeforePlaceholder = rawSrc
|
||||
el.src = TRANSPARENT_PLACEHOLDER
|
||||
}
|
||||
}
|
||||
|
||||
function prepareImageLoadingState(el, skipFade, keepVisible = false) {
|
||||
if (keepVisible) {
|
||||
el.style.opacity = '1'
|
||||
@ -94,8 +118,15 @@ async function processImage(el, url) {
|
||||
|
||||
try {
|
||||
const resolvedUrl = resolveProtectedAssetUrl(url)
|
||||
const requestToken = Symbol('smart-image-request')
|
||||
el._smartImageRequestToken = requestToken
|
||||
preventNativeBrokenImage(el)
|
||||
const imageUrl = await imageCacheManager.scheduleRequest(resolvedUrl)
|
||||
|
||||
if (el._smartImageRequestToken !== requestToken) {
|
||||
return
|
||||
}
|
||||
|
||||
const handleLoad = () => {
|
||||
showImage(el)
|
||||
|
||||
@ -120,7 +151,19 @@ async function processImage(el, url) {
|
||||
} catch (error) {
|
||||
const fallbackUrl = resolveProtectedAssetUrl(url)
|
||||
console.warn(`Image processing failed: ${fallbackUrl}`, error)
|
||||
el.src = fallbackUrl
|
||||
const imageLoaded = await loadNativeImage(fallbackUrl)
|
||||
if (imageLoaded) {
|
||||
el.src = fallbackUrl
|
||||
}
|
||||
showImage(el)
|
||||
}
|
||||
}
|
||||
|
||||
function loadNativeImage(url) {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image()
|
||||
img.onload = () => resolve(true)
|
||||
img.onerror = () => resolve(false)
|
||||
img.src = url
|
||||
})
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -49,6 +49,7 @@ export const pageConfig = {
|
||||
'gameTitleSSR',
|
||||
'giftBg',
|
||||
'helpBt',
|
||||
'historyBg',
|
||||
'leftBt',
|
||||
'lotteryPrizeActiveBg',
|
||||
'lotteryPrizeBg',
|
||||
@ -65,6 +66,7 @@ export const pageConfig = {
|
||||
'redeemSR2013',
|
||||
'redeemSSR2021',
|
||||
'redeemSSR2022',
|
||||
'resultBg',
|
||||
'RewardTop1',
|
||||
'RewardTop2',
|
||||
'RewardTop3',
|
||||
|
||||
@ -375,7 +375,7 @@
|
||||
"barrageList": ""
|
||||
},
|
||||
"actions": {
|
||||
"rewardDraw": "点击 lotteryBg 下层 draw1Bt 调用 apiPostCardRewardDraw({ activityId:'1001', ticketType:当前奖池 poolType, drawCount:1 }),当前奖池票券数量为 0 时按钮置灰且点击无效;点击 draw5BtBg 调用 drawCount:5,当前奖池票券数量不足 5 时按钮置灰且点击无效;接口成功后刷新 apiGetCardTicketList('1001') 同步第一层三种票券数量,并保留当前 lotteryPrizeBg / lotteryPrizeActiveBg 转圈动画",
|
||||
"rewardDraw": "点击 lotteryBg 下层 draw1Bt 调用 apiPostCardRewardDraw({ activityId:'1001', ticketType:当前奖池 poolType, drawCount:1 }),当前奖池票券数量为 0 时按钮置灰且点击无效;点击 draw5BtBg 调用 drawCount:5,当前奖池票券数量不足 5 时按钮置灰且点击无效;接口成功后刷新 apiGetCardTicketList('1001') 同步第一层三种票券数量,先根据接口结果播放 lotteryPrizeActiveBg 模拟转圈动画,动画结束后再展示 resultBg 结果弹窗,底部按钮文本为 Confirm,点击后关闭弹窗",
|
||||
"drawOnce": "点击 GameDrawBtBg 调用 apiPostCardDraw({ activityId:'1001', drawCount:1, needCoins: cardDrawTicketCount === 0 });接口成功后刷新 apiGetCardTicketList('1001') 和 R 级 apiPostCardBagList({ activityId:'1001', cardGrade:'R', current:1, size:20 })",
|
||||
"drawFive": "",
|
||||
"combineCard": "点击 Combine 调用 apiPostCardCombine({ activityId:'1001', cardTemplateIds });cardTemplateIds 为当前奖池用户已拥有且数量大于 0 的不同卡片 templateId 字符串数组;R 需要 5 个不同 R 卡,SR 需要 3 个不同 SR 卡;成功后刷新当前等级卡包和目标等级卡包",
|
||||
@ -427,21 +427,21 @@
|
||||
"layout": "flex-column rows + row flex center",
|
||||
"gap": "R 使用 3vw;SR/SSR 使用 lottery-prize-list-large 将间距缩小为 1vw",
|
||||
"itemWidth": "R/SR 使用 calc((100% - var(--lottery-prize-gap) * 2) / 3);SSR 使用 calc((100% - var(--lottery-prize-gap)) / 2.2)",
|
||||
"modeSwitch": "左右 leftBt 切换 lotteryR / lotterySR / lotterySSR,切换时重置 activeLotteryIndex;抽奖动画执行中暂不允许切换奖池",
|
||||
"modeSwitch": "左右 leftBt 切换 lotteryR / lotterySR / lotterySSR,切换时重置 activeLotteryIndex;奖励池抽奖请求中暂不允许切换奖池",
|
||||
"defaultImage": "lotteryPrizeBg",
|
||||
"activeImage": "lotteryPrizeActiveBg",
|
||||
"apiData": "有 /activity/card/reward/prize/list 接口数据时优先使用接口返回数组;字段为 id、poolType、prizeName、prizeType、prizeValue、prizeImage、sortOrder;按 sortOrder 升序排序,并补充当前渲染 index 用于 lotteryPrizeActiveBg 高亮动画",
|
||||
"itemContent": "每个奖品仍使用 itemCenter,背景根据 activeLotteryIndex 切换 lotteryPrizeBg / lotteryPrizeActiveBg;itemCenter 内部使用垂直 flex,第一行 prizeImage 宽度 60%,第二行展示奖励信息;奖励信息使用 formatRewardDisplay(prize.prizeType, { content: prize.prizeValue, quantity: prize.prizeValue }),无结果时回退 prizeName",
|
||||
"itemContent": "每个奖品仍使用 itemCenter,背景根据 activeLotteryIndex 切换 lotteryPrizeBg / lotteryPrizeActiveBg;itemCenter 内部使用垂直 flex,第一行 prizeImage 宽度 60%,第二行展示奖励信息;奖励信息有 days 时展示 `${days} days`,没有 days 时展示 prizeValue,不展示 prizeName",
|
||||
"remark": "奖品区域按行渲染,每一行使用 flex 居中,避免 grid 在最后一行数量不足时贴左;没有接口数据时保留本地测试奖品占位,方便预览。"
|
||||
},
|
||||
"animation": {
|
||||
"reference": "/activities/lucky-dollars-season5",
|
||||
"mode": "切换奖品 itemCenter 的背景图来模拟转圈,而不是叠加 prizeActiveFrame 发光边框",
|
||||
"mode": "当前奖励池抽奖接口成功后先播放 lotteryPrizeActiveBg 模拟转圈动画,动画结束后再展示 resultBg 结果弹窗",
|
||||
"activeRule": "同一时间只有一个奖品 itemCenter 使用 lotteryPrizeActiveBg,其余使用 lotteryPrizeBg",
|
||||
"order": "从左到右、从上到下依次变化背景图",
|
||||
"resultFlow": "点击抽奖后先请求接口,接口返回中奖奖品,再根据奖品 id/name 计算最终位置,然后启动动画并停在中奖位置",
|
||||
"multiDraw": "5 连抽也只执行一次动画,最终位置取接口返回结果中的第一个中奖奖品",
|
||||
"currentPreview": "requestLotteryDraw 已接入 /activity/card/reward/draw;接口结果暂未映射具体中奖奖品位置时,动画仍优先停在当前奖池第 4 个奖品(数组下标 3),如果当前奖池不足 4 个,则停在最后一个奖品;后续根据接口返回 prizeId/name 再补最终位置映射",
|
||||
"multiDraw": "5 连抽也直接展示结果弹窗,弹窗图片取接口返回结果中的第一个中奖奖品;没有中奖标记时取第一条结果",
|
||||
"currentPreview": "requestLotteryDraw 已接入 /activity/card/reward/draw;接口成功后先计算中奖停点并播放模拟动画,动画结束后再展示 resultBg 结果弹窗,底部按钮文本为 Confirm,点击后关闭弹窗",
|
||||
"performanceNote": "当前奖品数量少,直接切换两个 itemCenter 的 imgUrl 可以满足效果;后续如果奖品数量增加、动画频率提高或低端机出现闪烁/卡顿,优先考虑改成固定默认背景 + 单个 active 覆盖层,或用 class 切换预加载后的 CSS background,以减少图片 src 变更。",
|
||||
"decisionRule": "只要满足效果和布局,优先选择更省性能、用户体验更稳定的方案;如果 Codex 发现更好的样式或动画实现,需要先说明方案、优缺点和影响范围,并询问是否采用,不能静默替换用户已调好的结构。"
|
||||
},
|
||||
@ -857,7 +857,7 @@
|
||||
"topLayer": "display:flex; flex:1; align-items:center; gap:4vw",
|
||||
"sideColumns": "左右两列 width:10vw; flex:0 0 10vw; 内部居中放 leftBt,右侧 leftBt 水平翻转;点击左右按钮切换 lotteryR / lotterySR / lotterySSR",
|
||||
"prizeGrid": "中间 flex:1;使用 flex-direction:column 分行渲染,行内 flex 居中;R 使用 --lottery-prize-gap:3vw,SR/SSR 使用 lottery-prize-list-large 缩小到 1vw;接口返回奖品按 sortOrder 排序后渲染,lotteryR 默认行数 2/3/2,lotterySR 3 个一行展示并放大,lotterySSR 2 个一行展示并更大;默认 lotteryPrizeBg,当前 activeLotteryIndex 使用 lotteryPrizeActiveBg;奖品 itemCenter 内部垂直展示 60% 宽 prizeImage 和 formatRewardDisplay 生成的奖励信息",
|
||||
"animation": "参考 LuckyDollars Season5 的接口返回后再开启动画逻辑;当前页面不叠加发光边框,而是通过 lotteryPrizeBg / lotteryPrizeActiveBg 背景图切换模拟转圈。点击 1 抽或 5 连抽都只执行一次动画,顺序按当前奖池渲染数组从左到右、从上到下,最终停在接口结果对应位置;当前接口结果未映射时优先停第 4 个,奖池不足 4 个时停最后一个。",
|
||||
"animation": "当前奖励池抽奖播放 lotteryPrizeActiveBg 模拟转圈动画;点击 1 抽或 5 连抽接口成功后先计算中奖停点并播放动画,动画结束后再展示 resultBg 结果弹窗,底部按钮文本为 Confirm,点击后关闭弹窗。",
|
||||
"performanceDecision": "当前先保留直接切换 imgUrl 的实现,因为测试奖品数量少且两张图已预加载,成本可控。若后续体验不稳,优先提议改为默认背景常驻 + 单个 active 覆盖层,或 class 切 CSS background;任何更优样式方案先询问用户是否采用。",
|
||||
"bottomLayer": "display:flex; align-items:center; justify-content:space-around; gap:0; margin:0; padding:0;draw1Bt 和 draw5BtBg 宽度 38vw;两个按钮内部水平居中、垂直贴底展示 10vw 当前奖池票券图,切换 lotteryR / lotterySR / lotterySSR 时同步切换 ticketR / ticketSR / ticketSSR;draw1Bt 需要当前奖池奖励票数量 >= 1,draw5BtBg 需要 >= 5,不满足时置灰且不给点击;点击调用 /activity/card/reward/draw,ticketType 使用当前奖池 poolType,drawCount 为 1 或 5,成功后刷新第一层三种票券数量"
|
||||
},
|
||||
@ -892,8 +892,8 @@
|
||||
"rankingModule": {
|
||||
"source": "布局参考 /activities/lucky-dollars-season5;数据接口改用 /activity/week-star/ranking 和 /activity/week-star/my-rank",
|
||||
"top3": "前三名使用 GulbenFestival 页面私有 TopUser 组件 src/views/Activities/GulbenFestival/components/topUser.vue,不修改 LuckyDollars / GamesKing 等参考页面组件;边框图使用 Gulben 的 top1 / top2 / top3;布局为左中右一行,顺序第二名、第一名、第三名,第二/三名 flex:1; min-width:0,第一名 flex:1.25; min-width:0;贡献值样式在私有组件中模仿 src/views/Ranking/GamesKing/components/topUserDaily.vue,使用金币图标、圆角渐变底、#ffdf36 描边、内阴影和按名次区分的渐变色;排名文字和贡献值文字统一使用白色",
|
||||
"listAfterTop3": "从第四名开始渲染列表;当前使用 Gulben 的 RankingItem 图片承载列表项;排名文字和贡献值文字统一使用白色;贡献值参考 WeeklyStar 使用金币图标 + quantity/amount,不再显示 $;名称/id 的 flex-column 中新增一行等级图,依次展示 vip1、itemCenter(userLevel1Bg)、itemCenter(wealthLevel1Bg),后两者宽度暂用 10vw",
|
||||
"myRanking": "切换到 Personal Rank / Ranking 时固定贴底展示我的排名;当前同样使用 RankingItem 承载;排名文字和贡献值文字统一使用白色;贡献值使用金币图标,名称下方同步展示 vip1 / userLevel1Bg / wealthLevel1Bg 等级占位",
|
||||
"listAfterTop3": "从第四名开始渲染列表;当前使用 Gulben 的 RankingItem 图片承载列表项;排名文字和贡献值文字统一使用白色;贡献值参考 WeeklyStar 使用金币图标 + quantity/amount,不再显示 $;名称/id 的 flex-column 中新增一行等级图,vip 图片根据 rankingList 列表项 vipLevelName 按 VISCOUNT->vip1、EARL->vip2、MARQUIS->vip3、DUKEI->vip4、KING->vip5、EMPEROR->vip6 映射展示,没有 vipLevelName 时不展示;userLevel 背景图根据 charmLevel 按 1-10/11-20/21-30/31-40/41-50 对应 userLevel1Bg~userLevel5Bg,wealthLevel 背景图根据 wealthLevel 按同样分段对应 wealthLevel1Bg~wealthLevel5Bg,内部居中展示对应等级文字,文字白色 font-size:1em",
|
||||
"myRanking": "切换到 Personal Rank / Ranking 时固定贴底展示我的排名;当前同样使用 RankingItem 承载;排名文字和贡献值文字统一使用白色;贡献值使用金币图标,名称下方同步展示 vip / userLevelBg / wealthLevelBg;vip 图片根据 myRanking.vipLevelName 按 VISCOUNT->vip1、EARL->vip2、MARQUIS->vip3、DUKEI->vip4、KING->vip5、EMPEROR->vip6 映射展示,没有 vipLevelName 时不展示;userLevel 背景图根据 myRanking.charmLevel 按 1-10/11-20/21-30/31-40/41-50 对应 userLevel1Bg~userLevel5Bg,wealthLevel 背景图根据 myRanking.wealthLevel 按同样分段对应 wealthLevel1Bg~wealthLevel5Bg,内部居中展示对应等级文字,文字白色 font-size:1em",
|
||||
"pagination": "pageSize=10;current 从 1 开始;接口返回 rankUsers.records 追加到 ranking;当 current * rankUsers.size >= rankUsers.total 时停止上拉;rankingLoading / rankingHasMore 防止重复请求"
|
||||
},
|
||||
"data": "pageConfig.activity.personalRankActivityType 默认 THIS_WEEK;weekly-star 字段映射为 userNickname -> userName、quantity -> amount;礼物临时使用 KingAndQueen templateId=1966020557831610370"
|
||||
@ -968,12 +968,13 @@
|
||||
- 补充规则弹窗:点击首行 `ruleBtBg` 打开 `MaskLayer`,弹窗内容用 `itemCenter` 承载 `ruleInfoBg`,点击遮罩关闭、点击弹窗内容阻止冒泡;规则文字使用 `color:#FFF; font-size:1em; font-weight:600`;内容区 `max-height:62vh` 内可上下滑动,并通过 `rule-popup-scroll` 隐藏滚动条;规则文案按标题和 `(1)/(2)/(3)` 拆成独立行渲染
|
||||
- 补充集卡活动基础接口:Gulben 页面调用卡片相关接口时固定传字符串 `activityId:'1001'`,`src/api/activity.js` 中的接口方法保持通用,不写死活动 ID;页面初始化时调用 `apiGetCardTicketList('1001')` 查询用户票券数量、按 `STANDARD/PREMIUM/LUXURY` 三个 `poolType` 调用 `apiGetCardRewardPrizeList('1001', poolType)` 查询奖励池奖品列表,分别对应页面展示 `R/SR/SSR`;调用 `apiGetCardRewardMarquee('1001')` 查询中奖飘窗列表
|
||||
- 补充用户卡包查询:页面初始化时调用 `apiPostCardBagList({ activityId:'1001', cardGrade, current:1, size:20 })`,按 `R/SR/SSR` 三个 `cardGrade` 分别查询并存储;集卡玩法第四层固定渲染 R=5、SR=3、SSR=2 个卡位,接口 records 中返回对应 `cardTemplateId` 时,用 `redeemR2001~redeemR2005`、`redeemSR2011~redeemSR2013`、`redeemSSR2021~redeemSSR2022` 替换默认 `cardBackBg`,没有返回的卡位继续展示 `cardBackBg`;正面卡片使用 `itemCenter` 承载,并在右上角展示 `quantity` 数量角标,`cardBackBg` 不展示角标
|
||||
- 对接抽奖区奖品池展示:`/activity/card/reward/prize/list` 返回的奖品按当前奖池 `poolType` 存储,渲染时按 `sortOrder` 升序排序并补充 `index`;奖品 itemCenter 背景继续用 `lotteryPrizeBg / lotteryPrizeActiveBg` 参与动画,内部垂直展示 `prizeImage`(宽度 60%)和奖励信息;奖励信息用 `formatRewardDisplay(prize.prizeType, { content: prize.prizeValue, quantity: prize.prizeValue })` 生成,失败时回退 `prizeName`
|
||||
- 补充抽奖动画占位逻辑:参考 `/activities/lucky-dollars-season5` 的“接口返回结果后计算最终位置再开启动画”,当前通过 `lotteryPrizeBg` / `lotteryPrizeActiveBg` 切换背景图模拟转圈;5 连抽只执行一次动画
|
||||
- 对接抽奖区奖品池展示:`/activity/card/reward/prize/list` 返回的奖品按当前奖池 `poolType` 存储,渲染时按 `sortOrder` 升序排序并补充 `index`;奖品 itemCenter 背景继续用 `lotteryPrizeBg / lotteryPrizeActiveBg` 参与动画,内部垂直展示 `prizeImage`(宽度 60%)和奖励信息;奖励信息有 `days` 时展示 `${days} days`,没有 `days` 时展示 `prizeValue`,不展示 `prizeName`
|
||||
- 补充奖励池抽奖结果逻辑:`/activity/card/reward/draw` 接口成功后先计算中奖停点并播放 `lotteryPrizeActiveBg` 模拟转圈动画,动画结束后再展示 `resultBg` 结果弹窗;底部按钮文本为 `Confirm`,点击后关闭弹窗
|
||||
- 补充 `collectGameBg` 五层预览结构:票券数量、提示文案、当前奖池标题、当前奖池卡牌、操作按钮;支持 `gameR` / `gameSR` / `gameSSR` 本地切换
|
||||
- 补充 CollectCards 第三个任务模块 `taskModule`:任务接口和字段先参考 LuckyDollars Season5,列表项使用 `itemCenter` + `taskItemBg`,contentStyle 固定为 `display:flex; padding:8px; justify-content:space-between; align-items:center`,不引用 Season5 的任务类型图标和票券图标;右侧任务按钮也改为 `itemCenter` 背景图 + 翻译文字;Gulben 页面中任务列表和领取任务等涉及 `activityId` 的接口统一固定传 `gulbenActivityId` 的值,也就是字符串 `'1001'`
|
||||
- 补充 Personal Rank / Reward 预览结构:只展示 `RewardTop1`、`RewardTop2`、`RewardTop3` 三个 100vw 的 `itemCenter` 模块,从上往下排序;奖励数据调用 `apiGetActivityResource(rewardActivityType)`,当前 `rewardActivityType` 临时使用 `DAILY_RECHARGE_TASK2` 便于预览;接口返回 10 套奖励时只取前 3 套,每套最多 6 个奖励;如果某套只有 5 个奖励,则复制第 5 个奖励补到第 6 个位置用于预览;布局参考 DailyRecharge,左侧主奖励大图,右侧小奖励固定按 2/2/1 三行展示,第三行即使没有奖励也保留单展示位不可见占位并居中;奖励文案统一使用 `formatRewardDisplay(reward.type, reward)`
|
||||
- 补充 Personal Rank / Ranking 预览结构:活动礼物模块使用 `giftBg` 的 3 个预设空位直接展示 giftPhoto / giftCandy,不额外套参考页的 `gift` 单项背景;排行榜布局参考 LuckyDollars Season5,数据改用 `/activity/week-star/ranking` 和 `/activity/week-star/my-rank`;前三名使用 GulbenFestival 私有 `components/topUser.vue` + `top1/top2/top3`,布局为第二名/第一名/第三名左中右排列且第一名稍宽;贡献值参考 WeeklyStar 使用金币图标,前三名贡献值外观在私有组件内参考 GamesKing 的 `topUserDaily.vue` 圆角渐变底;排行榜排名和贡献值文字统一使用白色;第四名以后和我的排名使用 `RankingItem` 承载,名称下方新增 `vip1`、`userLevel1Bg`、`wealthLevel1Bg` 等级行,切换到该标签时贴底展示我的排名,并通过底部 `rankingLoadmore` 上拉加载更多
|
||||
- 补充 Personal Rank / Ranking 预览结构:活动礼物模块使用 `giftBg` 的 3 个预设空位直接展示 giftPhoto / giftCandy,不额外套参考页的 `gift` 单项背景;排行榜布局参考 LuckyDollars Season5,数据改用 `/activity/week-star/ranking` 和 `/activity/week-star/my-rank`;前三名使用 GulbenFestival 私有 `components/topUser.vue` + `top1/top2/top3`,布局为第二名/第一名/第三名左中右排列且第一名稍宽;贡献值参考 WeeklyStar 使用金币图标,前三名贡献值外观在私有组件内参考 GamesKing 的 `topUserDaily.vue` 圆角渐变底;排行榜排名和贡献值文字统一使用白色;第四名以后和我的排名使用 `RankingItem` 承载,名称下方新增 vip 等级图、userLevelBg、wealthLevelBg 等级行,vip 图片根据 `vipLevelName` 按 `VISCOUNT->vip1`、`EARL->vip2`、`MARQUIS->vip3`、`DUKEI->vip4`、`KING->vip5`、`EMPEROR->vip6` 映射展示,没有 `vipLevelName` 时不展示;userLevel 背景图根据 `charmLevel` 按 1-10/11-20/21-30/31-40/41-50 对应 `userLevel1Bg~userLevel5Bg`,wealthLevel 背景图根据 `wealthLevel` 按同样分段对应 `wealthLevel1Bg~wealthLevel5Bg`,内部居中展示对应等级文字,文字白色且 `font-size:1em`;切换到该标签时贴底展示我的排名,并通过底部 `rankingLoadmore` 上拉加载更多
|
||||
- `Personal Rank / Reward` 和 `Personal Rank / Ranking` 的活动奖励、活动礼物都来自 `/activity/king-queen/effective?templateId=2057676469390872578`;页面内必须通过共享缓存方法读取,避免进入 Reward 和 Ranking 时重复请求同一个 effective 接口。
|
||||
- 补充 Room Rank / Reward 和 Ranking:Reward 直接 100vw 展示 `ruleActivity`;Ranking 复用 Personal Rank 的排行榜展示和 weekly-star 上拉分页,但不展示活动礼物模块,头像统一 `border-radius:4px`,列表和我的排名只展示名称、ID、金币数量,不展示 `vip1`、`userLevel1Bg`、`wealthLevel1Bg` 三个等级图片
|
||||
- 创建本地 OSS 目录 `public/oss/h5/Azizi/Activities/GulbenFestival/`
|
||||
- 添加路由 `/activities/gulben-festival`
|
||||
@ -1024,25 +1025,26 @@
|
||||
- `connectToAppHandler()` 内通过 `connectToApp(() => { completePreloading() })` 在 APP 连接成功或浏览器环境初始化完成后再执行 `completePreloading()`;因此 `initData()`、首屏 blocking 图片预加载、后台图片预加载都在 APP header / 用户信息处理后触发。
|
||||
- Room Rank 的 UI 仍复用当前排行榜结构,不展示活动礼物模块;头像保持 `border-radius:4px`,列表和我的排名只展示名称、ID、金币数量,不展示 `vip1`、`userLevel1Bg`、`wealthLevel1Bg`。
|
||||
|
||||
## 本次同步:奖励池抽奖结果停点
|
||||
## 本次同步:奖励池抽奖结果弹窗
|
||||
|
||||
- `/activity/card/reward/draw` 返回数据按 `body.rewards` 解析;兼容旧的 `body.results` 和直接数组返回,最终统一存入 `drawResult.results`。
|
||||
- 单抽时使用返回的第一条抽奖结果定位奖品位置。
|
||||
- 5 连抽只执行一次转圈动画,优先取 `rewards` 中第一个 `isWin === true` 的奖品作为停点;如果没有中奖标记,则退回使用第一条结果。
|
||||
- 停点匹配优先级为 `prizeId -> prizeName -> prizeImage -> prizeType + prizeValue`,匹配当前奖池 `activeLotteryPrizes` 中对应奖品后,将 `lotteryPrizeActiveBg` 停在该奖品位置。
|
||||
- 单抽和 5 连抽接口成功后先播放奖励池模拟转圈动画,动画结束后再展示 `resultBg` 结果弹窗。
|
||||
- 结果弹窗图片优先取 `rewards` 中第一个 `isWin === true` 的奖品;如果没有中奖标记,则退回使用第一条结果;奖励池抽奖结果图宽度为 `45vw`,抽卡/合成结果图继续使用 `28vw`。
|
||||
- 结果弹窗底部按钮文本为 `Confirm`,点击后关闭弹窗。
|
||||
- 停点匹配优先级为 `prizeId -> prizeImage -> prizeType + prizeValue`,匹配当前奖池 `activeLotteryPrizes` 中对应奖品后,将 `lotteryPrizeActiveBg` 停在该奖品位置。
|
||||
- 匹配不到接口奖品时才使用本地 `prizeIndex` 兜底;没有任何可用停点时停在当前奖池第一个奖品,避免继续使用旧测试逻辑固定停第 4 个。
|
||||
|
||||
## 本次同步:抽奖区奖励文案
|
||||
|
||||
- `lotteryBg` 奖品 itemCenter 内第二行奖励信息直接展示接口返回的 `prizeName`。
|
||||
- 该区域不再使用 `formatRewardDisplay(prize.prizeType, { content: prize.prizeValue, quantity: prize.prizeValue })` 拼接奖励文案,避免和接口配置的奖品名称不一致。
|
||||
- `lotteryBg` 奖品 itemCenter 内第二行奖励信息不展示接口返回的 `prizeName`。
|
||||
- 该区域直接按接口字段展示:有 `days` 时展示 `${days} days`,没有 `days` 时展示 `prizeValue`。
|
||||
|
||||
## 本次同步:抽奖动画防抖动
|
||||
|
||||
- `lotteryBg` 奖品格子的 `itemCenter` 背景固定使用 `lotteryPrizeBg`,抽奖动画不再通过切换 `:imgUrl` 改变背景图。
|
||||
- `lotteryPrizeActiveBg` 改为放在奖品格子内部的绝对定位覆盖图,通过 `lottery-prize-active-bg-show` 切换 `opacity` 展示选中态。
|
||||
- 由于 `lotteryPrizeActiveBg` 和 `lotteryPrizeBg` 只有颜色差异,使用覆盖图透明度切换可以避免动画过程中反复改图片 `src`,减少移动端图片重新解码、布局重算导致的页面抖动。
|
||||
- 奖品图片和 `prizeName` 文案统一放到覆盖图上层,使用 `position:relative; z-index:1` 保持显示层级。
|
||||
- 奖品图片和奖励信息统一放到覆盖图上层,使用 `position:relative; z-index:1` 保持显示层级。
|
||||
|
||||
## 本次同步:抽奖高亮防抖动方案
|
||||
|
||||
@ -1063,7 +1065,7 @@
|
||||
- `lotteryPrizeActiveBg` 仍然用于抽奖选中态视觉,但不再作为 `itemCenter` 的 `imgUrl` 频繁切换。
|
||||
- 奖品格 `itemCenter` 底图固定为 `imageUrl('lotteryPrizeBg')`。
|
||||
- 每个奖品格内部常驻一张绝对定位的 `lotteryPrizeActiveBg` 覆盖图,class 为 `lottery-prize-active-bg`。
|
||||
- 当前选中奖品只切换 `lottery-prize-active-bg-show`,通过 `opacity` 展示 active 图;奖品图片和 `prizeName` 使用 `z-index:1` 保持在 active 图上层。
|
||||
- 当前选中奖品只切换 `lottery-prize-active-bg-show`,通过 `opacity` 展示 active 图;奖品图片和奖励信息使用 `z-index:1` 保持在 active 图上层。
|
||||
- 这样既保留 `lotteryPrizeActiveBg` 的设计效果,也避免抽奖动画中反复修改 `itemCenter` 背景图 `src` 导致页面抖动。
|
||||
|
||||
## 本次同步:抽奖默认选中态
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user