feat: 优化活动列表和首页交互效果

This commit is contained in:
davy 2025-04-07 10:21:22 +08:00
parent 12a5c6771a
commit 4dd491b61b
4 changed files with 787 additions and 641 deletions

View File

@ -6,51 +6,30 @@
<view class="" >探索啤酒风味新边界</view>
</view> -->
<view class="container">
<scroll-view scroll-y style="height: 100%;width: 100%;">
<view class="" style="margin: 20rpx 0;font-size: 32rpx;color: #030303;font-weight: 600;">
<view class="" >TasteRoom 风味屋 · 风味之旅系列</view>
<!-- 活动标题和描述 -->
<view class="activity-header">
<view class="activity-title">{{activityInfo.title || 'TasteRoom 风味屋 · 风味之旅系列'}}</view>
<view class="activity-desc">{{activityInfo.description || '探索啤酒风味新边界'}}</view>
</view>
<view class="" style="margin: 20rpx 0;font-size: 24rpx;color: #606060;">
<view class="" >探索啤酒风味新边界</view>
</view>
<view style="width: 100%;" class="flex align-center flex-wrap justify-between">
<view class="" style="width: 32%;height: 100%;text-align: center;" v-for="it in currentMonth">
<image :src="it.cover" style="width: 100%;height: 300rpx;margin-right:20rpx;"></image>
<view class="" style="height: 80rpx;font-size: 28rpx;color: #030303;text-align: left;margin: 12rpx 0;">{{it.beerName}}</view>
<view class=""
style="font-size: 24rpx;color: rgba(30, 32, 25, 0.8);text-align: left;">西打酒
</view>
<view class=""
style="font-size: 24rpx;color: rgba(30, 32, 25, 0.8);text-align: left;margin: 12rpx 0;">
TasteRoom风味屋
</view>
<view class="flex align-center " style="color: #FFCC00;font-size: 28rpx;margin-bottom: 32rpx;">4.9
<image src="@/static/vector.png" style="width: 20rpx;height: 20rpx;margin-left: 10rpx;">
</image>
<!-- 酒款列表 -->
<view class="beer-list">
<view class="beer-item" v-for="(item, index) in beerList" :key="index" @click="toBeer(item)">
<image class="beer-image" :src="item.cover" mode="aspectFill"></image>
<view class="beer-info">
<view class="beer-name">{{item.beerName}}</view>
<view class="beer-style">{{item.beerStyles}}</view>
<view class="beer-brand">{{item.brandName}}</view>
<view class="beer-rating">
<text>{{item.avgOverallRating || '4.9'}}</text>
<image src="@/static/vector.png" class="rating-icon"></image>
</view>
</view>
</view>
</view>
<!-- <view class="beerItem">
<view class="beerCard flex align-center" @click="toBeer(it)">
<image :src="it.cover" style="width: 144rpx;height: 204rpx;margin-right:20rpx;"></image>
<view class="flex-1">
<view class="word-all margin-bottom-sm" style="color:#1E2019">
{{it.beerName}}
</view>
<view class="word-all margin-bottom-sm"
style="font-size: 24rpx;color: rgba(30, 32, 25, 0.8);">{{ it.beerStyles}}
</view>
<view class="word-all margin-bottom-sm" style="color:#1E2019">{{ it.brandName}}</view>
</view>
</view>
</view> -->
</view>
<view style="width: 100%;color: #979797;margin-top: 60rpx;margin-bottom: 60rpx;text-align: center;">更多新品发布信息敬请关注更新~</view>
<view class="more-tip">更多新品发布信息敬请关注更新~</view>
</scroll-view>
</view>
<!-- <view class="flex align-center justify-between tip">
@ -63,58 +42,69 @@
<script>
import {
getLastSixMonth,
getNewBeerListByMonth,
popularStyle,
getFeaturePage,
getBeerList
} from "@/api/platform.js"
export default {
components: {
},
data() {
return {
dataList: [],
currentMonth: [],
currentMonthIndex: 0,
popularStyleList: [],
activityId: '',
activityInfo: {},
beerList: [],
queryForm: {
num: null
search: ''
},
bgcolor:'#19367A',
};
},
onLoad() {
this.getPopularStyle()
this.getLastSixMonthFun()
this.$refs.successRef.open
// uni.showTabBar()
onLoad(options) {
if (options.id) {
this.activityId = options.id
this.getActivityDetail()
}
},
onShow(){
// uni.showTabBar()
},
methods: {
//
getPopularStyle() {
popularStyle().then(res => {
console.log(res)
this.popularStyleList = res.data
//
async getActivityDetail() {
try {
const res = await getFeaturePage(this.activityId)
if (res.code === 200 && res.data) {
this.activityInfo = res.data
//
this.getBeerList()
}
} catch (error) {
console.error('获取活动详情失败:', error)
uni.showToast({
title: '获取活动详情失败',
icon: 'none'
})
}
},
changeMonth(item, index) {
this.currentMonthIndex = index
this.currentMonth = item.beers
this.queryForm.num = index
this.getNewBeerListByMonthFun()
},
getLastSixMonthFun() {
getLastSixMonth().then(res => {
console.log(res)
this.dataList = res.data
this.queryForm.num = 0
this.getNewBeerListByMonthFun()
//
async getBeerList() {
try {
const res = await getBeerList(this.activityInfo.keyword || '')
if (res.code === 200 && res.data) {
this.beerList = res.data
}
} catch (error) {
console.error('获取酒款列表失败:', error)
uni.showToast({
title: '获取酒款列表失败',
icon: 'none'
})
}
},
//
//
toBeer(item) {
uni.navigateTo({
url: '/pages/index/review?beerId=' + item.id

View File

@ -1,75 +1,81 @@
<template>
<view class="page flex flex-col">
<!-- 固定搜索框 -->
<view class="fixed-search-bar">
<SearchBar :placeholders="'搜索酒款/品牌'" :bgcolor="'#19367A'" :borderRs="'0px 0px 24rpx 24rpx'"/>
<view class="page">
<!-- 搜索框 -->
<view class="search-box">
<view class="search-input">
<image src="/static/icons/search.png" class="search-icon" />
<input
type="text"
v-model="searchKey"
placeholder="搜索品牌"
placeholder-class="placeholder"
@input="handleSearch"
/>
</view>
</view>
<!-- 热门厂牌和我的关注 -->
<view class="hot-header" v-if="hotflag">
<view class="flex align-center hot-center">
<view class="" style="margin-left: 24rpx;" @click="hotClick('1')" :class="hotActives=='1'?'hot-actives':''">
<view class="">热门厂牌</view>
</view>
<view class="" style="color: #E0E0E0;margin-left: 20rpx;">|</view>
<view style="margin-left: 24rpx;" @click="hotClick('2')" :class="hotActives=='2'?'hot-actives':''">
<view class="">我的关注</view>
<!-- 加载状态 -->
<view v-if="loading" class="loading-state">
<view class="loading-skeleton" v-for="i in 5" :key="i">
<view class="skeleton-logo"></view>
<view class="skeleton-content">
<view class="skeleton-title"></view>
<view class="skeleton-subtitle"></view>
</view>
</view>
</view>
<!-- 热门厂牌列表 -->
<!-- <view class="hot-bom flex align-center justify-between flex-wrap" v-if="hotflag">
<view class="" v-for="it in 8" style="width: 25%;text-align: center;">
<view class="">
<image src="@/static/fengwei.png" mode="" style="width: 88rpx;height: 88rpx;"></image>
</view>
<view class="" style="font-size: 28rpx;margin: 16rpx 0 12rpx 0;">风味屋</view>
</view>
</view> -->
<!-- 联系人列表和索引栏 -->
<view class="container" :style="{ height: containerHeight + 'px',}" :class="hotflag?'':'hot-act'">
<scroll-view scroll-y :scroll-into-view="scrollIntoView" class="list" @scroll="handleScroll">
<view class="hot-bom flex align-center justify-between flex-wrap" v-if="hotflag">
<view class="" v-for="it in 8" style="width: 25%;text-align: center;">
<view class="">
<image src="@/static/bg/fengwei.png" mode="" style="width: 88rpx;height: 88rpx;"></image>
</view>
<view class="" style="font-size: 28rpx;margin: 16rpx 0 12rpx 0;">风味屋</view>
</view>
</view>
<view v-for="(group, letter) in groupedData" :key="letter" :id="letter">
<view class="group-title">
{{ letter }}
<!-- 自定义 switch -->
<view class="flex align-center" v-if="currentVisibleLetter === letter">
<view class="" style="font-size: 24rpx;color: #606060;margin-right: 15rpx;">只看在售</view>
<!-- :class="{ 'switch-on': isOnSaleFilter[letter], 'switch-off': !isOnSaleFilter[letter] }"
@click="toggleFilter(letter)" -->
<view
class="custom-switch"
:class="{ 'switch-on': isOnSaleFilter, 'switch-off': !isOnSaleFilter }"
@click="toggleGlobalFilter"
<!-- 字母索引列表 -->
<view v-else class="index-list">
<scroll-view
scroll-y
class="content"
:scroll-into-view="currentLetter ? 'letter-' + currentLetter : ''"
>
<view class="switch-thumb"></view>
<block v-for="(group, letter) in groupedBreweries" :key="letter">
<view :id="'letter-' + letter" class="letter-section">
<view class="letter-title">{{letter}}</view>
<view
class="brewery-item hover-effect"
v-for="brewery in group"
:key="brewery.id"
@click="navigateToBrewery(brewery)"
>
<image
:src="brewery.logo || '/static/images/default-logo.png'"
class="brewery-logo"
mode="aspectFill"
:lazy-load="true"
/>
<view class="brewery-info">
<text class="brewery-name text-ellipsis">{{brewery.breweryName}}</text>
<text class="beer-count">{{brewery.beerCount || 0}}款在售</text>
</view>
<image
src="/static/icons/arrow-right.png"
class="arrow-icon"
/>
</view>
</view>
</view>
<view v-for="(item, index) in group" :key="index" class="contact-item">
<view class="">
<image src="@/static/logouts.png" style="width:88rpx;height: 88rpx;" mode=""></image>
</view>
<text style="margin-left: 52rpx;font-size: 28rpx;">{{ item.name }}</text>
</view>
</block>
<!-- 空状态 -->
<view v-if="Object.keys(groupedBreweries).length === 0" class="empty-state">
<image src="/static/images/empty.png" class="empty-image" />
<text class="empty-text">暂无相关品牌</text>
</view>
</scroll-view>
<!-- 字母索引栏 -->
<view class="index-bar">
<view v-for="letter in indexLetters" :key="letter" @tap="scrollToGroup(letter)"
:class="{ 'active-letter': activeLetter === letter }">
{{ letter }}
<!-- 右侧字母导航 -->
<view class="letter-nav">
<view
v-for="letter in letters"
:key="letter"
class="letter-item"
:class="{ active: currentLetter === letter }"
@click="scrollToLetter(letter)"
>
{{letter}}
</view>
</view>
</view>
@ -77,268 +83,410 @@
</template>
<script>
import { getLastSixMonth, getNewBeerListByMonth, popularStyle } from "@/api/platform.js";
import CustomNavBar from '@/components/CustomNavBar.vue';
import SearchBar from '@/components/SearchBar.vue';
export default {
components: {
CustomNavBar,
SearchBar
},
import { getBreweries, getBeerList } from '@/api/bar.js'
export default {
name: 'HotLabel',
data() {
return {
dataList: [],
currentMonth: [],
currentMonthIndex: 0,
popularStyleList: [],
queryForm: {
num: null,
},
bgcolor: '#19367A',
containerHeight: 0, //
rawGroupedData: [
{ name: "Alice", phone: "123456789", group: "A", isOnSale: true },
{ name: "Alice1", phone: "123456789", group: "A", isOnSale: false },
{ name: "Bob", phone: "987654321", group: "B", isOnSale: true },
{ name: "Bob1", phone: "987654321", group: "B", isOnSale: false },
{ name: "Charlie", phone: "555555555", group: "C", isOnSale: true },
{ name: "David", phone: "111111111", group: "D", isOnSale: true },
{ name: "Eve", phone: "222222222", group: "E", isOnSale: true },
{ name: "Frank", phone: "333333333", group: "F", isOnSale: true },
{ name: "Grace", phone: "444444444", group: "G", isOnSale: true },
{ name: "Hank", phone: "555555555", group: "H", isOnSale: true },
{ name: "Ivy", phone: "666666666", group: "I", isOnSale: true },
{ name: "Jack", phone: "777777777", group: "J", isOnSale: true },
],
scrollIntoView: "", //
isOnSaleFilter: {},
originalGroupedData: {},
activeLetter: "", //
hotActives: '1',
hotflag:true,
currentVisibleLetter: null,
isOnSaleFilter: false,
//
searchKey: '',
searchTimer: null,
};
},
computed: {
//
groupedData() {
const grouped = this.rawGroupedData.reduce((acc, item) => {
const group = item.group;
if (!acc[group]) acc[group] = [];
acc[group].push(item);
return acc;
}, {});
//
breweries: [],
groupedBreweries: {},
currentLetter: '',
letters: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
// //
// Object.keys(grouped).forEach((letter) => {
// if (this.isOnSaleFilter[letter]) {
// grouped[letter] = grouped[letter].filter((item) => item.isOnSale);
// }
// });
//
if (this.isOnSaleFilter) {
Object.keys(grouped).forEach((letter) => {
grouped[letter] = grouped[letter].filter((item) => item.isOnSale);
});
//
loading: false
}
return grouped;
},
//
indexLetters() {
return Object.keys(this.groupedData).sort();
},
onShow() {
this.initData()
},
methods: {
hotClick(ind) {
this.hotActives = ind;
},
toggleGlobalFilter() {
this.isOnSaleFilter = !this.isOnSaleFilter;
},
handleScroll(e) {
const scrollTop = e.detail.scrollTop;
if (scrollTop < 2) {
this.hotflag = true; //
//
async initData() {
try {
uni.showLoading({
title: '加载中...'
})
const res = await getBreweries()
console.log('获取品牌列表响应:', res)
if (res && res.data) {
this.breweries = res.data.map(item => ({
id: item.id,
breweryName: item.breweryName || '',
logo: item.logo || '',
beerCount: 0
}))
//
await this.getBeerCounts()
//
this.groupBreweries()
} else {
throw new Error('获取品牌列表失败')
}
} catch (error) {
console.error('初始化数据失败:', error)
uni.showToast({
title: '加载失败,请重试',
icon: 'none'
})
} finally {
uni.hideLoading()
}
},
//
// getPopularStyle() {
// popularStyle().then((res) => {
// console.log(res);
// this.popularStyleList = res.data;
// });
// },
// getLastSixMonthFun() {
// getLastSixMonth().then((res) => {
// console.log(res);
// this.dataList = res.data;
// this.queryForm.num = 0;
// this.getNewBeerListByMonthFun();
// });
// },
//
scrollToGroup(letter) {
this.scrollIntoView = letter;
this.activeLetter = letter;
this.hotflag = false
this.currentVisibleLetter = letter; //
//
async getBeerCounts() {
const promises = this.breweries.map(async (brewery) => {
try {
const res = await getBeerList({ breweryId: brewery.id })
if (res && res.data) {
brewery.beerCount = res.data.total || 0
}
} catch (error) {
console.error(`获取品牌 ${brewery.id} 的酒款数量失败:`, error)
brewery.beerCount = 0
}
})
await Promise.all(promises)
},
//
calculateContainerHeight() {
const systemInfo = uni.getSystemInfoSync();
const searchBarHeight = 120; //
const hotHeaderHeight = 90; //
const hotBomHeight = 200; //
this.containerHeight = systemInfo.windowHeight - searchBarHeight - hotHeaderHeight - hotBomHeight;
//
groupBreweries() {
const grouped = {}
this.breweries.forEach(brewery => {
if (!brewery.breweryName) return
let firstLetter = brewery.breweryName.charAt(0).toUpperCase()
if (!/[A-Z]/.test(firstLetter)) {
firstLetter = '#'
}
if (!grouped[firstLetter]) {
grouped[firstLetter] = []
}
grouped[firstLetter].push(brewery)
})
Object.keys(grouped).forEach(letter => {
grouped[letter].sort((a, b) =>
a.breweryName.localeCompare(b.breweryName, 'zh')
)
})
this.letters = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')]
if (grouped['#']) {
this.letters.push('#')
}
console.log('分组后的数据:', grouped)
this.groupedBreweries = grouped
},
//
toggleFilter(letter) {
this.$set(this.isOnSaleFilter, letter, !this.isOnSaleFilter[letter]);
//
handleSearch() {
if (this.searchTimer) {
clearTimeout(this.searchTimer)
}
this.searchTimer = setTimeout(() => {
const searchKey = this.searchKey.trim().toLowerCase()
if (!searchKey) {
this.groupBreweries()
return
}
const filteredBreweries = this.breweries.filter(brewery =>
brewery.breweryName.toLowerCase().includes(searchKey)
)
const grouped = {}
filteredBreweries.forEach(brewery => {
let firstLetter = brewery.breweryName.charAt(0).toUpperCase()
if (/[\u4e00-\u9fa5]/.test(firstLetter)) {
firstLetter = this.getFirstPinYinLetter(brewery.breweryName)
}
if (!/[A-Z]/.test(firstLetter)) {
firstLetter = '#'
}
if (!grouped[firstLetter]) {
grouped[firstLetter] = []
}
grouped[firstLetter].push(brewery)
})
this.groupedBreweries = grouped
}, 300)
},
//
scrollToLetter(letter) {
this.currentLetter = letter
},
mounted() {
//
this.originalGroupedData = JSON.parse(JSON.stringify(this.groupedData));
this.calculateContainerHeight();
this.currentVisibleLetter = this.indexLetters[0];
//
navigateToBrewery(brewery) {
uni.navigateTo({
url: `/pages/activityList/styleSelection?breweryId=${brewery.id}`
})
},
};
//
getFirstPinYinLetter(str) {
const pinyin = require('pinyin')
if (!str) return '#'
const firstChar = str.charAt(0)
if (!/[\u4e00-\u9fa5]/.test(firstChar)) return firstChar.toUpperCase()
const pinyinArr = pinyin(firstChar, {
style: pinyin.STYLE_FIRST_LETTER,
heteronym: false
})
return (pinyinArr[0] || ['#'])[0].toUpperCase()
}
}
}
</script>
<style scoped lang="scss">
<style lang="scss" scoped>
.page {
display: flex;
flex-direction: column;
height: 100vh;
font-family: Roboto;
}
min-height: 100vh;
background: #FFFFFF;
padding-top: 120rpx;
.fixed-search-bar {
//
.search-box {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
background-color: #fff; //
}
.hot-header {
margin-top: 120rpx; //
}
.hot-center {
height: 90rpx;
color: #606060;
font-size: 24rpx;
padding: 24rpx;
background: #FFFFFF;
}
z-index: 100;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
.hot-actives {
font-size: 28rpx;
font-weight: 600;
color: #030303;
}
.hot-bom {
padding: 0 24rpx;
background: #FFFFFF;
}
.container {
flex: 1;
.search-input {
display: flex;
}
align-items: center;
height: 72rpx;
background: #F5F5F5;
border-radius: 36rpx;
padding: 0 24rpx;
.list {
.search-icon {
width: 32rpx;
height: 32rpx;
margin-right: 16rpx;
opacity: 0.3;
}
input {
flex: 1;
height: 100%;
}
font-size: 28rpx;
color: #333333;
}
.group-title {
padding: 10px;
background-color: #f9f9f9;
font-weight: bold;
.placeholder {
color: #999999;
}
}
}
//
.loading-state {
padding: 24rpx;
.loading-skeleton {
display: flex;
align-items: center;
justify-content: space-between;
}
.contact-item {
padding: 24rpx;
margin-bottom: 24rpx;
background: #FFFFFF;
padding: 10px;
border-bottom: 1px solid #eee;
border-radius: 12rpx;
.skeleton-logo {
width: 80rpx;
height: 80rpx;
background: #F5F5F5;
border-radius: 12rpx;
margin-right: 24rpx;
animation: skeleton-loading 1.5s infinite;
}
.skeleton-content {
flex: 1;
.skeleton-title {
width: 60%;
height: 32rpx;
background: #F5F5F5;
border-radius: 4rpx;
margin-bottom: 12rpx;
animation: skeleton-loading 1.5s infinite;
}
.skeleton-subtitle {
width: 40%;
height: 24rpx;
background: #F5F5F5;
border-radius: 4rpx;
animation: skeleton-loading 1.5s infinite;
}
}
}
}
//
.index-list {
display: flex;
height: calc(100vh - 120rpx);
.content {
flex: 1;
height: 100%;
.letter-section {
.letter-title {
padding: 16rpx 24rpx;
font-size: 28rpx;
color: #999999;
background: #F9F9F9;
}
.brewery-item {
display: flex;
align-items: center;
}
padding: 24rpx;
margin: 0 24rpx;
border-bottom: 2rpx solid #F5F5F5;
transition: all 0.3s ease;
.index-bar {
width: 20px;
&:active {
background: #F9F9F9;
transform: scale(0.98);
}
.brewery-logo {
width: 80rpx;
height: 80rpx;
border-radius: 12rpx;
margin-right: 24rpx;
background: #F5F5F5;
box-shadow: 0 4rpx 8rpx rgba(0,0,0,0.05);
}
.brewery-info {
flex: 1;
.brewery-name {
font-size: 28rpx;
color: #333333;
margin-bottom: 8rpx;
font-weight: 500;
}
.beer-count {
font-size: 24rpx;
color: #999999;
}
}
.arrow-icon {
width: 32rpx;
height: 32rpx;
opacity: 0.3;
}
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #f8f8f8;
}
padding: 96rpx 0;
.index-bar view {
padding: 2px 0;
font-size: 12px;
cursor: pointer;
border-radius: 50%; /* 圆形背景 */
width: 20px;
height: 20px;
.empty-image {
width: 240rpx;
height: 240rpx;
margin-bottom: 32rpx;
}
.empty-text {
font-size: 28rpx;
color: #999999;
}
}
}
//
.letter-nav {
display: flex;
flex-direction: column;
justify-content: center;
padding: 24rpx 12rpx;
background: #FFFFFF;
.letter-item {
width: 32rpx;
height: 32rpx;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s;
font-size: 24rpx;
color: #666666;
margin: 4rpx 0;
border-radius: 50%;
transition: all 0.3s ease;
&.active {
color: #FFFFFF;
background: #D42E78;
}
&:active {
transform: scale(0.9);
}
}
}
}
}
.index-bar view.active-letter {
background-color: #D42E78; /* 选中字母的背景色 */
color: white; /* 选中字母的文字颜色 */
@keyframes skeleton-loading {
0% {
opacity: 0.6;
}
50% {
opacity: 0.8;
}
100% {
opacity: 0.6;
}
}
/* 自定义 switch 样式 */
.custom-switch {
width: 50rpx;
height: 28rpx;
border-radius: 14rpx;
position: relative;
cursor: pointer;
transition: background-color 0.3s;
-webkit-tap-highlight-color: transparent; /* 移除点击高亮 */
user-select: none; /* 禁止文本选择 */
//
.hover-effect {
transition: all 0.2s ease;
&:active {
transform: scale(0.96);
opacity: 0.8;
}
}
.switch-on {
background-color: #D42E78; /* 开启状态颜色(蓝色) */
}
.switch-off {
background-color: #e5e5e5; /* 关闭状态颜色 */
}
.switch-thumb {
width: 24rpx;
height: 24rpx;
background-color: white;
border-radius: 12rpx;
position: absolute;
top: 2rpx;
transition: left 0.3s;
}
.switch-on .switch-thumb {
left: 24rpx; /* 开启状态滑块位置 */
}
.switch-off .switch-thumb {
left: 2rpx; /* 关闭状态滑块位置 */
}
.hot-act{
padding-top: 120rpx;
//
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@ -1,24 +1,6 @@
<template>
<view class="page">
<!-- 筛选导航 -->
<view class="filter-section">
<view class="filter-tabs">
<view
v-for="(tab, index) in dropdownMenuList"
:key="tab.prop"
class="tab-item"
:class="{ active: activeTab === tab.prop }"
@click="$refs.filterDropdown.toggleMenu(index)"
>
<text class="tab-text">{{ tab.title }}</text>
<image
class="tab-icon"
:class="{ up: activeTab === tab.prop }"
:src="activeTab === tab.prop ? '/static/icons/arrow-active.png' : '/static/icons/arrow.png'"
/>
</view>
</view>
<Sieving
ref="filterDropdown"
:dropdownMenu="dropdownMenuList"
@ -27,7 +9,6 @@
@close="handleFilterClose"
@open="handleFilterOpen"
/>
</view>
<!-- 统计信息 -->
<view class="stats-bar">
@ -45,7 +26,7 @@
@refresherrefresh="onRefresh"
>
<view
class="beer-card"
class="beer-card hover-effect"
v-for="beer in beerList"
:key="beer.id"
@click="navigateToBeerDetail(beer)"
@ -54,7 +35,8 @@
<image
:src="beer.cover"
class="beer-image"
mode="aspectFit"
mode="aspectFill"
:lazy-load="true"
/>
<view class="beer-details">
<text class="beer-name text-ellipsis">{{beer.beerName}}</text>
@ -85,36 +67,6 @@
</view>
</scroll-view>
</view>
<!-- 风格筛选弹窗 -->
<uni-popup
ref="stylePopup"
type="right"
background-color="#fff"
>
<view class="style-popup">
<scroll-view
scroll-y
class="style-list"
>
<view
v-for="style in popularStyleList"
:key="style.id"
class="style-item"
:class="{'active': selectedStyle === style.beerStyles}"
@click="selectStyle(style)"
>
<text class="style-name">{{ style.beerStyles }}</text>
</view>
</scroll-view>
<view
class="clear-button"
@click="clearFilter"
>
清除筛选
</view>
</view>
</uni-popup>
</view>
</template>
@ -125,14 +77,13 @@
popularStyle,
getBrands,
getBeerStyles,
getBeerByStyle
} from "@/api/platform.js"
import CustomNavBar from '@/components/CustomNavBar.vue'
import Sieving from '@/components/sieving/index.vue'
export default {
name: 'StyleSelection',
components: {
CustomNavBar,
Sieving
},
data() {
@ -145,15 +96,13 @@
hasMore: true,
//
selectedStyle: '',
popularStyleList: [],
activeTab: '',
//
queryParams: {
pageNum: 1,
pageSize: 10,
sortType: 'latest',
style: ''
styleId: ''
},
//
@ -212,9 +161,10 @@
async initData() {
try {
await Promise.all([
this.getPopularStyles(),
this.getBeerList()
this.getBrandOptions(),
this.getStyleOptions()
])
await this.getBeerList()
} catch (error) {
console.error('初始化数据失败:', error)
uni.showToast({
@ -224,13 +174,31 @@
}
},
//
async getPopularStyles() {
//
async getBrandOptions() {
try {
const res = await popularStyle()
this.popularStyleList = res.data || []
const res = await getBrands()
const brands = res.data || []
this.dropdownMenuList[0].options = brands.map(brand => ({
label: brand.brandName,
value: brand.id
}))
} catch (error) {
console.error('获取热门风格失败:', error)
console.error('获取品牌列表失败:', error)
}
},
//
async getStyleOptions() {
try {
const res = await getBeerStyles()
const styles = res.data || []
this.dropdownMenuList[1].options = styles.map(style => ({
label: style.beerStyles,
value: style.id
}))
} catch (error) {
console.error('获取风格列表失败:', error)
}
},
@ -245,7 +213,7 @@
this.beerList = []
}
const res = await getNewBeerListByMonth(this.queryParams)
const res = await getBeerByStyle(this.queryParams)
const newList = res.data || []
this.beerList = isRefresh ?
@ -278,39 +246,15 @@
sortType: sort
}
if (sort === 'comprehensive') {
this.queryParams.weights = {
popular: 0.5,
rating: 0.3,
latest: 0.2
}
} else {
delete this.queryParams.weights
}
this.getBeerList(true)
},
handleFilterOpen(tabName) {
this.queryParams.style = tabName
this.activeTab = tabName
},
handleFilterClose() {
this.queryParams.style = ''
},
selectStyle(style) {
this.selectedStyle = style.beerStyles
this.queryParams.style = style.beerStyles
this.getBeerList(true)
this.$refs.stylePopup.close()
},
clearFilter() {
this.selectedStyle = ''
this.queryParams.style = ''
this.getBeerList(true)
this.$refs.stylePopup.close()
this.activeTab = ''
},
//
@ -336,73 +280,37 @@
</script>
<style lang="scss" scoped>
.page {
.page {
min-height: 100vh;
background: #F2F2F2;
.filter-section {
background: #FFFFFF;
border-bottom: 1rpx solid #F5F5F5;
.filter-tabs {
display: flex;
align-items: center;
height: 64rpx;
padding: 0 32rpx;
.tab-item {
width: 122rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #606060;
position: relative;
margin-right: 24rpx;
.tab-text {
margin-right: 8rpx;
}
.tab-icon {
width: 24rpx;
height: 24rpx;
transition: transform 0.3s;
&.up {
transform: rotate(180deg);
}
}
&.active {
color: #D42E78;
.tab-icon {
filter: invert(36%) sepia(75%) saturate(1217%)
hue-rotate(308deg) brightness(87%) contrast(98%);
}
}
}
}
}
background: #F9F9F9;
//
.stats-bar {
padding: 20rpx 32rpx;
padding: 16rpx 24rpx;
background: #FFFFFF;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
.stats-text {
font-size: 24rpx;
color: #606060;
color: #999999;
}
}
//
.content-container {
flex: 1;
overflow: hidden;
.beer-list {
padding: 0 32rpx;
height: calc(100vh - 180rpx);
padding: 0 24rpx;
.beer-card {
background: #FFFFFF;
border-radius: 16rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
.beer-info {
display: flex;
@ -410,10 +318,11 @@
padding: 24rpx;
.beer-image {
width: 144rpx;
height: 204rpx;
width: 120rpx;
height: 120rpx;
border-radius: 12rpx;
margin-right: 24rpx;
background: #F8F8F8;
object-fit: cover;
}
.beer-details {
@ -421,67 +330,117 @@
.beer-name {
font-size: 28rpx;
color: #1E2019;
margin-bottom: 16rpx;
font-weight: 600;
color: #333333;
margin-bottom: 8rpx;
}
.beer-style {
font-size: 24rpx;
color: rgba(30, 32, 25, 0.7);
margin-bottom: 12rpx;
color: #666666;
margin-bottom: 8rpx;
}
.brand-name {
font-size: 24rpx;
color: #606060;
color: #999999;
}
}
.arrow-icon {
width: 32rpx;
height: 32rpx;
opacity: 0.4;
}
}
margin-left: 16rpx;
opacity: 0.3;
}
}
}
.style-popup {
height: 100%;
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 96rpx 0;
.style-list {
flex: 1;
padding: 32rpx;
.empty-image {
width: 240rpx;
height: 240rpx;
margin-bottom: 32rpx;
}
.style-item {
padding: 24rpx 0;
.empty-text {
font-size: 28rpx;
color: #333333;
&.active {
color: #19367A;
font-weight: 500;
}
color: #999999;
}
}
.clear-button {
height: 88rpx;
line-height: 88rpx;
.loading-status {
padding: 32rpx 0;
text-align: center;
font-size: 28rpx;
color: #606060;
border-top: 1rpx solid #F5F5F5;
.loading-wrapper {
display: flex;
align-items: center;
justify-content: center;
.loading-icon {
width: 40rpx;
height: 40rpx;
margin-right: 16rpx;
}
.loading-text {
font-size: 24rpx;
color: #999999;
}
}
.text-ellipsis {
.no-more {
font-size: 24rpx;
color: #999999;
}
}
}
}
}
//
.hover-effect {
transition: all 0.2s ease;
&:active {
transform: scale(0.96);
opacity: 0.8;
}
}
//
.beer-image {
will-change: transform;
backface-visibility: hidden;
transform: translateZ(0);
}
//
.skeleton {
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 37%, #f2f2f2 63%);
background-size: 400% 100%;
animation: skeleton-loading 1.4s ease infinite;
}
@keyframes skeleton-loading {
0% {
background-position: 100% 50%;
}
100% {
background-position: 0 50%;
}
}
//
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>

View File

@ -6,12 +6,12 @@
</view>
</view> -->
<view v-if="showJoinImg && bannerJoin" class="join-box" @click="toJoin">
<image :src="bannerJoin.bannerUrl" class="img"></image>
<image :src="bannerJoin.bannerUrl" class="img" mode="aspectFill" :lazy-load="true"></image>
</view>
<!-- 轮播 -->
<swiper v-else class="join-box" circular :autoplay="true" :indicator-dots="true">
<swiper-item v-for="(item,index) in homeBanner" :key="index">
<image :src="item.bannerUrl" class="img"></image>
<image :src="item.bannerUrl" class="img" mode="aspectFill" :lazy-load="true"></image>
</swiper-item>
</swiper>
@ -19,7 +19,7 @@
<!-- 快捷导航 -->
<view class="bg-white"
style="border-radius: 30rpx 30rpx 12rpx 12rpx;padding-top:8rpx;position: relative;margin-top:-20rpx">
<view class="search-box" @click="toSearch">
<view class="search-box hover-effect" @click="toSearch">
<view class="search-input">
<text class="cuIcon-search" style="font-size: 40rpx;color: #A2A2A2;margin-right: 24rpx;"></text>
<text style="color: #A2A2A2;">搜索酒款名称品牌名称啤酒风格</text>
@ -29,29 +29,11 @@
</view>
</view>
<view class="flex justify-between bg-white" style="padding: 20rpx 36rpx;">
<view class="nav-item" @click="toGo(1)">
<view class="nav-item hover-effect" v-for="(item, index) in navItems" :key="index" @click="toGo(index + 1)">
<view class="flex justify-center align-center img-box">
<image src="/static/nav-1.png" style="width: 48rpx;height: 48rpx;"></image>
<image :src="'/static/nav-' + (index + 1) + '.png'" style="width: 48rpx;height: 48rpx;" :lazy-load="true"></image>
</view>
<text class="text-bold">新酒上市</text>
</view>
<view class="nav-item" @click="toGo(2)">
<view class="flex justify-center align-center img-box">
<image src="/static/nav-2.png" style="width: 48rpx;height: 48rpx;"></image>
</view>
<text class="text-bold">生成酒单</text>
</view>
<view class="nav-item" @click="toGo(3)">
<view class="flex justify-center align-center img-box">
<image src="/static/nav-3.png" style="width: 48rpx;height: 48rpx;"></image>
</view>
<text class="text-bold">酒币换购</text>
</view>
<view class="nav-item" @click="toGo(4)">
<view class="flex justify-center align-center img-box">
<image src="/static/nav-4.png" style="width: 48rpx;height: 48rpx;"></image>
</view>
<text class="text-bold">关注厂牌</text>
<text class="text-bold">{{item}}</text>
</view>
</view>
</view>
@ -143,6 +125,7 @@
total: 0,
loading: false,
finished: false,
navItems: ['新酒上市', '生成酒单', '酒币换购', '关注厂牌'],
};
},
created() {
@ -643,4 +626,70 @@
.activity-item {
display: block;
}
.hover-effect {
transition: all 0.2s ease;
&:active {
transform: scale(0.96);
opacity: 0.8;
}
}
.search-box {
// ... existing code ...
&:active {
transform: scale(0.98);
opacity: 0.9;
}
}
.nav-item {
// ... existing code ...
&:active {
transform: scale(0.95);
opacity: 0.8;
}
}
.more-btn-box {
// ... existing code ...
&:active {
transform: scale(0.98);
opacity: 0.9;
}
}
.activity-item {
// ... existing code ...
&:active {
transform: scale(0.98);
opacity: 0.9;
}
}
//
.img {
width: 100%;
height: 100%;
will-change: transform;
backface-visibility: hidden;
transform: translateZ(0);
}
//
.skeleton {
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 37%, #f2f2f2 63%);
background-size: 400% 100%;
animation: skeleton-loading 1.4s ease infinite;
}
@keyframes skeleton-loading {
0% {
background-position: 100% 50%;
}
100% {
background-position: 0 50%;
}
}
</style>