zdtap-uniapp-main/pages/index/brandHome.vue

1123 lines
26 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page-content">
<!-- 自定义导航栏 -->
<view class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-content">
<view class="back-btn" @click="goBack">
<text class="cuIcon-back"></text>
</view>
</view>
</view>
<!-- 品牌广告图片 -->
<view class="banner-container">
<image class="banner" :src="breweryInfo.brandCover" mode="aspectFill"></image>
<view class="banner-overlay"></view>
</view>
<!-- 品牌信息卡片 -->
<view class="brand-header">
<view class="brand-info">
<view class="flex align-start justify-between">
<view class="flex align-center">
<image :src="breweryInfo.brandLogo" class="brand-logo" mode="aspectFill"></image>
<view class="flex flex-col">
<text class="brand-name">{{ breweryInfo.brandName}}</text>
<view class="brand-location">
<text class="cuIcon-location"></text>
<text>{{breweryInfo.country}}·{{breweryInfo.province}}·{{breweryInfo.city}}</text>
</view>
</view>
</view>
<view class="action-buttons">
<view class="action-btn favor-btn" @click="favorBreweryFun(1)" v-if="!isFavor">
<image src="/static/heart-add.png" class="action-icon" mode="aspectFit"></image>
</view>
<view class="action-btn favor-btn active" @click="favorBreweryFun(2)" v-else>
<image src="/static/heart-tick.png" class="action-icon" mode="aspectFit"></image>
</view>
<view class="action-btn share-btn" @click="share">
<image src="/static/send.png" class="action-icon" mode="aspectFit"></image>
</view>
</view>
</view>
<view class="brand-stats">
<view class="stat-item">
<text class="stat-value">{{ beerTotal}}</text>
<text class="stat-label">在售酒款</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-value">{{ breweryInfo.buildingDate && breweryInfo.buildingDate.substr(0,4)}}</text>
<text class="stat-label">since</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-value">{{myCoin || '--'}}</text>
<text class="stat-label">啤酒币</text>
</view>
</view>
<view class="brand-desc-container">
<view class="brand-desc">{{breweryInfo.desc}}</view>
</view>
</view>
</view>
<!-- 标签页导航 -->
<view class="tab-nav">
<view
class="tab-item"
:class="{'active':currentTab == 1}"
@click="changeTab(1)"
>
<text>在售酒款</text>
</view>
<view
class="tab-item"
:class="{'active':currentTab == 2}"
@click="changeTab(2)"
>
<text>累积活动</text>
</view>
<view
class="tab-item"
:class="{'active':currentTab == 3}"
@click="changeTab(3)"
>
<text>啤酒币兑换</text>
</view>
</view>
<!-- 内容区域 -->
<view class="content-box">
<!-- 在售酒款 -->
<view v-if="currentTab == 1" class="tab-content">
<scroll-view
scroll-y
enable-flex
class="scroll-container"
@scrolltolower="onScrollToLower"
>
<view class="beer-grid">
<beer-card
v-for="(item, index) in beerList"
:key="index"
:item="item"
@click="toReview(item)"
v-if="item && item.id"
></beer-card>
</view>
<view
class="loading-state"
:class="loading ? 'loading' : beerList.length >= beerTotal ? 'no-more' : ''"
v-if="beerList.length > 0"
>
<view v-if="loading" class="loading-text">
<text class="cuIcon-loading2 iconfont-spin"></text>
<text>加载中...</text>
</view>
<text v-else-if="beerList.length >= beerTotal" class="no-more-text">没有更多了</text>
</view>
<view class="empty-state" v-if="!loading && beerList.length === 0">
<image src="/static/empty.png" mode="aspectFit" class="empty-image"></image>
<text class="empty-text">暂无在售酒款</text>
</view>
</scroll-view>
</view>
<!-- 累积活动 -->
<view v-if="currentTab == 2" class="tab-content">
<scroll-view
scroll-y
enable-flex
class="scroll-container"
@scrolltolower="onScrollToLower"
>
<!-- 空状态 -->
<view v-if="!loading && activityList.length === 0" class="empty-state">
<image src="/static/images/empty.png" class="empty-image" mode="aspectFit"></image>
<text class="empty-text">暂无活动</text>
</view>
<!-- 活动列表 -->
<view class="activity-list" v-else>
<activity-item
v-for="(item, index) in activityList"
:key="index"
:item="item"
@click="toActivityDetail"
@review="toReview"
></activity-item>
</view>
<!-- 加载状态 -->
<view
class="loading-state"
:class="loading ? 'loading' : activityList.length >= activityTotal ? 'no-more' : ''"
v-if="activityList.length > 0"
>
<view v-if="loading" class="loading-text">
<text class="cuIcon-loading2 iconfont-spin"></text>
<text>加载中...</text>
</view>
<text v-else-if="activityList.length >= activityTotal" class="no-more-text">没有更多了</text>
</view>
</scroll-view>
</view>
<!-- 啤酒币兑换 -->
<view v-if="currentTab == 3" class="tab-content">
<scroll-view
scroll-y
enable-flex
class="scroll-container"
@scrolltolower="onScrollToLower"
>
<view class="goods-grid">
<view
class="goods-item"
v-for="(item, index) in goodsList"
:key="index"
@click="toDetail(item)"
>
<image class="goods-image" :src="item.goodsCover" mode="aspectFill"></image>
<view class="goods-info">
<view class="goods-title text-cut">{{item.goodsName}}</view>
<view class="goods-price">
<view class="price-left">
<text class="price-num">{{item.redeemedNum}}</text>
<text class="cuIcon-rechargefill coin-icon"></text>
</view>
<text class="brand-name">{{item.brandName}}</text>
</view>
</view>
</view>
</view>
<view
class="loading-state"
:class="loading ? 'loading' : goodsList.length >= goodsTotal ? 'no-more' : ''"
v-if="goodsList.length > 0"
>
<view v-if="loading" class="loading-text">
<text class="cuIcon-loading2 iconfont-spin"></text>
<text>加载中...</text>
</view>
<text v-else-if="goodsList.length >= goodsTotal" class="no-more-text">没有更多了</text>
</view>
<view class="empty-state" v-if="!loading && goodsList.length === 0">
<image src="/static/empty.png" mode="aspectFit" class="empty-image"></image>
<text class="empty-text">暂无兑换商品</text>
</view>
</scroll-view>
</view>
</view>
<!-- 登录弹窗组件 -->
<loginPopup ref="loginRef" @loginSuccess="loginSuccess"></loginPopup>
</view>
</template>
<script>
import {
getBreweryInfo,
getBreweryActivities,
getBreweryBeerList,
getBreweryGoodsList,
favorBrewery,
getBreweryFavorStatus,
getBreweryCoinBalance
} from '@/api/bar.js'
import loginPopup from '@/components/loginPopup.vue';
import ActivityItem from '@/components/ActivityItem.vue';
import BeerCard from '@/components/BeerCard.vue';
export default {
components: {
loginPopup,
ActivityItem,
BeerCard
},
data() {
return {
statusBarHeight: 0, // 状态栏高度
showTabNav: false,
scrollTop: 0,
breweryId: '',
currentTab: 1,
breweryInfo: {}, // 品牌方详情
activityList: [],
goodsList: [],
beerList: [],
loading: false,
beerTotal: 0, // 在售酒款总数
goodsTotal: 0, // 兑换商品总数
activityTotal: 0, // 活动总数
total: 0,
formattedDate: '', // 当前年月日
isFavor: false, // 是否收藏
myCoin: 0, // 我的啤酒币
queryForm: {
pageNum: 1,
pageSize: 5,
id: ''
},
};
},
onLoad({
breweryId
}) {
// 获取状态栏高度
this.statusBarHeight = uni.getSystemInfoSync().statusBarHeight
this.breweryId = breweryId
this.queryForm.id = breweryId
this.getBreweryInfoFun()
this.getBreweryActivitiesFun()
this.getBreweryBeerListFun()
this.getBreweryGoodsListFun()
this.getDate()
this.getBreweryFavorStatusFun()
this.getBreweryCoinBalanceFun() // 啤酒币余额
},
methods: {
// 返回上一页
goBack() {
uni.navigateBack()
},
changeTab(index) {
this.currentTab = index
this.queryForm.pageNum = 1
if (index == 1) {
this.beerList = []
this.getBreweryBeerListFun()
} else if (index == 2) {
this.activityList = []
this.getBreweryActivitiesFun()
} else if (index == 3) {
this.goodsList = []
this.getBreweryGoodsListFun()
}
},
// 登录成功回调
loginSuccess() {
this.getBreweryFavorStatusFun()
this.getBreweryCoinBalanceFun()
},
share() {
uni.downloadFile({
url: this.breweryInfo.brandCover,
success: (res) => {
console.log(res)
// #ifdef MP-WEIXIN
uni.showShareImageMenu({
provider: 'weixin',
// path: '/pages/index/featureInfo?id=' + this.id,
path: res.tempFilePath,
shareType: 0,
success: (res) => {
console.log(res)
},
fail: (err) => {
console.log(err)
}
})
// #endif
}
})
},
// 啤酒币余额
getBreweryCoinBalanceFun() {
getBreweryCoinBalance(this.breweryId).then(res => {
if (res.data) {
this.myCoin = res.data.balance || 0
} else {
this.myCoin = 0
}
}).catch(err => {
console.error('获取啤酒币余额失败:', err)
this.myCoin = 0
})
},
// 收藏品牌方状态
getBreweryFavorStatusFun() {
getBreweryFavorStatus(this.breweryId).then(res => {
if (res.data) {
this.isFavor = true
} else {
this.isFavor = false
}
}).catch(err => {
console.error('获取收藏状态失败:', err)
uni.showToast({
title: err.msg || '获取收藏状态失败,请稍后重试',
icon: 'none'
})
})
},
// 获取当前年月日
getDate() {
const currentDate = new Date();
// 获取年份
const year = currentDate.getFullYear();
// 获取月份(注意:月份是从 0 开始计数的,所以要加 1
const month = String(currentDate.getMonth() + 1).padStart(2, '0');
// 获取日期
const day = String(currentDate.getDate()).padStart(2, '0');
// 格式化日期为 YYYY-MM-DD 格式
this.formattedDate = `${year}-${month}-${day}`;
},
// 计算剩余天数
getRemainingDays(date) {
const targetDate = new Date(date);
const currentDate = new Date();
const timeDiff = targetDate.getTime() - currentDate.getTime();
const remainingDays = Math.ceil(timeDiff / (1000 * 3600 * 24));
return remainingDays;
},
// 品牌方详情
getBreweryInfoFun() {
getBreweryInfo(this.breweryId).then(res => {
this.breweryInfo = res.data
}).catch(err => {
console.error('获取品牌方详情失败:', err)
uni.showToast({
title: err.msg || '获取品牌方详情失败,请稍后重试',
icon: 'none'
})
})
},
// 累积活动
getBreweryActivitiesFun() {
this.loading = true
getBreweryActivities(this.queryForm).then(res => {
this.loading = false
this.activityTotal = res.total
if (res.rows && res.rows.length > 0) {
let arr = res.rows.map(it => {
it.remainingDays = this.getRemainingDays(it.endDate)
return it
})
this.activityList = [...this.activityList, ...arr]
}
}).catch(err => {
console.error('获取累积活动失败:', err)
uni.showToast({
title: err.msg || '获取活动列表失败,请稍后重试',
icon: 'none'
})
this.loading = false
})
},
// 在售酒款
getBreweryBeerListFun() {
this.loading = true
getBreweryBeerList(this.queryForm).then(res => {
this.loading = false
this.beerTotal = res.total
if (res.rows && res.rows.length > 0) {
this.beerList = [...this.beerList, ...res.rows.filter(item => item && item.id)]
}
}).catch(err => {
console.error('获取在售酒款失败:', err)
uni.showToast({
title: err.msg || '获取酒款列表失败,请稍后重试',
icon: 'none'
})
this.loading = false
})
},
// 兑换商品
getBreweryGoodsListFun() {
this.loading = true
getBreweryGoodsList(this.queryForm).then(res => {
this.loading = false
this.goodsTotal = res.total
if (res.rows && res.rows.length > 0) {
this.goodsList = [...this.goodsList, ...res.rows]
}
}).catch(err => {
console.error('获取兑换商品失败:', err)
uni.showToast({
title: err.msg || '获取商品列表失败,请稍后重试',
icon: 'none'
})
this.loading = false
})
},
// 滚动加载更多
onScrollToLower() {
if (this.loading) return
if (this.currentTab === 1 && this.activityList.length < this.activityTotal) {
this.queryForm.pageNum++
this.getBreweryActivitiesFun()
} else if (this.currentTab === 2 && this.beerList.length < this.beerTotal) {
this.queryForm.pageNum++
this.getBreweryBeerListFun()
} else if (this.currentTab === 3 && this.goodsList.length < this.goodsTotal) {
this.queryForm.pageNum++
this.getBreweryGoodsListFun()
}
},
// 收藏品牌方
favorBreweryFun(status) {
const token = uni.getStorageSync('token')
if (!token) {
this.$refs.loginRef.open()
return
}
// 检查认证状态
const barInfo = uni.getStorageSync('barInfo')
if (!barInfo) {
uni.showModal({
title: '提示',
content: '请先认证门店',
showCancel: true,
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/index/registration'
})
}
}
})
return
}
// 处理不同的认证状态
if (barInfo.authState === 0) {
uni.showModal({
title: '提示',
content: '请先认证门店',
showCancel: true,
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/index/registration'
})
}
}
})
return
} else if (barInfo.authState === 2) {
uni.showToast({
title: '您的门店正在认证中,请耐心等待',
icon: 'none'
})
return
}
let data = {
breweryId: this.breweryId,
status
}
favorBrewery(data).then(res => {
if (status == 1) {
this.isFavor = true
uni.showToast({
title: '收藏成功',
icon: 'success'
})
} else {
this.isFavor = false
uni.showToast({
title: '取消收藏',
icon: 'none'
})
}
}).catch(err => {
if (err.code === 500 && err.msg.includes('门店未认证')) {
uni.showModal({
title: '提示',
content: '请先认证门店',
showCancel: true,
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/index/registration'
})
}
}
})
} else {
uni.showToast({
title: err.msg || '操作失败',
icon: 'none'
})
}
})
},
// 跳转详情
toDetail(item) {
uni.navigateTo({
url: "/pagesCoin/goodsDetail?id=" + item.id + "&breweryId=" + item.breweryId
})
},
// 跳转活动详情
toActivityDetail(item) {
uni.navigateTo({
url: "/pagesActivity/activityDetail?id=" + item.id
})
},
// 跳转酒评
toReview(it) {
const token = uni.getStorageSync('token')
if (!token) {
this.$refs.loginRef.open()
return
}
uni.navigateTo({
url: "/pages/index/review?beerId=" + it.id
})
},
onPageScroll(e) {
const tabNavThreshold = 300; // 设置一个阈值,当滚动超过这个值时显示导航
this.scrollTop = e.scrollTop;
this.showTabNav = this.scrollTop > tabNavThreshold;
},
},
}
</script>
<style lang="scss" scoped>
.page-content {
min-height: 100vh;
background-color: #F6F7FB;
position: relative;
/* 导航栏样式 */
.custom-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background: transparent;
.nav-content {
height: 44px;
display: flex;
align-items: center;
padding: 0 32rpx;
.back-btn {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.9);
border-radius: 50%;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
.cuIcon-back {
font-size: 36rpx;
color: #333;
}
}
}
}
/* 品牌广告图片 */
.banner-container {
position: relative;
width: 100%;
height: 0;
padding-bottom: 75%; /* 4:3 比例 */
margin-top: v-bind('statusBarHeight + 44 + "px"');
overflow: hidden;
.banner {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
&:active {
transform: scale(1.02);
}
}
.banner-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 200rpx;
background: linear-gradient(to top, rgba(0,0,0,0.7), transparent);
}
}
/* 品牌信息卡片 */
.brand-header {
position: relative;
width: 100%;
margin-top: -150rpx;
z-index: 1;
.brand-info {
background-color: #fff;
border-radius: 42rpx 0rpx 0 0;
padding: 40rpx 32rpx;
box-shadow: 0 -10rpx 30rpx rgba(0, 0, 0, 0.05);
}
.brand-logo {
width: 100rpx;
height: 100rpx;
margin-right: 24rpx;
border-radius: 50%;
object-fit: cover;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
border: 4rpx solid #fff;
transition: transform 0.3s ease;
&:active {
transform: scale(1.05);
}
}
.brand-name {
color: #0B0E26;
font-size: 36rpx;
font-weight: 600;
margin-bottom: 12rpx;
line-height: 1.4;
}
.brand-location {
display: flex;
align-items: center;
color: #5E5F60;
font-size: 24rpx;
line-height: 1.4;
.cuIcon-location {
font-size: 24rpx;
margin-right: 8rpx;
color: #19367A;
}
}
.action-buttons {
display: flex;
align-items: center;
gap: 16rpx;
.action-btn {
width: 72rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
background: #F5F5F5;
border-radius: 50%;
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
}
&.active {
background: #19367A;
}
&.favor-btn {
&.active {
background: rgba(255, 77, 79, 0.1);
}
}
}
.action-icon {
width: 36rpx;
height: 36rpx;
}
}
.brand-stats {
display: flex;
justify-content: space-between;
align-items: center;
margin: 32rpx 0;
padding: 32rpx 0;
background: rgba(246, 247, 251, 0.5);
border-radius: 12rpx;
.stat-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
.stat-value {
font-size: 40rpx;
font-weight: 600;
color: #0B0E26;
margin-bottom: 12rpx;
line-height: 1.2;
}
.stat-label {
font-size: 24rpx;
color: #9C9BA6;
line-height: 1.4;
}
}
.stat-divider {
width: 2rpx;
height: 60rpx;
background: rgba(0, 0, 0, 0.05);
}
}
.brand-desc-container {
margin-left: 20rpx;
margin-right: 20rpx;
padding-top: 20rpx;
.brand-desc {
color: #666666;
font-size: 26rpx;
line-height: 1.8;
text-indent: 2em;
}
}
}
/* 标签页导航容器 */
.tab-nav-container {
position: relative;
background: #fff;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
margin-bottom: 24rpx;
width: 100%;
}
/* 标签页导航 */
.tab-nav {
display: flex;
justify-content: space-between;
padding: 0 24rpx;
background-color: #fff;
&:after {
content: '';
position: absolute;
// bottom: -16rpx;
left: 0;
right: 0;
height: 2rpx;
background: #FFF;
}
.tab-item {
flex: 1;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
font-weight: 500;
color: #666666;
position: relative;
transition: all 0.3s ease;
&.active {
color: #19367A;
font-weight: 600;
&:after {
content: '';
position: absolute;
bottom: -16rpx;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 8rpx;
background: #19367A;
border-radius: 4rpx;
}
}
&:active {
opacity: 0.8;
}
}
}
/* 内容区域 */
.content-box {
margin-top: 32rpx;
box-sizing: border-box;
background-color: #fff;
border-radius: 24rpx;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.08);
/* 标签页内容区 */
.tab-content {
min-height: 600rpx;
width: 100%;
box-sizing: border-box;
}
/* 滚动容器 */
.scroll-container {
width: 100%;
height: 100%;
padding: 0;
box-sizing: border-box;
}
/* 活动列表容器 */
.activity-list {
width: 100%;
padding: 32rpx 32rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
// border: 1rpx solid #FFD700;
}
/* 啤酒网格 */
.beer-grid {
width: 100%;
padding: 32rpx 32rpx;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
/* 商品网格 */
.goods-grid {
width: 100%;
display: flex;
flex-wrap: wrap;
padding: 32rpx 32rpx;
gap: 24rpx;
// padding: 0 24rpx;
box-sizing: border-box;
}
/* 商品卡片 */
.goods-item {
width: calc((100% - 24rpx) / 2);
background: #FFFFFF;
border-radius: 12rpx;
overflow: hidden;
box-sizing: border-box;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
}
.goods-image {
width: 100%;
height: 258rpx;
background: #F5F5F5;
transition: transform 0.3s ease;
&:active {
transform: scale(1.05);
}
}
.goods-info {
padding: 16rpx;
box-sizing: border-box;
}
.goods-title {
font-size: 24rpx;
color: #1A1A1A;
font-weight: 600;
margin-bottom: 12rpx;
}
.goods-price {
display: flex;
justify-content: space-between;
align-items: center;
.price-left {
display: flex;
align-items: center;
.price-num {
font-size: 32rpx;
color: #1A1A1A;
font-weight: bold;
}
.coin-icon {
color: #FFD700;
font-size: 24rpx;
margin-left: 12rpx;
}
}
.brand-name {
font-size: 20rpx;
color: #808080;
background: #F6F7FB;
padding: 4rpx 12rpx;
border-radius: 20rpx;
}
}
}
/* 加载状态 */
.loading-state {
width: 100%;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx 0;
&.loading {
.loading-text {
display: flex;
align-items: center;
color: #979797;
font-size: 24rpx;
.cuIcon-loading2 {
margin-right: 8rpx;
font-size: 28rpx;
filter: drop-shadow(0 1rpx 2rpx rgba(0, 0, 0, 0.1));
animation: loading-rotate 1s linear infinite;
}
}
}
&.no-more {
.no-more-text {
position: relative;
color: #979797;
font-size: 24rpx;
text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.05);
padding: 0 30rpx;
&::before,
&::after {
content: '';
position: absolute;
top: 50%;
width: 80rpx;
height: 1px;
background: linear-gradient(to right, transparent, #979797);
}
&::before {
left: -60rpx;
}
&::after {
right: -60rpx;
transform: rotate(180deg);
}
}
}
}
/* 空状态 */
.empty-state {
width: 100%;
height: 400rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.empty-image {
width: 200rpx;
height: 200rpx;
margin-bottom: 20rpx;
opacity: 0.8;
}
.empty-text {
color: #979797;
font-size: 28rpx;
}
}
}
}
/* 文本截断 */
.text-cut {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* 加载动画 */
.iconfont-spin {
animation: spin 1s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* 加载动画 */
@keyframes loading-rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* 上滑加载动画 */
@keyframes slide-in {
from {
transform: translateY(100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
/* 活动列表项动画 */
.activity-item {
animation: slide-in 0.3s ease-out;
}
/* 商品卡片动画 */
.goods-item {
animation: slide-in 0.3s ease-out;
}
/* 啤酒卡片动画 */
.beer-card {
animation: slide-in 0.3s ease-out;
}
</style>