LatestActivitiesSection.vue
5.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
<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/ui/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)
const imageUrl = item?.sl_img || item?.fx_img || item?.banner_img || 'https://cdn.ipadbiz.cn/mlaj/images/default_block.png'
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>