/** * 活动相关工具函数 * 从 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) } }) }