topfans/frontend/utils/activity-config.js
2026-04-13 16:08:52 +08:00

265 lines
7.2 KiB
JavaScript

/**
* 应援活动主题配置模块
* 支持生日、演唱会、巴士三种活动类型
* 支持从后端动态获取配置
*/
import {
getActivityListApi,
getActivityDetailApi,
getActivityItemsApi,
getActivityProgressApi,
purchaseActivityItemApi
} from './api.js'
// 活动类型常量
export const ACTIVITY_TYPES = {
BIRTHDAY: 'birthday',
CONCERT: 'concert',
BUS: 'bus',
}
// 阶段常量(内部使用)
const STAGE_TYPES = {
EARLY: 'early', // 0-25%
MID: 'mid', // 26-50%
LATE: 'late', // 51-75%
COMPLETED: 'completed' // 76-100%
}
// 默认气泡文本(当后端未返回时使用)
const DEFAULT_BUBBLE_TEXTS = ['加油!', '一起努力!', '继续前进!']
// ==================== API 调用函数 ====================
/**
* 获取活动列表
* @param {number} starId - 明星ID
* @param {string} status - 活动状态(可选)
* @param {number} page - 页码
* @param {number} pageSize - 每页数量
* @returns {Promise} 活动列表
*/
export async function fetchActivityList(starId, status = '', page = 1, pageSize = 10) {
try {
const response = await getActivityListApi(starId, status, page, pageSize)
if (response.code === 200 && response.data) {
return {
activities: response.data.activities || [],
page: response.data.page || page,
pageSize: response.data.page_size || pageSize,
total: response.data.total || 0
}
}
throw new Error(response.message || '获取活动列表失败')
} catch (error) {
console.error('fetchActivityList error:', error)
throw error
}
}
/**
* 获取活动详情
* @param {number} activityId - 活动ID
* @returns {Promise} 活动详情
*/
export async function fetchActivityDetail(activityId) {
try {
const response = await getActivityDetailApi(activityId)
if (response.code === 200 && response.data) {
return enrichActivityData(response.data)
}
throw new Error(response.message || '获取活动详情失败')
} catch (error) {
console.error('fetchActivityDetail error:', error)
throw error
}
}
/**
* 获取活动道具列表
* @param {number} activityId - 活动ID
* @returns {Promise} 道具列表
*/
export async function fetchActivityItems(activityId) {
try {
const response = await getActivityItemsApi(activityId)
if (response.code === 200 && response.data) {
return response.data.items || []
}
throw new Error(response.message || '获取道具列表失败')
} catch (error) {
console.error('fetchActivityItems error:', error)
throw error
}
}
/**
* 获取活动进度
* @param {number} activityId - 活动ID
* @returns {Promise} 进度信息
*/
export async function fetchActivityProgress(activityId) {
try {
const response = await getActivityProgressApi(activityId)
if (response.code === 200 && response.data) {
return response.data
}
throw new Error(response.message || '获取活动进度失败')
} catch (error) {
console.error('fetchActivityProgress error:', error)
throw error
}
}
/**
* 购买活动道具
* @param {number} activityId - 活动ID
* @param {string} itemType - 道具类型
* @param {number} quantity - 购买数量
* @returns {Promise} 购买结果
*/
export async function purchaseItem(activityId, itemType, quantity = 1) {
try {
const response = await purchaseActivityItemApi(activityId, itemType, quantity)
// 检查活动状态,只有 active 才能购买
if (response.code === 200 && response.data) {
if (response.data.activity_status && response.data.activity_status !== 'active') {
return {
success: false,
message: response.data.message || '活动不在进行中,无法购买'
}
}
return {
success: true,
totalCrystalSpent: response.data.total_crystal_spent,
totalContribution: response.data.total_contribution,
currentProgress: response.data.current_progress,
remainingBalance: response.data.remaining_balance
}
}
return {
success: false,
message: response.message || '购买道具失败'
}
} catch (error) {
console.error('purchaseItem error:', error)
return {
success: false,
message: error.message || '购买道具失败'
}
}
}
// ==================== 数据处理函数 ====================
/**
* 丰富活动数据(添加默认配置)
* @param {Object} activity - 后端返回的活动数据
* @returns {Object} 丰富后的活动数据
*/
function enrichActivityData(activity) {
const currentStage = activity.current_stage || calculateStage(activity.current_progress, activity.target_progress)
// 使用后端返回的数据,如果没有则使用默认值
const bubbleTexts = activity.bubble_texts || DEFAULT_BUBBLE_TEXTS
return {
...activity,
current_stage: currentStage,
bubble_texts: bubbleTexts,
// 转换道具数据格式
items: (activity.items || []).map(item => ({
id: item.id,
type: item.item_type,
label: item.item_name,
icon: item.icon_url,
cost: item.crystal_cost,
contributionPoints: item.contribution_points
}))
}
}
/**
* 根据进度计算当前阶段
* @param {number} current - 当前进度
* @param {number} target - 目标进度
* @returns {string} 阶段名称
*/
function calculateStage(current, target) {
if (current >= target) {
return STAGE_TYPES.COMPLETED
}
const progress = (current / target) * 100
if (progress <= 25) {
return STAGE_TYPES.EARLY
} else if (progress <= 50) {
return STAGE_TYPES.MID
} else if (progress <= 75) {
return STAGE_TYPES.LATE
} else {
return STAGE_TYPES.COMPLETED
}
}
/**
* 获取活动的时间状态描述
* @param {number} startTime - 开始时间 (秒级时间戳)
* @param {number} endTime - 结束时间 (秒级时间戳)
* @returns {Object} 状态信息
*/
export function getActivityTimeStatus(startTime, endTime) {
// 后端返回的是秒级时间戳,需要转换为毫秒
const startTimeMs = startTime * 1000
const endTimeMs = endTime * 1000
const now = Date.now()
if (now < startTimeMs) {
// 未开始
const diff = startTimeMs - now
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
if (days > 0) {
return {
status: 'pending',
text: `距开始还有 ${days}${hours} 小时`
}
} else {
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))
return {
status: 'pending',
text: `距开始还有 ${minutes} 分钟`
}
}
} else if (now > endTimeMs) {
// 已结束
return {
status: 'expired',
text: '活动已结束'
}
} else {
// 进行中
const diff = endTimeMs - now
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
if (days > 0) {
return {
status: 'active',
text: `还剩 ${days}${hours} 小时`
}
} else {
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))
return {
status: 'active',
text: `还剩 ${minutes} 分钟`
}
}
}
}