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 class="" >探索啤酒风味新边界</view>
</view> --> </view> -->
<view class="container"> <view class="container">
<scroll-view scroll-y style="height: 100%;width: 100%;"> <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> <view class="activity-title">{{activityInfo.title || 'TasteRoom 风味屋 · 风味之旅系列'}}</view>
<view class="" style="margin: 20rpx 0;font-size: 24rpx;color: #606060;"> <view class="activity-desc">{{activityInfo.description || '探索啤酒风味新边界'}}</view>
<view class="" >探索啤酒风味新边界</view> </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> </view>
<!-- <view class="beerItem"> <view class="more-tip">更多新品发布信息敬请关注更新~</view>
<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>
</scroll-view> </scroll-view>
</view> </view>
<!-- <view class="flex align-center justify-between tip"> <!-- <view class="flex align-center justify-between tip">
@ -63,58 +42,69 @@
<script> <script>
import { import {
getLastSixMonth, getFeaturePage,
getNewBeerListByMonth, getBeerList
popularStyle,
} from "@/api/platform.js" } from "@/api/platform.js"
export default { export default {
components: { components: {
}, },
data() { data() {
return { return {
dataList: [], activityId: '',
currentMonth: [], activityInfo: {},
currentMonthIndex: 0, beerList: [],
popularStyleList: [],
queryForm: { queryForm: {
num: null search: ''
}, },
bgcolor:'#19367A', bgcolor:'#19367A',
}; };
}, },
onLoad() { onLoad(options) {
this.getPopularStyle() if (options.id) {
this.getLastSixMonthFun() this.activityId = options.id
this.$refs.successRef.open this.getActivityDetail()
// uni.showTabBar() }
}, },
onShow(){ onShow(){
// uni.showTabBar() // uni.showTabBar()
}, },
methods: { methods: {
// //
getPopularStyle() { async getActivityDetail() {
popularStyle().then(res => { try {
console.log(res) const res = await getFeaturePage(this.activityId)
this.popularStyleList = res.data 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 async getBeerList() {
this.queryForm.num = index try {
this.getNewBeerListByMonthFun() 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'
})
}
}, },
getLastSixMonthFun() {
getLastSixMonth().then(res => { //
console.log(res)
this.dataList = res.data
this.queryForm.num = 0
this.getNewBeerListByMonthFun()
})
},
//
toBeer(item) { toBeer(item) {
uni.navigateTo({ uni.navigateTo({
url: '/pages/index/review?beerId=' + item.id url: '/pages/index/review?beerId=' + item.id

View File

@ -1,75 +1,81 @@
<template> <template>
<view class="page flex flex-col"> <view class="page">
<!-- 固定搜索框 --> <!-- 搜索框 -->
<view class="fixed-search-bar"> <view class="search-box">
<SearchBar :placeholders="'搜索酒款/品牌'" :bgcolor="'#19367A'" :borderRs="'0px 0px 24rpx 24rpx'"/> <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>
<!-- 热门厂牌和我的关注 --> <!-- 加载状态 -->
<view class="hot-header" v-if="hotflag"> <view v-if="loading" class="loading-state">
<view class="flex align-center hot-center"> <view class="loading-skeleton" v-for="i in 5" :key="i">
<view class="" style="margin-left: 24rpx;" @click="hotClick('1')" :class="hotActives=='1'?'hot-actives':''"> <view class="skeleton-logo"></view>
<view class="">热门厂牌</view> <view class="skeleton-content">
</view> <view class="skeleton-title"></view>
<view class="" style="color: #E0E0E0;margin-left: 20rpx;">|</view> <view class="skeleton-subtitle"></view>
<view style="margin-left: 24rpx;" @click="hotClick('2')" :class="hotActives=='2'?'hot-actives':''">
<view class="">我的关注</view>
</view> </view>
</view> </view>
</view> </view>
<!-- 热门厂牌列表 --> <!-- 字母索引列表 -->
<!-- <view class="hot-bom flex align-center justify-between flex-wrap" v-if="hotflag"> <view v-else class="index-list">
<view class="" v-for="it in 8" style="width: 25%;text-align: center;"> <scroll-view
<view class=""> scroll-y
<image src="@/static/fengwei.png" mode="" style="width: 88rpx;height: 88rpx;"></image> class="content"
</view> :scroll-into-view="currentLetter ? 'letter-' + currentLetter : ''"
<view class="" style="font-size: 28rpx;margin: 16rpx 0 12rpx 0;">风味屋</view> >
</view> <block v-for="(group, letter) in groupedBreweries" :key="letter">
</view> --> <view :id="'letter-' + letter" class="letter-section">
<view class="letter-title">{{letter}}</view>
<!-- 联系人列表和索引栏 --> <view
<view class="container" :style="{ height: containerHeight + 'px',}" :class="hotflag?'':'hot-act'"> class="brewery-item hover-effect"
<scroll-view scroll-y :scroll-into-view="scrollIntoView" class="list" @scroll="handleScroll"> v-for="brewery in group"
<view class="hot-bom flex align-center justify-between flex-wrap" v-if="hotflag"> :key="brewery.id"
<view class="" v-for="it in 8" style="width: 25%;text-align: center;"> @click="navigateToBrewery(brewery)"
<view class=""> >
<image src="@/static/bg/fengwei.png" mode="" style="width: 88rpx;height: 88rpx;"></image> <image
</view> :src="brewery.logo || '/static/images/default-logo.png'"
<view class="" style="font-size: 28rpx;margin: 16rpx 0 12rpx 0;">风味屋</view> class="brewery-logo"
</view> mode="aspectFill"
</view> :lazy-load="true"
<view v-for="(group, letter) in groupedData" :key="letter" :id="letter"> />
<view class="brewery-info">
<view class="group-title"> <text class="brewery-name text-ellipsis">{{brewery.breweryName}}</text>
{{ letter }} <text class="beer-count">{{brewery.beerCount || 0}}款在售</text>
<!-- 自定义 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 class="switch-thumb"></view>
</view> </view>
<image
src="/static/icons/arrow-right.png"
class="arrow-icon"
/>
</view> </view>
</view> </view>
<view v-for="(item, index) in group" :key="index" class="contact-item"> </block>
<view class="">
<image src="@/static/logouts.png" style="width:88rpx;height: 88rpx;" mode=""></image> <!-- 空状态 -->
</view> <view v-if="Object.keys(groupedBreweries).length === 0" class="empty-state">
<text style="margin-left: 52rpx;font-size: 28rpx;">{{ item.name }}</text> <image src="/static/images/empty.png" class="empty-image" />
</view> <text class="empty-text">暂无相关品牌</text>
</view> </view>
</scroll-view> </scroll-view>
<!-- 字母索引栏 -->
<view class="index-bar"> <!-- 右侧字母导航 -->
<view v-for="letter in indexLetters" :key="letter" @tap="scrollToGroup(letter)" <view class="letter-nav">
:class="{ 'active-letter': activeLetter === letter }"> <view
{{ letter }} v-for="letter in letters"
:key="letter"
class="letter-item"
:class="{ active: currentLetter === letter }"
@click="scrollToLetter(letter)"
>
{{letter}}
</view> </view>
</view> </view>
</view> </view>
@ -77,268 +83,410 @@
</template> </template>
<script> <script>
import { getLastSixMonth, getNewBeerListByMonth, popularStyle } from "@/api/platform.js"; import { getBreweries, getBeerList } from '@/api/bar.js'
import CustomNavBar from '@/components/CustomNavBar.vue';
import SearchBar from '@/components/SearchBar.vue'; export default {
export default { name: 'HotLabel',
components: { data() {
CustomNavBar, return {
SearchBar //
}, searchKey: '',
data() { searchTimer: null,
return {
dataList: [], //
currentMonth: [], breweries: [],
currentMonthIndex: 0, groupedBreweries: {},
popularStyleList: [], currentLetter: '',
queryForm: { letters: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
num: null,
//
loading: false
}
},
onShow() {
this.initData()
},
methods: {
//
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()
}
}, },
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,
}; //
}, async getBeerCounts() {
computed: { const promises = this.breweries.map(async (brewery) => {
// try {
groupedData() { const res = await getBeerList({ breweryId: brewery.id })
const grouped = this.rawGroupedData.reduce((acc, item) => { if (res && res.data) {
const group = item.group; brewery.beerCount = res.data.total || 0
if (!acc[group]) acc[group] = []; }
acc[group].push(item); } catch (error) {
return acc; console.error(`获取品牌 ${brewery.id} 的酒款数量失败:`, error)
}, {}); brewery.beerCount = 0
}
})
// // await Promise.all(promises)
// 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);
});
}
return grouped; //
}, groupBreweries() {
// const grouped = {}
indexLetters() {
return Object.keys(this.groupedData).sort(); this.breweries.forEach(brewery => {
}, if (!brewery.breweryName) return
},
methods: { let firstLetter = brewery.breweryName.charAt(0).toUpperCase()
hotClick(ind) { if (!/[A-Z]/.test(firstLetter)) {
this.hotActives = ind; firstLetter = '#'
}, }
toggleGlobalFilter() {
this.isOnSaleFilter = !this.isOnSaleFilter; if (!grouped[firstLetter]) {
}, grouped[firstLetter] = []
handleScroll(e) { }
const scrollTop = e.detail.scrollTop; grouped[firstLetter].push(brewery)
if (scrollTop < 2) { })
this.hotflag = true; //
} Object.keys(grouped).forEach(letter => {
}, grouped[letter].sort((a, b) =>
// a.breweryName.localeCompare(b.breweryName, 'zh')
// getPopularStyle() { )
// popularStyle().then((res) => { })
// console.log(res);
// this.popularStyleList = res.data; this.letters = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')]
// }); if (grouped['#']) {
// }, this.letters.push('#')
// getLastSixMonthFun() { }
// getLastSixMonth().then((res) => {
// console.log(res); console.log('分组后的数据:', grouped)
// this.dataList = res.data; this.groupedBreweries = grouped
// this.queryForm.num = 0; },
// this.getNewBeerListByMonthFun();
// }); //
// }, handleSearch() {
// if (this.searchTimer) {
scrollToGroup(letter) { clearTimeout(this.searchTimer)
this.scrollIntoView = letter; }
this.activeLetter = letter;
this.hotflag = false this.searchTimer = setTimeout(() => {
this.currentVisibleLetter = letter; // const searchKey = this.searchKey.trim().toLowerCase()
},
// if (!searchKey) {
calculateContainerHeight() { this.groupBreweries()
const systemInfo = uni.getSystemInfoSync(); return
const searchBarHeight = 120; // }
const hotHeaderHeight = 90; //
const hotBomHeight = 200; // const filteredBreweries = this.breweries.filter(brewery =>
this.containerHeight = systemInfo.windowHeight - searchBarHeight - hotHeaderHeight - hotBomHeight; brewery.breweryName.toLowerCase().includes(searchKey)
}, )
//
toggleFilter(letter) { const grouped = {}
this.$set(this.isOnSaleFilter, letter, !this.isOnSaleFilter[letter]); filteredBreweries.forEach(brewery => {
}, let firstLetter = brewery.breweryName.charAt(0).toUpperCase()
}, if (/[\u4e00-\u9fa5]/.test(firstLetter)) {
mounted() { firstLetter = this.getFirstPinYinLetter(brewery.breweryName)
// }
this.originalGroupedData = JSON.parse(JSON.stringify(this.groupedData)); if (!/[A-Z]/.test(firstLetter)) {
this.calculateContainerHeight(); firstLetter = '#'
this.currentVisibleLetter = this.indexLetters[0]; }
}, if (!grouped[firstLetter]) {
}; grouped[firstLetter] = []
}
grouped[firstLetter].push(brewery)
})
this.groupedBreweries = grouped
}, 300)
},
//
scrollToLetter(letter) {
this.currentLetter = letter
},
//
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> </script>
<style scoped lang="scss"> <style lang="scss" scoped>
.page { .page {
display: flex; min-height: 100vh;
flex-direction: column;
height: 100vh;
font-family: Roboto;
}
.fixed-search-bar {
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;
background: #FFFFFF; background: #FFFFFF;
padding-top: 120rpx;
//
.search-box {
position: fixed;
top: 0;
left: 0;
right: 0;
padding: 24rpx;
background: #FFFFFF;
z-index: 100;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
.search-input {
display: flex;
align-items: center;
height: 72rpx;
background: #F5F5F5;
border-radius: 36rpx;
padding: 0 24rpx;
.search-icon {
width: 32rpx;
height: 32rpx;
margin-right: 16rpx;
opacity: 0.3;
}
input {
flex: 1;
height: 100%;
font-size: 28rpx;
color: #333333;
}
.placeholder {
color: #999999;
}
}
}
//
.loading-state {
padding: 24rpx;
.loading-skeleton {
display: flex;
align-items: center;
padding: 24rpx;
margin-bottom: 24rpx;
background: #FFFFFF;
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;
&: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;
padding: 96rpx 0;
.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;
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);
}
}
}
}
} }
.hot-actives { @keyframes skeleton-loading {
font-size: 28rpx; 0% {
font-weight: 600; opacity: 0.6;
color: #030303; }
50% {
opacity: 0.8;
}
100% {
opacity: 0.6;
}
} }
.hot-bom { //
padding: 0 24rpx; .hover-effect {
background: #FFFFFF; transition: all 0.2s ease;
&:active {
transform: scale(0.96);
opacity: 0.8;
}
} }
.container { //
flex: 1; .text-ellipsis {
display: flex; overflow: hidden;
} text-overflow: ellipsis;
white-space: nowrap;
.list {
flex: 1;
height: 100%;
}
.group-title {
padding: 10px;
background-color: #f9f9f9;
font-weight: bold;
display: flex;
align-items: center;
justify-content: space-between;
}
.contact-item {
background: #FFFFFF;
padding: 10px;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
}
.index-bar {
width: 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #f8f8f8;
}
.index-bar view {
padding: 2px 0;
font-size: 12px;
cursor: pointer;
border-radius: 50%; /* 圆形背景 */
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s;
}
.index-bar view.active-letter {
background-color: #D42E78; /* 选中字母的背景色 */
color: white; /* 选中字母的文字颜色 */
}
/* 自定义 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; /* 禁止文本选择 */
}
.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;
} }
</style> </style>

View File

@ -1,33 +1,14 @@
<template> <template>
<view class="page"> <view class="page">
<!-- 筛选导航 --> <!-- 筛选导航 -->
<view class="filter-section"> <Sieving
<view class="filter-tabs"> ref="filterDropdown"
<view :dropdownMenu="dropdownMenuList"
v-for="(tab, index) in dropdownMenuList" themeColor="#D42E78"
:key="tab.prop" @confirm="handleFilterConfirm"
class="tab-item" @close="handleFilterClose"
:class="{ active: activeTab === tab.prop }" @open="handleFilterOpen"
@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"
themeColor="#D42E78"
@confirm="handleFilterConfirm"
@close="handleFilterClose"
@open="handleFilterOpen"
/>
</view>
<!-- 统计信息 --> <!-- 统计信息 -->
<view class="stats-bar"> <view class="stats-bar">
@ -45,7 +26,7 @@
@refresherrefresh="onRefresh" @refresherrefresh="onRefresh"
> >
<view <view
class="beer-card" class="beer-card hover-effect"
v-for="beer in beerList" v-for="beer in beerList"
:key="beer.id" :key="beer.id"
@click="navigateToBeerDetail(beer)" @click="navigateToBeerDetail(beer)"
@ -54,7 +35,8 @@
<image <image
:src="beer.cover" :src="beer.cover"
class="beer-image" class="beer-image"
mode="aspectFit" mode="aspectFill"
:lazy-load="true"
/> />
<view class="beer-details"> <view class="beer-details">
<text class="beer-name text-ellipsis">{{beer.beerName}}</text> <text class="beer-name text-ellipsis">{{beer.beerName}}</text>
@ -85,36 +67,6 @@
</view> </view>
</scroll-view> </scroll-view>
</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> </view>
</template> </template>
@ -125,14 +77,13 @@
popularStyle, popularStyle,
getBrands, getBrands,
getBeerStyles, getBeerStyles,
getBeerByStyle
} from "@/api/platform.js" } from "@/api/platform.js"
import CustomNavBar from '@/components/CustomNavBar.vue'
import Sieving from '@/components/sieving/index.vue' import Sieving from '@/components/sieving/index.vue'
export default { export default {
name: 'StyleSelection', name: 'StyleSelection',
components: { components: {
CustomNavBar,
Sieving Sieving
}, },
data() { data() {
@ -145,15 +96,13 @@
hasMore: true, hasMore: true,
// //
selectedStyle: '', activeTab: '',
popularStyleList: [],
// //
queryParams: { queryParams: {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
sortType: 'latest', styleId: ''
style: ''
}, },
// //
@ -212,9 +161,10 @@
async initData() { async initData() {
try { try {
await Promise.all([ await Promise.all([
this.getPopularStyles(), this.getBrandOptions(),
this.getBeerList() this.getStyleOptions()
]) ])
await this.getBeerList()
} catch (error) { } catch (error) {
console.error('初始化数据失败:', error) console.error('初始化数据失败:', error)
uni.showToast({ uni.showToast({
@ -224,13 +174,31 @@
} }
}, },
// //
async getPopularStyles() { async getBrandOptions() {
try { try {
const res = await popularStyle() const res = await getBrands()
this.popularStyleList = res.data || [] const brands = res.data || []
this.dropdownMenuList[0].options = brands.map(brand => ({
label: brand.brandName,
value: brand.id
}))
} catch (error) { } 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 = [] this.beerList = []
} }
const res = await getNewBeerListByMonth(this.queryParams) const res = await getBeerByStyle(this.queryParams)
const newList = res.data || [] const newList = res.data || []
this.beerList = isRefresh ? this.beerList = isRefresh ?
@ -278,39 +246,15 @@
sortType: sort 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) this.getBeerList(true)
}, },
handleFilterOpen(tabName) { handleFilterOpen(tabName) {
this.queryParams.style = tabName this.activeTab = tabName
}, },
handleFilterClose() { handleFilterClose() {
this.queryParams.style = '' this.activeTab = ''
},
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()
}, },
// //
@ -336,152 +280,167 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.page { .page {
min-height: 100vh; min-height: 100vh;
background: #F2F2F2; background: #F9F9F9;
.filter-section { //
background: #FFFFFF; .stats-bar {
border-bottom: 1rpx solid #F5F5F5; padding: 16rpx 24rpx;
background: #FFFFFF;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
.filter-tabs { .stats-text {
font-size: 24rpx;
color: #999999;
}
}
//
.content-container {
flex: 1;
overflow: hidden;
.beer-list {
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;
align-items: center;
padding: 24rpx;
.beer-image {
width: 120rpx;
height: 120rpx;
border-radius: 12rpx;
margin-right: 24rpx;
object-fit: cover;
}
.beer-details {
flex: 1;
.beer-name {
font-size: 28rpx;
font-weight: 600;
color: #333333;
margin-bottom: 8rpx;
}
.beer-style {
font-size: 24rpx;
color: #666666;
margin-bottom: 8rpx;
}
.brand-name {
font-size: 24rpx;
color: #999999;
}
}
.arrow-icon {
width: 32rpx;
height: 32rpx;
margin-left: 16rpx;
opacity: 0.3;
}
}
}
.empty-state {
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
height: 64rpx; padding: 96rpx 0;
padding: 0 32rpx;
.tab-item { .empty-image {
width: 122rpx; width: 240rpx;
height: 64rpx; height: 240rpx;
margin-bottom: 32rpx;
}
.empty-text {
font-size: 28rpx;
color: #999999;
}
}
.loading-status {
padding: 32rpx 0;
text-align: center;
.loading-wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 28rpx;
color: #606060;
position: relative;
margin-right: 24rpx;
.tab-text { .loading-icon {
margin-right: 8rpx; width: 40rpx;
height: 40rpx;
margin-right: 16rpx;
} }
.tab-icon { .loading-text {
width: 24rpx; font-size: 24rpx;
height: 24rpx; color: #999999;
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%);
}
} }
} }
}
}
.stats-bar { .no-more {
padding: 20rpx 32rpx; font-size: 24rpx;
color: #999999;
.stats-text {
font-size: 24rpx;
color: #606060;
}
}
.content-container {
.beer-list {
padding: 0 32rpx;
.beer-card {
background: #FFFFFF;
margin-bottom: 24rpx;
.beer-info {
display: flex;
align-items: center;
padding: 24rpx;
.beer-image {
width: 144rpx;
height: 204rpx;
margin-right: 24rpx;
background: #F8F8F8;
}
.beer-details {
flex: 1;
.beer-name {
font-size: 28rpx;
color: #1E2019;
margin-bottom: 16rpx;
}
.beer-style {
font-size: 24rpx;
color: rgba(30, 32, 25, 0.7);
margin-bottom: 12rpx;
}
.brand-name {
font-size: 24rpx;
color: #606060;
}
}
.arrow-icon {
width: 32rpx;
height: 32rpx;
opacity: 0.4;
}
}
} }
} }
} }
.style-popup {
height: 100%;
display: flex;
flex-direction: column;
.style-list {
flex: 1;
padding: 32rpx;
.style-item {
padding: 24rpx 0;
font-size: 28rpx;
color: #333333;
&.active {
color: #19367A;
font-weight: 500;
}
}
}
.clear-button {
height: 88rpx;
line-height: 88rpx;
text-align: center;
font-size: 28rpx;
color: #606060;
border-top: 1rpx solid #F5F5F5;
}
}
} }
}
.text-ellipsis { //
overflow: hidden; .hover-effect {
text-overflow: ellipsis; transition: all 0.2s ease;
white-space: nowrap;
&: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> </style>

View File

@ -6,12 +6,12 @@
</view> </view>
</view> --> </view> -->
<view v-if="showJoinImg && bannerJoin" class="join-box" @click="toJoin"> <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> </view>
<!-- 轮播 --> <!-- 轮播 -->
<swiper v-else class="join-box" circular :autoplay="true" :indicator-dots="true"> <swiper v-else class="join-box" circular :autoplay="true" :indicator-dots="true">
<swiper-item v-for="(item,index) in homeBanner" :key="index"> <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-item>
</swiper> </swiper>
@ -19,7 +19,7 @@
<!-- 快捷导航 --> <!-- 快捷导航 -->
<view class="bg-white" <view class="bg-white"
style="border-radius: 30rpx 30rpx 12rpx 12rpx;padding-top:8rpx;position: relative;margin-top:-20rpx"> 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"> <view class="search-input">
<text class="cuIcon-search" style="font-size: 40rpx;color: #A2A2A2;margin-right: 24rpx;"></text> <text class="cuIcon-search" style="font-size: 40rpx;color: #A2A2A2;margin-right: 24rpx;"></text>
<text style="color: #A2A2A2;">搜索酒款名称品牌名称啤酒风格</text> <text style="color: #A2A2A2;">搜索酒款名称品牌名称啤酒风格</text>
@ -29,29 +29,11 @@
</view> </view>
</view> </view>
<view class="flex justify-between bg-white" style="padding: 20rpx 36rpx;"> <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"> <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> </view>
<text class="text-bold">新酒上市</text> <text class="text-bold">{{item}}</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>
</view> </view>
</view> </view>
</view> </view>
@ -143,6 +125,7 @@
total: 0, total: 0,
loading: false, loading: false,
finished: false, finished: false,
navItems: ['新酒上市', '生成酒单', '酒币换购', '关注厂牌'],
}; };
}, },
created() { created() {
@ -643,4 +626,70 @@
.activity-item { .activity-item {
display: block; 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> </style>