LatestActivitiesSection.vue 5.7 KB
<template>
    <section v-if="activities.length" class="mb-7">
        <div class="flex justify-between items-center mb-3">
            <h3 class="font-medium">最新活动</h3>
            <a href="https://wxm.behalo.cc/pages/activity/activity" target="_blank" class="text-xs text-gray-500 flex items-center">
                更多
                <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
                </svg>
            </a>
        </div>
        <div class="space-y-4">
            <div v-for="activity in activities.slice(0, 4)" :key="activity.id">
                <ActivityCard :activity="activity" />
            </div>
        </div>
    </section>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import ActivityCard from '@/components/activity/ActivityCard.vue'

const activities = ref([])

/**
 * @description 将任意值安全转换为数字
 * @param {any} v 原始值
 * @returns {number|null}
 */
const number_or_null = (v) => {
    if (v === null || v === undefined || v === '') return null
    const n = Number(v)
    return isNaN(n) ? null : n
}

/**
 * @description 仅格式化为日期字符串(YYYY-MM-DD)
 * @param {Date} d 日期对象
 * @returns {string}
 */
const format_date_only = (d) => {
    const y = d.getFullYear()
    const m = String(d.getMonth() + 1).padStart(2, '0')
    const day = String(d.getDate()).padStart(2, '0')
    return `${y}-${m}-${day}`
}

/**
 * @description 格式化活动时间区间
 * @param {string} start_str 活动开始时间
 * @param {string} end_str 活动结束时间
 * @returns {string}
 */
const format_period = (start_str, end_str) => {
    if (!start_str || !end_str) return ''
    const start = new Date(start_str)
    const end = new Date(end_str)
    if (isNaN(start.getTime()) || isNaN(end.getTime())) return ''
    return `${format_date_only(start)} 至 ${format_date_only(end)}`
}

/**
 * @description 计算报名状态
 * @param {string} stu_start_at 报名开始时间字符串
 * @param {string} stu_end_at 报名结束时间字符串
 * @param {Date} now 当前时间
 * @param {string} act_start_at 活动开始时间
 * @param {string} act_end_at 活动结束时间
 * @returns {string}
 */
const compute_enroll_status = (stu_start_at, stu_end_at, now, act_start_at, act_end_at) => {
    if (stu_start_at && stu_end_at) {
        const start = new Date(stu_start_at)
        const end = new Date(stu_end_at)
        if (!isNaN(start.getTime()) && !isNaN(end.getTime())) {
            if (now >= start && now <= end) return '报名中'
            if (now < start) return '即将开始'
            return '已结束'
        }
    }

    if (act_start_at && act_end_at) {
        const a_start = new Date(act_start_at)
        const a_end = new Date(act_end_at)
        if (!isNaN(a_start.getTime()) && !isNaN(a_end.getTime())) {
            if (now < a_start) return '报名中'
            if (now >= a_start && now <= a_end) return '进行中'
            if (now > a_end) return '已结束'
        }
    }

    return '即将开始'
}

/**
 * @description 获取并处理外部活动数据
 * @returns {Promise<void>}
 */
const fetch_external_activities = async () => {
    const url = 'https://bhapi.behalo.cc/api/get_act/?city_name=&pub_status=1&page_idx=1&page_size=300&search_option=4'
    try {
        const resp = await fetch(url, { method: 'GET' })
        const json = await resp.json()

        const list = Array.isArray(json)
            ? json
            : Array.isArray(json?.data?.list)
                ? json.data.list
                : Array.isArray(json?.list)
                    ? json.list
                    : Array.isArray(json?.rows)
                        ? json.rows
                        : []

        const now = new Date()
        const mapped = list.map((item) => {
            const xs_price = number_or_null(item?.xs_price)
            const sc_price = number_or_null(item?.sc_price)
            let imageUrl = item?.sl_img || item?.fx_img || item?.banner_img || 'https://cdn.ipadbiz.cn/mlaj/images/default_block.png'
            // 压缩阿里云图片资源
            imageUrl = imageUrl+ '?x-oss-process=image/quality,q_70/resize,w_100'
            const period = format_period(item?.act_start_at, item?.act_end_at)

            const upper = number_or_null(item?.stu_num_upper)
            const gap = number_or_null(item?.bookgap_info?.stu_gap)
            const participantsCount = upper != null && gap != null ? Math.max(upper - gap, 0) : null

            const status = compute_enroll_status(
                item?.stu_start_at,
                item?.stu_end_at,
                now,
                item?.act_start_at,
                item?.act_end_at
            )

            return {
                id: item?.act_id,
                title: item?.act_title || '',
                imageUrl,
                isHot: false,
                isFree: false,
                location: item?.act_address || item?.city_name || '',
                period,
                price: xs_price != null ? xs_price : '',
                originalPrice: sc_price != null && sc_price > 0 ? sc_price : '',
                participantsCount: participantsCount != null ? participantsCount : '',
                maxParticipants: upper != null ? upper : '',
                mock_link: 'https://wxm.behalo.cc/pages/activity/info?type=2&id=' + item?.act_id,
                status
            }
        })

        activities.value = mapped.filter((a) => a.status === '报名中')
    } catch (err) {
        activities.value = []
    }
}

onMounted(async () => {
    await fetch_external_activities()
})
</script>