bindbox-mini/utils/activity.js

202 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 活动相关工具函数
* 从 yifanshang/duiduipeng/wuxianshang 页面中提取的公共逻辑
*/
/**
* 解包API返回的数据
* @param {any} list - API返回的数据
* @returns {Array} 数组
*/
export function unwrap(list) {
if (Array.isArray(list)) return list
const obj = list || {}
const data = obj.data || {}
const arr = obj.list || obj.items || data.list || data.items || data
return Array.isArray(arr) ? arr : []
}
/**
* 判断真值(支持多种格式)
* @param {any} v - 待判断的值
* @returns {boolean}
*/
export function truthy(v) {
if (typeof v === 'boolean') return v
const s = String(v || '').trim().toLowerCase()
if (!s) return false
return s === '1' || s === 'true' || s === 'yes' || s === 'y' || s === '是' || s === 'boss是真的' || s === 'boss' || s === '大boss'
}
/**
* 检测是否为BOSS奖
* @param {Object} item - 奖品对象
* @returns {boolean}
*/
export function detectBoss(item) {
const i = item || {}
return truthy(i.is_boss) || truthy(i.boss) || truthy(i.isBoss) || truthy(i.boss_true) || truthy(i.boss_is_true) || truthy(i.bossText) || truthy(i.tag)
}
/**
* 奖品等级映射 (与管理端保持一致)
*/
export const PRIZE_LEVEL_LABELS = {
1: 'S',
2: 'A',
3: 'B',
4: 'C',
5: 'D',
6: 'E',
7: 'F',
8: 'G',
9: 'H',
11: 'Last'
}
/**
* 等级数字转字母/标签
* @param {number|string} level - 等级
* @returns {string}
*/
export function levelToAlpha(level) {
if (level === 'BOSS') return 'BOSS'
const n = Number(level)
if (PRIZE_LEVEL_LABELS[n]) return PRIZE_LEVEL_LABELS[n]
if (isNaN(n) || n <= 0) return String(level || '赏')
// 兜底逻辑:如果超出定义的映射,使用 A, B, C...
return String.fromCharCode(64 + n)
}
/**
* 状态转文本
* @param {number} status - 状态码
* @returns {string}
*/
export function statusToText(status) {
if (status === 1) return '进行中'
if (status === 0) return '未开始'
if (status === 2) return '已结束'
return String(status || '')
}
/**
* 标准化期列表数据
* @param {any} list - API返回的期列表
* @returns {Array}
*/
export function normalizeIssues(list) {
const arr = unwrap(list)
return arr.map((i, idx) => ({
id: i.id ?? String(idx),
title: i.title ?? i.name ?? '',
no: i.no ?? i.index ?? i.issue_no ?? i.issue_number ?? null,
status_text: i.status_text ?? (i.status === 1 ? '进行中' : i.status === 0 ? '未开始' : i.status === 2 ? '已结束' : '')
}))
}
/**
* 标准化奖励列表数据
* @param {any} list - API返回的奖励列表
* @param {Function} cleanUrl - URL清理函数
* @returns {Array}
*/
export function normalizeRewards(list, cleanUrl = (u) => u) {
const arr = unwrap(list)
const items = arr.map((i, idx) => ({
id: i.product_id ?? i.id ?? String(idx),
title: i.name ?? i.title ?? '',
image: cleanUrl(i.product_image ?? i.image ?? i.img ?? i.pic ?? i.banner ?? ''),
weight: Number(i.weight) || 0,
boss: detectBoss(i),
min_score: i.min_score || 0,
level: levelToAlpha(i.prize_level ?? i.level ?? (detectBoss(i) ? 'BOSS' : '赏'))
}))
const total = items.reduce((acc, it) => acc + (it.weight > 0 ? it.weight : 0), 0)
const enriched = items.map(it => {
const rawPercent = total > 0 ? (it.weight / total) * 100 : 0
return {
...it,
percent: parseFloat(rawPercent.toFixed(2)) // 统一保留2位小数
}
})
// 按 weight 升序排列(从小到大)
enriched.sort((a, b) => (a.weight - b.weight))
return enriched
}
/**
* 查找最新的期ID
* @param {Array} list - 期列表
* @returns {string}
*/
export function pickLatestIssueId(list) {
const arr = Array.isArray(list) ? list : []
let latest = arr[arr.length - 1] && arr[arr.length - 1].id
let maxNo = -Infinity
arr.forEach(i => {
const n = Number(i.no)
if (!Number.isNaN(n) && Number.isFinite(n) && n > maxNo) {
maxNo = n
latest = i.id
}
})
return latest || (arr[0] && arr[0].id) || ''
}
/**
* 按等级分组奖励
* @param {Array} rewards - 奖励列表
* @param {string} playType - 活动类型 ('match', 'matching' 对对碰模式,其他为普通模式)
* @returns {Array} 分组后的奖励
*/
export function groupRewardsByLevel(rewards, playType = 'normal') {
const isMatchType = ['match', 'matching'].includes(playType)
const groups = {}
; (rewards || []).forEach(item => {
let level = item.level || '赏'
// 如果是对对碰(具有 min_score 且不是 BOSS则组名包含对子数
if (item.min_score > 0 && level !== 'BOSS') {
level = `${item.min_score}对子`
}
if (!groups[level]) groups[level] = []
groups[level].push(item)
})
return Object.keys(groups).sort((a, b) => {
// Last 和 BOSS 优先(仅限普通模式)
if (!isMatchType) {
if (a === 'Last' || a === 'BOSS') return -1
if (b === 'Last' || b === 'BOSS') return 1
}
// 对对碰模式:按 min_score 升序排序
if (isMatchType) {
const extractScore = (key) => {
const match = key.match(/(\d+)对子/)
return match ? parseInt(match[1], 10) : 0
}
return extractScore(a) - extractScore(b)
}
// 普通模式:分组之间按该组最小 weight 排序(升序)
const minWeightA = Math.min(...groups[a].map(item => item.weight || 0))
const minWeightB = Math.min(...groups[b].map(item => item.weight || 0))
return minWeightA - minWeightB
}).map(key => {
const levelRewards = groups[key]
// 对对碰模式:保持 min_score 升序(已在 previewRewards 排序)
// 普通模式:确保分组内的奖品按 weight 升序排列(从小到大)
if (!isMatchType) {
levelRewards.sort((a, b) => (a.weight - b.weight))
}
const total = levelRewards.reduce((sum, item) => sum + (Number(item.percent) || 0), 0)
return {
level: key,
rewards: levelRewards,
totalPercent: total.toFixed(2)
}
})
}