303 lines
7.8 KiB
Vue
303 lines
7.8 KiB
Vue
<template>
|
|
<view class="page">
|
|
<scroll-view class="content" scroll-y>
|
|
<view v-if="loading" class="loading-wrap"><view class="spinner"></view></view>
|
|
<view v-else-if="filteredActivities.length > 0" class="activity-grid">
|
|
<view class="activity-item" v-for="a in filteredActivities" :key="a.id" @tap="onActivityTap(a)">
|
|
<view class="thumb-box">
|
|
<image class="thumb" :src="a.image" mode="aspectFill" />
|
|
<view class="tag-hot">HOT</view>
|
|
</view>
|
|
<view class="info">
|
|
<view class="name">{{ a.title }}</view>
|
|
<view class="bottom-row">
|
|
<text class="price-text">{{ a.category_name }} · {{ a.subtitle }}</text>
|
|
<view class="btn-go">GO</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<view v-else class="empty">
|
|
<image class="empty-img" src="/static/empty.png" mode="widthFix" />
|
|
<text class="empty-text">暂无{{ title }}活动</text>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed } from 'vue'
|
|
import { onLoad } from '@dcloudio/uni-app'
|
|
import { request, authRequest } from '@/utils/request.js'
|
|
|
|
const title = ref('')
|
|
const categoryTarget = ref('')
|
|
const activities = ref([])
|
|
const loading = ref(false)
|
|
|
|
const filteredActivities = computed(() => {
|
|
if (!categoryTarget.value) return activities.value
|
|
const target = categoryTarget.value.trim()
|
|
return activities.value.filter(a => {
|
|
const cat = (a.category_name || '').trim()
|
|
return cat === target || cat.includes(target)
|
|
})
|
|
})
|
|
|
|
function apiGet(url) {
|
|
const token = uni.getStorageSync('token')
|
|
const fn = token ? authRequest : request
|
|
return fn({ url })
|
|
}
|
|
|
|
function cleanUrl(u) {
|
|
const s = String(u || '').trim()
|
|
const m = s.match(/https?:\/\/[^\s'"`]+/)
|
|
if (m && m[0]) return m[0]
|
|
return s.replace(/[`'\"]/g, '').trim()
|
|
}
|
|
|
|
function buildSubtitle(i) {
|
|
const base = i.subTitle ?? i.sub_title ?? i.subtitle ?? i.desc ?? i.description ?? ''
|
|
if (base) return base
|
|
const price = (i.price_draw !== undefined && i.price_draw !== null) ? `¥${(Number(i.price_draw || 0) / 100).toFixed(2)}` : ''
|
|
return price
|
|
}
|
|
|
|
async function loadData() {
|
|
loading.value = true
|
|
try {
|
|
const res = await apiGet('/api/app/activities')
|
|
let list = []
|
|
if (Array.isArray(res)) list = res
|
|
else if (res && (Array.isArray(res.list) || Array.isArray(res.data))) list = res.list || res.data
|
|
|
|
activities.value = list.map((i, idx) => ({
|
|
id: i.id ?? String(idx),
|
|
image: cleanUrl(i.image ?? i.banner ?? i.coverUrl ?? i.cover_url ?? i.img ?? i.pic ?? ''),
|
|
title: (i.title ?? i.name ?? '').replace(/无限赏|一番赏|对对碰|爬塔/g, '').trim(),
|
|
subtitle: buildSubtitle(i),
|
|
category_name: i.category_name ?? i.categoryName ?? '',
|
|
link: cleanUrl(i.linkUrl ?? i.link_url ?? i.link ?? i.url ?? '')
|
|
})).filter(i => i.image || i.title)
|
|
|
|
} catch (e) {
|
|
activities.value = []
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function onActivityTap(a) {
|
|
const name = (a.category_name || '').trim()
|
|
const id = a.id
|
|
let path = ''
|
|
|
|
// Navigate to DETAIL, not list
|
|
if (name.includes('一番赏')) path = '/pages-activity/activity/yifanshang/index'
|
|
else if (name.includes('无限赏')) path = '/pages-activity/activity/wuxianshang/index'
|
|
else if (name.includes('对对碰')) path = '/pages-activity/activity/duiduipeng/index'
|
|
else if (name.includes('爬塔')) path = '/pages-activity/activity/pata/index'
|
|
|
|
if (path && id) {
|
|
uni.navigateTo({ url: `${path}?id=${id}` })
|
|
return
|
|
}
|
|
if (a.link && /^\/.+/.test(a.link)) {
|
|
uni.navigateTo({ url: a.link })
|
|
}
|
|
}
|
|
|
|
onLoad((opts) => {
|
|
if (opts && opts.category) {
|
|
categoryTarget.value = decodeURIComponent(opts.category)
|
|
title.value = categoryTarget.value
|
|
uni.setNavigationBarTitle({ title: categoryTarget.value })
|
|
}
|
|
loadData()
|
|
})
|
|
|
|
// 分享功能
|
|
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
|
|
|
onShareAppMessage(() => {
|
|
const inviteCode = uni.getStorageSync('invite_code') || (uni.getStorageSync('user_info') || {}).invite_code || ''
|
|
return {
|
|
title: `${title.value || '精彩活动'} - 柯大鸭潮玩`,
|
|
path: `/pages/index/index?invite_code=${inviteCode}`,
|
|
imageUrl: '/static/logo.png'
|
|
}
|
|
})
|
|
|
|
onShareTimeline(() => {
|
|
const inviteCode = uni.getStorageSync('invite_code') || (uni.getStorageSync('user_info') || {}).invite_code || ''
|
|
return {
|
|
title: `${title.value || '精彩活动'} - 柯大鸭潮玩`,
|
|
query: `invite_code=${inviteCode}`,
|
|
imageUrl: '/static/logo.png'
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.page {
|
|
min-height: 100vh;
|
|
background: $bg-page;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.content {
|
|
flex: 1;
|
|
padding: $spacing-lg;
|
|
}
|
|
.activity-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: $spacing-md;
|
|
}
|
|
.activity-item {
|
|
background: #fff;
|
|
border-radius: $radius-lg;
|
|
overflow: hidden;
|
|
box-shadow: $shadow-sm;
|
|
display: flex;
|
|
flex-direction: column;
|
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
position: relative;
|
|
|
|
&:active {
|
|
transform: scale(0.98);
|
|
box-shadow: $shadow-xs;
|
|
}
|
|
}
|
|
|
|
.thumb-box {
|
|
position: relative;
|
|
width: 100%;
|
|
padding-top: 100%; /* 1:1 Aspect Ratio */
|
|
height: 0;
|
|
background: $bg-secondary;
|
|
}
|
|
.thumb {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
background: linear-gradient(90deg, $bg-secondary 25%, #e8e8e8 50%, $bg-secondary 75%);
|
|
background-size: 200% 100%;
|
|
animation: shimmer 1.5s infinite;
|
|
}
|
|
.tag-hot {
|
|
position: absolute;
|
|
top: 16rpx; left: 16rpx;
|
|
background: rgba(255, 69, 58, 0.9);
|
|
color: #fff;
|
|
font-size: 20rpx;
|
|
padding: 6rpx 14rpx;
|
|
border-radius: 8rpx;
|
|
font-weight: 800;
|
|
letter-spacing: 1rpx;
|
|
box-shadow: 0 4rpx 12rpx rgba(255, 69, 58, 0.3);
|
|
backdrop-filter: blur(4rpx);
|
|
}
|
|
|
|
.info {
|
|
padding: 20rpx 20rpx;
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex: 1;
|
|
justify-content: space-between;
|
|
}
|
|
.name {
|
|
font-size: 28rpx;
|
|
font-weight: 700;
|
|
color: $text-main;
|
|
margin-bottom: 16rpx;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
line-height: 1.4;
|
|
height: 80rpx;
|
|
}
|
|
.bottom-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-end;
|
|
}
|
|
.price-text {
|
|
font-size: 22rpx;
|
|
color: $text-secondary;
|
|
max-width: 70%;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.btn-go {
|
|
background: $brand-primary;
|
|
color: #fff;
|
|
font-size: 20rpx;
|
|
font-weight: 900;
|
|
padding: 6rpx 20rpx;
|
|
border-radius: 100rpx;
|
|
box-shadow: 0 4rpx 10rpx rgba($brand-primary, 0.3);
|
|
transition: all 0.2s ease;
|
|
|
|
&:active {
|
|
transform: scale(0.9);
|
|
background: darken($brand-primary, 5%);
|
|
}
|
|
}
|
|
|
|
.loading-wrap {
|
|
display: flex; justify-content: center; padding: 100rpx;
|
|
}
|
|
.spinner {
|
|
width: 48rpx; height: 48rpx;
|
|
border: 4rpx solid $border-color-light;
|
|
border-top-color: $brand-primary;
|
|
border-radius: 50%;
|
|
animation: spin 0.8s linear infinite;
|
|
}
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
|
.empty {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding-top: 200rpx;
|
|
animation: fadeInUp 0.5s ease-out;
|
|
}
|
|
.empty-img {
|
|
width: 240rpx;
|
|
margin-bottom: 24rpx;
|
|
opacity: 0.5;
|
|
}
|
|
.empty-text {
|
|
color: $text-secondary;
|
|
font-size: 28rpx;
|
|
}
|
|
|
|
/* ============================================
|
|
动画增强
|
|
============================================ */
|
|
@keyframes shimmer {
|
|
0% { background-position: -200% 0; }
|
|
100% { background-position: 200% 0; }
|
|
}
|
|
|
|
/* 卡片交错入场 */
|
|
@for $i from 1 through 10 {
|
|
.activity-item:nth-child(#{$i}) {
|
|
animation: fadeInUp 0.4s ease-out #{$i * 0.05}s both;
|
|
}
|
|
}
|
|
|
|
@keyframes fadeInUp {
|
|
from { opacity: 0; transform: translateY(20rpx); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
</style>
|