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

View File

@ -1,54 +1,77 @@
<template> <template>
<view class="page"> <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="filter-tabs">
<view class="tag" @click="changeTab(1)">品牌筛选</view> <view class="tabs-content">
<view class="tag" :class="{'active': queryForm.orderByColumn == 'create_time'}" @click="changeTab(2)">上市时间</view> <view
<view class="tag" :class="{'active': queryForm.orderByColumn == 'beer_overall_rating'}" @click="changeTab(3)">评分</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> </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)"> <view class="list-container">
<image :src="it.cover" style="width: 144rpx;height: 204rpx;margin-right:20rpx;border-radius: 12rpx;"> <scroll-view
</image> scroll-y="true"
<view class="flex-1"> @scrolltolower="pageChange"
refresher-enabled="true"
<view class="word-all margin-bottom-sm" style="color:#1E2019"> :refresher-triggered="isRefreshing"
{{it.beerName}} @refresherrefresh="onRefresh"
</view> class="scroll-view"
<view class="word-all margin-bottom-sm" style="font-size: 24rpx;color: rgba(30, 32, 25, 0.8);"> >
{{ it.beerStyles}} <view class="beer-wrapper">
</view> <beer-card
<view class="word-all margin-bottom-sm" style="color:#1E2019"> v-for="it in beers"
<image :src="it.brandLogo" style="width: 30rpx;height: 30rpx;margin-right: 12rpx;"> :key="it.id"
</image> :item="it"
{{ it.brandName}} @click="toBeer"
</view> ></beer-card>
<!-- <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> </view>
<view class="cu-load" :class="loading?'loading': beers.length == total ? 'over' :'more'"></view>
</scroll-view> </scroll-view>
</view> </view>
<!-- 品牌筛选弹窗 -->
<brand-filter ref="brandFilterRef" @confirm="onBrandFilterConfirm"></brand-filter>
</view>
</template> </template>
<script> <script>
import { import {
getBeerByStyle, getBeerByStyle,
} from "@/api/platform.js" } from "@/api/platform.js"
import brandFilter from '@/components/brandFilter.vue'
import BeerCard from '@/components/BeerCard.vue'
export default { export default {
components: {
brandFilter,
BeerCard
},
data() { data() {
return { return {
beerStyles: '', beerStyles: '',
beers: [], beers: [],
total: 0, total: 0,
loading: false,
isRefreshing: false,
selectedBrand: null,
queryForm: { queryForm: {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
@ -61,7 +84,17 @@
onLoad({ onLoad({
beerStyles beerStyles
}) { }) {
this.beerStyles = beerStyles
this.queryForm.style = beerStyles this.queryForm.style = beerStyles
//
uni.setNavigationBarTitle({
title: beerStyles || '啤酒风格'
})
//
uni.setNavigationBarColor({
frontColor: '#ffffff', //
backgroundColor: '#19367A' //
})
this.searchByStyle() this.searchByStyle()
}, },
methods: { methods: {
@ -73,9 +106,6 @@
case 0: case 0:
this.queryForm.orderByColumn = 'beer_reviews_count' this.queryForm.orderByColumn = 'beer_reviews_count'
break; break;
case 1:
this.queryForm.orderByColumn = ''
break;
case 2: case 2:
this.queryForm.orderByColumn = 'create_time' this.queryForm.orderByColumn = 'create_time'
break; break;
@ -88,10 +118,21 @@
this.searchByStyle() this.searchByStyle()
}, },
searchByStyle() { searchByStyle() {
this.loading = true
getBeerByStyle(this.queryForm).then(res => { getBeerByStyle(this.queryForm).then(res => {
console.log(res) console.log(res)
this.total = res.total this.total = res.total
if (this.queryForm.pageNum === 1) {
this.beers = res.rows 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) { toBeer(item) {
@ -100,53 +141,160 @@
}) })
}, },
pageChange() { pageChange() {
if (this.beers.length < this.total) { if (this.beers.length < this.total && !this.loading) {
this.queryForm.pageNum++ this.queryForm.pageNum++
this.searchByStyle() 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();
} }
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.page { .page {
width: 100%; min-height: 100vh;
background: #F2F2F2; background: #F9F9F9;
display: flex;
flex-direction: column;
}
.top { .filter-tabs {
width: 100%; background: #FFFFFF;
background-color: #fff; padding: 0;
padding: 16rpx 30rpx; position: sticky;
.tag { top: 0;
color: #606060; z-index: 99;
border-radius: 12rpx; box-shadow: 0rpx 1rpx 3rpx 0rpx rgba(0, 0, 0, 0.1);
width: 134rpx;
.tabs-content {
display: flex;
align-items: center;
padding: 24rpx;
height: 88rpx;
margin-bottom: 12rpx;
margin-top: 12rpx;
.tab-item {
width: 144rpx;
height: 64rpx; height: 64rpx;
line-height: 64rpx; line-height: 64rpx;
text-align: center; border-radius: 12rpx;
font-size: 24rpx; background: #F9F9F9;
background-color: #F9F9F9;
margin-right: 16rpx; margin-right: 16rpx;
font-size: 24rpx;
font-weight: 500;
text-align: center;
&.active-tag {
color: #FFF;
background: #D42E78;
}
} }
.active { .brand-filter {
background-color: #39E5B1; 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; color: #FFFFFF;
} }
border-bottom:1px solid #E7E7E7; .filter-icon {
filter: brightness(0) invert(1);
}
}
}
}
}
.list-container {
flex: 1;
display: flex;
flex-direction: column;
.scroll-view {
flex: 1;
} }
.beer-wrapper {
padding: 24rpx;
.beerCard {
border-radius: 12rpx;
background: #FFFFFF;
padding: 24rpx 24rpx;
margin: 0 30rpx 24rpx;
} }
.cu-load {
text-align: center;
padding: 24rpx;
color: #999999;
font-size: 24rpx;
&.loading::after {
content: "加载中...";
} }
&.over::after {
content: "没有更多了";
}
&.more::after {
content: "上拉加载更多";
}
}
}
</style> </style>

View File

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