225 lines
8.6 KiB
Vue
225 lines
8.6 KiB
Vue
<template>
|
|
<view class="page">
|
|
<view class="notice-bar">
|
|
<swiper class="notice-swiper" vertical circular autoplay interval="3000" duration="300">
|
|
<swiper-item v-for="n in displayNotices" :key="n.id">
|
|
<view class="notice-item">{{ n.text }}</view>
|
|
</swiper-item>
|
|
</swiper>
|
|
</view>
|
|
|
|
<view class="banner-box">
|
|
<swiper class="banner-swiper" circular autoplay interval="4000" duration="500">
|
|
<swiper-item v-for="b in displayBanners" :key="b.id">
|
|
<image v-if="b.image" class="banner-image" :src="b.image" mode="aspectFill" @tap="onBannerTap(b)" />
|
|
<view v-else class="banner-fallback">
|
|
<text class="banner-fallback-text">{{ b.title || '敬请期待' }}</text>
|
|
</view>
|
|
</swiper-item>
|
|
</swiper>
|
|
</view>
|
|
|
|
<view class="activity-section">
|
|
<view class="section-title">活动</view>
|
|
<view v-if="activities.length" class="activity-grid">
|
|
<view class="activity-item" v-for="a in activities" :key="a.id" @tap="onActivityTap(a)">
|
|
<image v-if="a.image" class="activity-thumb" :src="a.image" mode="aspectFill" />
|
|
<view v-else class="banner-fallback">
|
|
<text class="banner-fallback-text">{{ a.title || '活动敬请期待' }}</text>
|
|
</view>
|
|
<text class="activity-name">{{ a.title }}</text>
|
|
<text class="activity-desc" v-if="a.subtitle">{{ a.subtitle }}</text>
|
|
</view>
|
|
</view>
|
|
<view v-else class="activity-empty">暂无活动</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import { authRequest, request } from '../../utils/request.js'
|
|
export default {
|
|
data() {
|
|
return {
|
|
notices: [],
|
|
banners: [],
|
|
activities: []
|
|
}
|
|
},
|
|
computed: {
|
|
displayNotices() {
|
|
if (Array.isArray(this.notices) && this.notices.length) return this.notices
|
|
return [
|
|
{ id: 'n1', text: '欢迎光临' },
|
|
{ id: 'n2', text: '最新活动敬请期待' }
|
|
]
|
|
},
|
|
displayBanners() {
|
|
if (Array.isArray(this.banners) && this.banners.length) return this.banners
|
|
return [
|
|
{ id: 'ph-1', title: '精彩内容即将上线', image: '' },
|
|
{ id: 'ph-2', title: '敬请期待', image: '' },
|
|
{ id: 'ph-3', title: '更多活动请关注', image: '' }
|
|
]
|
|
}
|
|
},
|
|
onShow() {
|
|
const token = uni.getStorageSync('token')
|
|
const phoneBound = !!uni.getStorageSync('phone_bound')
|
|
if (!token || !phoneBound) {
|
|
uni.showModal({
|
|
title: '提示',
|
|
content: '请先登录并绑定手机号',
|
|
confirmText: '去登录',
|
|
success: (res) => {
|
|
if (res.confirm) {
|
|
uni.navigateTo({ url: '/pages/login/index' })
|
|
}
|
|
}
|
|
})
|
|
}
|
|
try { console.log('home onShow', { token: !!token, phoneBound }) } catch (_) {}
|
|
this.loadHomeData()
|
|
},
|
|
methods: {
|
|
toArray(x) { return Array.isArray(x) ? x : [] },
|
|
unwrap(list) {
|
|
if (Array.isArray(list)) return list
|
|
const obj = list || {}
|
|
const arr = obj.list || obj.items || obj.data || []
|
|
return Array.isArray(arr) ? arr : []
|
|
},
|
|
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()
|
|
},
|
|
apiGet(url) {
|
|
const token = uni.getStorageSync('token')
|
|
const fn = token ? authRequest : request
|
|
return fn({ url })
|
|
},
|
|
normalizeNotices(list) {
|
|
const arr = this.unwrap(list)
|
|
return arr.map((i, idx) => ({
|
|
id: i.id ?? String(idx),
|
|
text: i.content ?? i.text ?? i.title ?? ''
|
|
})).filter(i => i.text)
|
|
},
|
|
normalizeBanners(list) {
|
|
const arr = this.unwrap(list)
|
|
console.log('normalizeBanners input', list, 'unwrapped', arr)
|
|
const mapped = arr.map((i, idx) => ({
|
|
id: i.id ?? String(idx),
|
|
title: i.title ?? '',
|
|
image: this.cleanUrl(i.imageUrl ?? i.image_url ?? i.image ?? i.img ?? i.pic ?? ''),
|
|
link: this.cleanUrl(i.linkUrl ?? i.link_url ?? i.link ?? i.url ?? ''),
|
|
sort: typeof i.sort === 'number' ? i.sort : 0
|
|
})).filter(i => i.image)
|
|
mapped.sort((a, b) => a.sort - b.sort)
|
|
console.log('normalizeBanners mapped', mapped)
|
|
return mapped
|
|
},
|
|
normalizeActivities(list) {
|
|
const arr = this.unwrap(list)
|
|
console.log('normalizeActivities input', list, 'unwrapped', arr)
|
|
const mapped = arr.map((i, idx) => ({
|
|
id: i.id ?? String(idx),
|
|
image: this.cleanUrl(i.banner ?? i.coverUrl ?? i.cover_url ?? i.image ?? i.img ?? i.pic ?? ''),
|
|
title: i.title ?? i.name ?? '',
|
|
subtitle: this.buildActivitySubtitle(i),
|
|
link: this.cleanUrl(i.linkUrl ?? i.link_url ?? i.link ?? i.url ?? ''),
|
|
category_name: (i.category_name ?? i.categoryName ?? '').trim(),
|
|
category_id: i.activity_category_id ?? i.category_id ?? i.categoryId ?? null
|
|
})).filter(i => i.image || i.title)
|
|
console.log('normalizeActivities mapped', mapped)
|
|
return mapped
|
|
},
|
|
buildActivitySubtitle(i) {
|
|
const base = i.subTitle ?? i.sub_title ?? i.subtitle ?? i.desc ?? i.description ?? ''
|
|
if (base) return base
|
|
const cat = i.category_name ?? i.categoryName ?? ''
|
|
const price = (i.price_draw !== undefined && i.price_draw !== null) ? `¥${i.price_draw}` : ''
|
|
const parts = [cat, price].filter(Boolean)
|
|
return parts.join(' · ')
|
|
},
|
|
async loadHomeData() {
|
|
const results = await Promise.allSettled([
|
|
this.apiGet('/api/app/notices'),
|
|
this.apiGet('/api/app/banners'),
|
|
this.apiGet('/api/app/activities')
|
|
])
|
|
const [nRes, bRes, acRes] = results
|
|
if (nRes.status === 'fulfilled') {
|
|
console.log('notices ok', nRes.value)
|
|
this.notices = this.normalizeNotices(nRes.value)
|
|
} else {
|
|
console.error('notices error', nRes.reason)
|
|
this.notices = []
|
|
}
|
|
if (bRes.status === 'fulfilled') {
|
|
console.log('banners ok', bRes.value)
|
|
this.banners = this.normalizeBanners(bRes.value)
|
|
} else {
|
|
console.error('banners error', bRes.reason)
|
|
this.banners = []
|
|
}
|
|
if (acRes.status === 'fulfilled') {
|
|
console.log('activities ok', acRes.value)
|
|
this.activities = this.normalizeActivities(acRes.value)
|
|
} else {
|
|
console.error('activities error', acRes.reason)
|
|
this.activities = []
|
|
}
|
|
console.log('home normalized', { notices: this.notices, banners: this.banners, activities: this.activities })
|
|
},
|
|
onBannerTap(b) {
|
|
const imgs = (Array.isArray(this.banners) ? this.banners : []).map(x => x.image).filter(Boolean)
|
|
const current = b && b.image
|
|
if (current) {
|
|
uni.previewImage({ urls: imgs.length ? imgs : [current], current })
|
|
return
|
|
}
|
|
if (b.link && /^\/.+/.test(b.link)) {
|
|
uni.navigateTo({ url: b.link })
|
|
}
|
|
},
|
|
onActivityTap(a) {
|
|
const name = (a.category_name || a.categoryName || '').trim()
|
|
const id = a.id
|
|
let path = ''
|
|
if (name === '一番赏') path = '/pages/activity/yifanshang/index'
|
|
else if (name === '无限赏') path = '/pages/activity/wuxianshang/index'
|
|
else if (name === '对对碰') path = '/pages/activity/duiduipeng/index'
|
|
if (path && id) {
|
|
uni.navigateTo({ url: `${path}?id=${id}` })
|
|
return
|
|
}
|
|
if (a.link && /^\/.+/.test(a.link)) {
|
|
uni.navigateTo({ url: a.link })
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.page { padding: 24rpx }
|
|
.notice-bar { height: 64rpx; background: #fff4e6; border-radius: 8rpx; overflow: hidden; margin-bottom: 24rpx; }
|
|
.notice-swiper { height: 64rpx }
|
|
.notice-item { height: 64rpx; line-height: 64rpx; padding: 0 24rpx; color: #a15c00; font-size: 26rpx }
|
|
.banner-box { margin-bottom: 24rpx }
|
|
.banner-swiper { width: 100%; height: 320rpx; border-radius: 12rpx; overflow: hidden }
|
|
.banner-image { width: 100%; height: 320rpx }
|
|
.banner-fallback { width: 100%; height: 320rpx; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #f5f5f5, #eaeaea) }
|
|
.banner-fallback-text { color: #666; font-size: 28rpx }
|
|
.activity-section { background: #ffffff; border-radius: 12rpx; padding: 24rpx }
|
|
.section-title { font-size: 30rpx; font-weight: 600; margin-bottom: 16rpx }
|
|
.activity-grid { display: flex; flex-wrap: wrap; margin: -12rpx }
|
|
.activity-item { width: 50%; padding: 12rpx }
|
|
.activity-thumb { width: 100%; height: 200rpx; border-radius: 8rpx }
|
|
.activity-name { display: block; margin-top: 8rpx; font-size: 26rpx; color: #222 }
|
|
.activity-desc { display: block; margin-top: 4rpx; font-size: 22rpx; color: #888 }
|
|
</style>
|