492 lines
12 KiB
Vue
492 lines
12 KiB
Vue
<template>
|
|
<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 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 v-else class="index-list">
|
|
<scroll-view
|
|
scroll-y
|
|
class="content"
|
|
:scroll-into-view="currentLetter ? 'letter-' + currentLetter : ''"
|
|
>
|
|
<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>
|
|
</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="letter-nav">
|
|
<view
|
|
v-for="letter in letters"
|
|
:key="letter"
|
|
class="letter-item"
|
|
:class="{ active: currentLetter === letter }"
|
|
@click="scrollToLetter(letter)"
|
|
>
|
|
{{letter}}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import { getBreweries, getBeerList } from '@/api/bar.js'
|
|
|
|
export default {
|
|
name: 'HotLabel',
|
|
data() {
|
|
return {
|
|
// 搜索相关
|
|
searchKey: '',
|
|
searchTimer: null,
|
|
|
|
// 数据相关
|
|
breweries: [],
|
|
groupedBreweries: {},
|
|
currentLetter: '',
|
|
letters: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
|
|
|
|
// 加载状态
|
|
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()
|
|
}
|
|
},
|
|
|
|
// 获取每个品牌的酒款数量
|
|
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)
|
|
},
|
|
|
|
// 按首字母分组品牌
|
|
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
|
|
},
|
|
|
|
// 搜索处理
|
|
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
|
|
},
|
|
|
|
// 导航到品牌详情
|
|
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 lang="scss" scoped>
|
|
.page {
|
|
min-height: 100vh;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@keyframes skeleton-loading {
|
|
0% {
|
|
opacity: 0.6;
|
|
}
|
|
50% {
|
|
opacity: 0.8;
|
|
}
|
|
100% {
|
|
opacity: 0.6;
|
|
}
|
|
}
|
|
|
|
// 添加按键反馈效果
|
|
.hover-effect {
|
|
transition: all 0.2s ease;
|
|
|
|
&:active {
|
|
transform: scale(0.96);
|
|
opacity: 0.8;
|
|
}
|
|
}
|
|
|
|
// 文本省略
|
|
.text-ellipsis {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
</style> |