refactor: 抽离BeerCard组件,优化代码结构

This commit is contained in:
davy 2025-04-07 13:40:26 +08:00
parent 92c79166f5
commit 6e6d4e145f
5 changed files with 452 additions and 293 deletions

121
components/BeerCard.vue Normal file
View File

@ -0,0 +1,121 @@
<template>
<view class="beerCard flex align-center" @click="onClick">
<image :src="item.cover" class="beer-cover"></image>
<view class="flex-1 beer-info">
<view class="beer-name">{{item.beerName}}</view>
<view class="beer-style">{{ item.beerStyles}}</view>
<view class="brand-info flex align-center">
<image :src="item.brandLogo" class="brand-logo"></image>
<text class="brand-name">{{ item.brandName}}</text>
</view>
<view class="rating-info flex align-center">
<text class="rating">
<text class="cuIcon-favorfill"></text>
{{ item.beerOverallRating}}
</text>
<text class="review-count">{{item.beerReviewsCount}} 条评论</text>
</view>
</view>
<image src="@/static/right-arrow.png" class="arrow-icon"></image>
</view>
</template>
<script>
export default {
name: 'BeerCard',
props: {
item: {
type: Object,
required: true
}
},
methods: {
onClick() {
this.$emit('click', this.item)
}
}
}
</script>
<style lang="scss" scoped>
.beerCard {
border-radius: 12rpx;
background: #FFFFFF;
padding: 24rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
}
.beer-cover {
width: 144rpx;
height: 204rpx;
margin-right: 20rpx;
border-radius: 12rpx;
object-fit: cover;
}
.beer-info {
.beer-name {
font-size: 32rpx;
color: #1E2019;
font-weight: 500;
margin-bottom: 12rpx;
}
.beer-style {
font-size: 24rpx;
color: rgba(30, 32, 25, 0.8);
margin-bottom: 12rpx;
}
.brand-info {
margin-bottom: 12rpx;
.brand-logo {
width: 30rpx;
height: 30rpx;
margin-right: 12rpx;
border-radius: 50%;
}
.brand-name {
font-size: 28rpx;
color: #1E2019;
}
}
.rating-info {
.rating {
background: #F5F5F5;
padding: 8rpx 16rpx;
border-radius: 16rpx;
color: #5F5F63;
font-size: 24rpx;
margin-right: 16rpx;
.cuIcon-favorfill {
color: #FFBC11;
font-size: 30rpx;
margin-right: 4rpx;
}
}
.review-count {
color: #5F5F63;
font-size: 24rpx;
}
}
}
.arrow-icon {
width: 40rpx;
height: 40rpx;
margin-right: 10rpx;
}
}
</style>

View File

@ -1,91 +0,0 @@
<template>
<view class="search-bar" :style="{ background:bgcolor, borderRadius:borderRs}">
<view class="search-input-container" >
<input
v-model="searchQuery"
class="search-input"
:placeholder="placeholders"
@input="handleInput"
/>
<view class="search-icon-container" @click="handleSearch">
<uni-icons type="search" size="30" color="#fff"></uni-icons>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'SearchBar',
props: {
placeholders: {
type: String,
default: '搜索'
},
borderRs: '',
bgcolor: '',
},
data() {
return {
searchQuery: ''
};
},
methods: {
handleInput(event) {
this.searchQuery = event.target.value;
},
handleSearch() {
//
console.log('搜索内容:', this.searchQuery);
}
}
};
</script>
<style scoped>
.search-bar {
display: flex;
justify-content: center;
align-items: center;
padding: 20rpx;
/* background-color: #19367A;
border-radius: 0px 0px 24rpx 24rpx; */
/* border-radius: 30rpx; */
}
.search-input-container {
display: flex;
align-items: center;
width: 100%;
background-color: #fff; /* 输入框背景色 */
border-radius: 30rpx;
overflow: hidden; /* 确保子元素不会超出容器 */
}
.search-input {
flex: 1;
padding: 10rpx 0;
padding-left: 20rpx;
font-size: 28rpx;
color: #333;
outline: none;
border: none;
height: 80rpx; /* 确保输入框高度 */
}
.search-icon-container {
display: flex;
justify-content: center;
align-items: center;
width: 80rpx;
height: 80rpx; /* 确保图标容器高度与输入框一致 */
background-color: #D42E78; /* 搜索图标背景色 */
border-radius: 0 30rpx 30rpx 0; /* 圆角处理 */
color: #fff;
}
.search-icon {
font-size: 30rpx;
}
</style>

View File

@ -65,19 +65,7 @@
<text class="date-day">{{ it.launchDate.slice(8, 10)}}</text>
<text class="date-month">/{{ it.launchDate.slice(5, 7)}}</text>
</view>
<view class="beer-card" @tap="toBeer(it)">
<image :src="it.cover" class="beer-image" mode="aspectFit" />
<view class="beer-info">
<view class="main-info">
<text class="beer-name text-ellipsis">{{it.beerName}}</text>
<view class="sub-info">
<text class="beer-style text-ellipsis">{{it.beerStyles}}</text>
<text class="brand-name text-ellipsis">{{it.brandName}}</text>
</view>
</view>
</view>
<image src="@/static/right-arrow.png" class="arrow-icon" mode="aspectFit" />
</view>
<beer-card :item="it" @click="toBeer(it)"></beer-card>
</view>
</view>
@ -91,10 +79,14 @@
import {
getLastSixMonth,
getNewBeerListByMonth,
popularStyle,
} from "@/api/platform.js"
popularStyle
} from "@/api/platform.js";
import BeerCard from '@/components/BeerCard.vue';
export default {
components: {
BeerCard
},
data() {
return {
dataList: [],
@ -115,53 +107,53 @@
};
},
onLoad() {
this.getLastSixMonthFun()
this.getLastSixMonthFun();
},
methods: {
changeMonth(item, index) {
this.currentMonthIndex = index
this.currentMonth = item.beers
this.queryForm.num = index
this.getNewBeerListByMonthFun()
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()
})
console.log(res);
this.dataList = res.data;
this.queryForm.num = 0;
this.getNewBeerListByMonthFun();
});
},
toBeer(item) {
uni.navigateTo({
url: '/pages/index/review?beerId=' + item.id
})
});
},
getNewBeerListByMonthFun() {
getNewBeerListByMonth(this.queryForm).then(res => {
console.log(res)
this.currentMonth = res.data
})
console.log(res);
this.currentMonth = res.data;
});
},
handleStyleChange(item) {
if (this.currentStyle === item.value) return
this.currentStyle = item.value
if (this.currentStyle === item.value) return;
this.currentStyle = item.value;
//
if (item.value === 'all') {
this.queryForm.style = ''
this.queryForm.style = '';
} else if (item.value === 'other') {
//
this.queryForm.style = '!ipa,!lager,!ale,!cider'
this.queryForm.style = '!ipa,!lager,!ale,!cider';
} else {
// 使
this.queryForm.style = item.value
this.queryForm.style = item.value;
}
this.getNewBeerListByMonthFun()
}
this.getNewBeerListByMonthFun();
}
}
};
</script>
<style lang="scss" scoped>
@ -210,19 +202,21 @@
z-index: 3;
}
}
//
.month-scroll {
flex: 1;
// margin: 32rpx 0 0 0;
height: 100%;
position: relative;
z-index: 0;
//
.month-content {
height: 100%;
display: inline-flex;
align-items: center;
padding: 0 64rpx 0 32rpx;
//
.month-item {
position: relative;
@ -232,10 +226,12 @@
margin-right: 48rpx;
flex-shrink: 0;
padding: 0 8rpx;
//
&:last-child {
margin-right: 32rpx;
}
//
.month-info {
display: flex;
@ -255,6 +251,7 @@
line-height: 1;
}
}
//
&.active {
&::after { //
@ -269,7 +266,7 @@
border-radius: 8rpx;
z-index: 0;
}
//
&::before { //
content: '';
position: absolute;
@ -281,15 +278,17 @@
background: #D42E78;
border-radius: 2rpx;
}
//
.month-info {
position: relative;
z-index: 1;
//
.month-num {
color: #D42E78;
font-weight: 500;
}
.count {
color: #D42E78;
}
@ -306,7 +305,7 @@
top: 80rpx;
background: #FFFFFF;
box-shadow: 0rpx 1rpx 3rpx 0rpx rgba(0, 0, 0, 0.1);
margin-top: -12rpx; //
margin-top: -12rpx;
.style-scroll {
.style-content {
@ -317,6 +316,7 @@
display: flex;
align-items: center;
white-space: nowrap;
//
.style-item {
position: relative;
@ -327,8 +327,9 @@
flex-shrink: 0;
&:last-child {
margin-right: 32rpx;
margin-right: 24rpx;
}
//
.style-text {
height: 64rpx;
@ -341,6 +342,7 @@
transition: all 0.3s ease;
white-space: nowrap;
}
//
&.active {
.style-text {
@ -367,23 +369,27 @@
.list-group {
margin-bottom: 24rpx;
//
.date-header {
display: flex;
align-items: center;
margin-bottom: 16rpx;
//
.date-icon {
width: 28rpx;
height: 28rpx;
margin-right: 8rpx;
}
//
.date-day {
font-size: 36rpx;
color: #030303;
font-weight: 600;
}
//
.date-month {
font-size: 24rpx;
@ -391,6 +397,7 @@
margin-left: 4rpx;
}
}
//
.beer-card {
display: flex;
@ -398,6 +405,7 @@
padding: 24rpx;
background: #FFFFFF;
border-radius: 12rpx;
//
.beer-image {
width: 144rpx;
@ -407,6 +415,7 @@
background: #F8F8F8;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
//
.beer-info {
flex: 1;
@ -415,6 +424,7 @@
display: flex;
flex-direction: column;
justify-content: space-between;
//
.main-info {
margin-bottom: 24rpx;
@ -426,9 +436,11 @@
margin-bottom: 24rpx;
line-height: 1.4;
}
//
.sub-info {
margin-bottom: 16rpx;
//
.beer-style {
font-size: 26rpx;
@ -442,43 +454,8 @@
}
}
}
//
.store-info {
display: flex;
align-items: center;
//
.store-icon {
width: 32rpx;
height: 32rpx;
margin-right: 8rpx;
border-radius: 4rpx;
}
//
.store-name {
flex: 1;
font-size: 24rpx;
color: #606060;
margin-right: 16rpx;
}
//
.rating {
display: flex;
align-items: baseline;
//
.rating-num {
font-size: 24rpx;
color: #FFC700;
font-weight: 600;
margin-right: 8rpx;
}
//
.review-count {
font-size: 20rpx;
color: #999999;
}
}
}
}
//
.arrow-icon {
width: 32rpx;
@ -486,6 +463,7 @@
opacity: 0.3;
margin-top: 8rpx;
}
//
&:active {
opacity: 0.8;
@ -493,6 +471,7 @@
}
}
}
//
.empty-tip {
text-align: center;
@ -503,6 +482,7 @@
}
}
}
//
.text-ellipsis {
overflow: hidden;

View File

@ -1,54 +1,77 @@
<template>
<view class="page">
<view class="top flex justify-start">
<view class="tag" :class="{'active': queryForm.orderByColumn == 'beer_reviews_count'}" @click="changeTab(0)">人气蹿升</view>
<view class="tag" @click="changeTab(1)">品牌筛选</view>
<view class="tag" :class="{'active': queryForm.orderByColumn == 'create_time'}" @click="changeTab(2)">上市时间</view>
<view class="tag" :class="{'active': queryForm.orderByColumn == 'beer_overall_rating'}" @click="changeTab(3)">评分</view>
<!-- 筛选导航 -->
<view class="filter-tabs">
<view class="tabs-content">
<view
class="tab-item"
:class="{'active-tag': queryForm.orderByColumn == 'beer_reviews_count'}"
@click="changeTab(0)"
>人气蹿升</view>
<view
class="tab-item"
:class="{'active-tag': queryForm.orderByColumn == 'create_time'}"
@click="changeTab(2)"
>上市时间</view>
<view
class="tab-item"
:class="{'active-tag': queryForm.orderByColumn == 'beer_overall_rating'}"
@click="changeTab(3)"
>评分</view>
<view class="brand-filter" :class="{ active: selectedBrand !== null }" @click="showBrandFilter">
<text>筛选品牌</text>
<image :src="'/static/icons/filter.svg'" mode="aspectFit" class="filter-icon"></image>
</view>
</view>
</view>
<scroll-view scroll-y style="height: calc(100vh - 110rpx);width: 100%;padding: 24rpx 0;" @scrolltolower="pageChange">
<view class="beerCard flex align-center" v-for="it in beers" @click="toBeer(it)">
<image :src="it.cover" style="width: 144rpx;height: 204rpx;margin-right:20rpx;border-radius: 12rpx;">
</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">
<image :src="it.brandLogo" style="width: 30rpx;height: 30rpx;margin-right: 12rpx;">
</image>
{{ it.brandName}}
</view>
<!-- <view style="margin-bottom: 18rpx;font-size: 30rpx;color: #5F5F63;">{{ beer.brandName}}</view> -->
<text style="border-radius: 16rpx;background: #F5F5F5;padding: 8rpx 16rpx;color: #5F5F63;">
<text class="cuIcon-favorfill" style="font-size: 30rpx;color: #FFBC11;"></text>
{{ it.beerOverallRating}}
</text>
<text style="color: #5F5F63;margin-left: 16rpx;">{{it.beerReviewsCount}} 条评论</text>
</view>
<image src="@/static/right-arrow.png" style="width: 40rpx;height: 40rpx;margin-right: 10rpx;">
</image>
<!-- 列表内容区域 -->
<view class="list-container">
<scroll-view
scroll-y="true"
@scrolltolower="pageChange"
refresher-enabled="true"
:refresher-triggered="isRefreshing"
@refresherrefresh="onRefresh"
class="scroll-view"
>
<view class="beer-wrapper">
<beer-card
v-for="it in beers"
:key="it.id"
:item="it"
@click="toBeer"
></beer-card>
</view>
<view class="cu-load" :class="loading?'loading': beers.length == total ? 'over' :'more'"></view>
</scroll-view>
</view>
<!-- 品牌筛选弹窗 -->
<brand-filter ref="brandFilterRef" @confirm="onBrandFilterConfirm"></brand-filter>
</view>
</template>
<script>
import {
getBeerByStyle,
} from "@/api/platform.js"
import brandFilter from '@/components/brandFilter.vue'
import BeerCard from '@/components/BeerCard.vue'
export default {
components: {
brandFilter,
BeerCard
},
data() {
return {
beerStyles: '',
beers: [],
total: 0,
loading: false,
isRefreshing: false,
selectedBrand: null,
queryForm: {
pageNum: 1,
pageSize: 10,
@ -61,7 +84,17 @@
onLoad({
beerStyles
}) {
this.beerStyles = beerStyles
this.queryForm.style = beerStyles
//
uni.setNavigationBarTitle({
title: beerStyles || '啤酒风格'
})
//
uni.setNavigationBarColor({
frontColor: '#ffffff', //
backgroundColor: '#19367A' //
})
this.searchByStyle()
},
methods: {
@ -73,9 +106,6 @@
case 0:
this.queryForm.orderByColumn = 'beer_reviews_count'
break;
case 1:
this.queryForm.orderByColumn = ''
break;
case 2:
this.queryForm.orderByColumn = 'create_time'
break;
@ -88,10 +118,21 @@
this.searchByStyle()
},
searchByStyle() {
this.loading = true
getBeerByStyle(this.queryForm).then(res => {
console.log(res)
this.total = res.total
if (this.queryForm.pageNum === 1) {
this.beers = res.rows
} else {
this.beers = [...this.beers, ...res.rows]
}
this.loading = false
this.isRefreshing = false
}).catch(err => {
console.error('获取啤酒列表失败:', err)
this.loading = false
this.isRefreshing = false
})
},
toBeer(item) {
@ -100,10 +141,44 @@
})
},
pageChange() {
if (this.beers.length < this.total) {
if (this.beers.length < this.total && !this.loading) {
this.queryForm.pageNum++
this.searchByStyle()
}
},
//
showBrandFilter() {
//
if (!this.beers || this.beers.length === 0) {
this.queryForm.pageNum = 1;
this.searchByStyle().then(() => {
this.$refs.brandFilterRef.extractBrandsFromList(this.beers);
this.$refs.brandFilterRef.open();
});
} else {
this.$refs.brandFilterRef.extractBrandsFromList(this.beers);
this.$refs.brandFilterRef.open();
}
},
//
onBrandFilterConfirm(result) {
this.selectedBrand = result.id;
this.queryForm.breweryId = result.id;
this.queryForm.pageNum = 1;
this.beers = [];
this.searchByStyle();
},
//
onRefresh() {
this.isRefreshing = true;
//
this.queryForm.pageNum = 1;
this.beers = [];
//
this.searchByStyle();
}
}
}
@ -111,42 +186,115 @@
<style lang="scss" scoped>
.page {
width: 100%;
background: #F2F2F2;
.top {
width: 100%;
background-color: #fff;
padding: 16rpx 30rpx;
.tag {
color: #606060;
border-radius: 12rpx;
width: 134rpx;
height: 64rpx;
line-height: 64rpx;
text-align: center;
font-size: 24rpx;
background-color: #F9F9F9;
margin-right: 16rpx;
min-height: 100vh;
background: #F9F9F9;
display: flex;
flex-direction: column;
}
.active {
background-color: #39E5B1;
.filter-tabs {
background: #FFFFFF;
padding: 0;
position: sticky;
top: 0;
z-index: 99;
box-shadow: 0rpx 1rpx 3rpx 0rpx rgba(0, 0, 0, 0.1);
.tabs-content {
display: flex;
align-items: center;
padding: 24rpx;
height: 88rpx;
margin-bottom: 12rpx;
margin-top: 12rpx;
.tab-item {
width: 144rpx;
height: 64rpx;
line-height: 64rpx;
border-radius: 12rpx;
background: #F9F9F9;
margin-right: 16rpx;
font-size: 24rpx;
font-weight: 500;
text-align: center;
&.active-tag {
color: #FFF;
background: #D42E78;
}
}
.brand-filter {
margin-left: auto;
display: flex;
align-items: center;
justify-content: center;
height: 64rpx;
min-width: 144rpx;
padding: 0 24rpx;
border-radius: 12rpx;
background: #FFFFFF;
border: 1rpx solid #D42E78;
text {
color: #D42E78;
font-size: 24rpx;
font-weight: 500;
margin-right: 8rpx;
}
.filter-icon {
width: 32rpx;
height: 32rpx;
}
&.active {
background: #D42E78;
border-color: #D42E78;
text {
color: #FFFFFF;
}
border-bottom:1px solid #E7E7E7;
.filter-icon {
filter: brightness(0) invert(1);
}
}
}
}
}
.list-container {
flex: 1;
display: flex;
flex-direction: column;
.beerCard {
border-radius: 12rpx;
background: #FFFFFF;
padding: 24rpx 24rpx;
margin: 0 30rpx 24rpx;
.scroll-view {
flex: 1;
}
.beer-wrapper {
padding: 24rpx;
}
.cu-load {
text-align: center;
padding: 24rpx;
color: #999999;
font-size: 24rpx;
&.loading::after {
content: "加载中...";
}
&.over::after {
content: "没有更多了";
}
&.more::after {
content: "上拉加载更多";
}
}
}
</style>

View File

@ -78,7 +78,7 @@
<view class="sub">全系列{{item.popular}}款产品在售</view>
</view>
<image
src="/static/bg/deletes.svg"
src="/static/right-arrow.png"
class="arrow">
</image>
</view>
@ -647,10 +647,11 @@
}
},
//
//
searchByStyle(item) {
this.keyword = item.beerStyles
this.search()
uni.navigateTo({
url: '/pages/index/styleBeer?beerStyles=' + item.beerStyles
});
},
//