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