You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

405 lines
14 KiB

<template>
<view class="breed-select-container">
<!-- 搜索栏 -->
<view class="search-container">
<u-search
v-model="searchValue"
placeholder="搜索宠物品种"
:show-action="false"
@change="onSearchChange"
shape="round"
bg-color="#fff"
></u-search>
</view>
<!-- 品种列表 -->
<view class="breed-list-container">
<scroll-view
scroll-y="true"
class="breed-list"
:scroll-into-view="scrollIntoView"
@scroll="onScroll"
scroll-with-animation="true"
>
<view
v-for="letter in alphabetList"
:key="letter"
v-if="groupedBreeds && groupedBreeds[letter] && Array.isArray(groupedBreeds[letter]) && groupedBreeds[letter].length > 0"
:id="`section-${letter}`"
class="breed-section"
>
<view class="section-header">{{ letter }}</view>
<view
v-for="(breed, index) in groupedBreeds[letter]"
:key="`${letter}-${index}`"
class="breed-item"
@click="() => selectBreed(breed)"
>
<text class="breed-name">{{ breed }}</text>
<view class="breed-divider" v-if="index < groupedBreeds[letter].length - 1"></view>
</view>
</view>
</scroll-view>
</view>
<!-- 字母索引 -->
<view class="letter-index">
<view
v-for="letter in alphabetList"
:key="letter"
class="letter-item"
:class="{ 'active': currentLetter === letter, 'has-content': groupedBreeds && groupedBreeds[letter] && Array.isArray(groupedBreeds[letter]) && groupedBreeds[letter].length > 0 }"
@click="scrollToLetter(letter)"
>
{{ letter }}
</view>
</view>
</view>
</template>
<script>
import { getDictList } from "@/api/system/user"
export default {
data() {
return {
petType: 'dog',
searchValue: '',
breedData: [],
filteredBreeds: [],
groupedBreeds: {},
alphabetList: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
currentLetter: 'A',
scrollIntoView: '',
selectedBreed: '',
scrollTimer: null
}
},
onLoad(options) {
this.petType = options.petType || 'dog'
this.selectedBreed = options.selectedBreed || ''
this.getPetBreeds()
},
methods: {
// 获取宠物品种数据
async getPetBreeds() {
try {
const petBreedType = this.petType === 'cat' ? 'pet_brand_cat' : 'pet_brand_dog'
const res = await getDictList(petBreedType)
if (res && res.code === 200 && res.data && Array.isArray(res.data)) {
// 过滤掉空值并去重
const validBreeds = res.data
.filter(e => e && e.dictLabel && typeof e.dictLabel === 'string')
.map(e => e.dictLabel.trim())
.filter(label => label.length > 0)
this.breedData = Array.from(new Set(validBreeds)).sort((a, b) => a.localeCompare(b, 'zh-CN'))
this.filteredBreeds = [...this.breedData]
this.groupBreedsByLetter()
console.log('品种数据加载成功,共', this.breedData.length, '个品种')
} else {
console.error('API返回数据格式异常:', res)
uni.showToast({
title: '获取品种数据失败',
icon: 'none'
})
}
} catch (error) {
console.error('获取品种数据失败:', error)
uni.showToast({
title: '获取品种数据失败',
icon: 'none'
})
}
},
// 按字母分组品种
groupBreedsByLetter() {
// 初始化所有字母组
this.groupedBreeds = {}
this.alphabetList.forEach(letter => {
this.groupedBreeds[letter] = []
})
// 确保filteredBreeds是数组且不为空
if (!this.filteredBreeds || !Array.isArray(this.filteredBreeds)) {
console.warn('filteredBreeds不是有效数组')
this.filteredBreeds = []
return
}
// 按字母分组
this.filteredBreeds.forEach(breed => {
if (breed && typeof breed === 'string' && breed.trim()) {
const firstChar = this.getFirstChar(breed)
if (this.groupedBreeds && this.groupedBreeds[firstChar] && Array.isArray(this.groupedBreeds[firstChar])) {
this.groupedBreeds[firstChar].push(breed)
}
}
})
// 对每个字母组内的品种进行排序
if (this.groupedBreeds) {
Object.keys(this.groupedBreeds).forEach(letter => {
if (this.groupedBreeds[letter] && Array.isArray(this.groupedBreeds[letter])) {
this.groupedBreeds[letter].sort((a, b) => a.localeCompare(b, 'zh-CN'))
}
})
}
},
// 获取品种名称的首字母
getFirstChar(breed) {
if (!breed || typeof breed !== 'string' || breed.trim() === '') {
return 'A'
}
const firstChar = breed.charAt(0)
// 如果是中文字符,返回拼音首字母
if (/[\u4e00-\u9fa5]/.test(firstChar)) {
return this.getPinyinFirstChar(firstChar)
}
return firstChar.toUpperCase()
},
// 获取中文字符的拼音首字母
getPinyinFirstChar(char) {
const pinyinMap = {
'阿': 'A', '艾': 'A', '澳': 'A', '爱': 'A', '安': 'A', '奥': 'A',
'巴': 'B', '比': 'B', '博': 'B', '边': 'B', '布': 'B', '伯': 'B',
'藏': 'C', '柴': 'C', '长': 'C', '成': 'C', '查': 'C', '春': 'C',
'大': 'D', '德': 'D', '杜': 'D', '斗': 'D', '丹': 'D', '道': 'D',
'俄': 'E', '恩': 'E', '尔': 'E',
'法': 'F', '芬': 'F', '佛': 'F', '费': 'F',
'高': 'G', '贵': 'G', '古': 'G', '格': 'G', '哥': 'G', '国': 'G',
'哈': 'H', '惠': 'H', '黑': 'H', '红': 'H', '华': 'H', '虎': 'H',
'吉': 'J', '金': 'J', '加': 'J', '杰': 'J', '京': 'J', '基': 'J',
'可': 'K', '卡': 'K', '克': 'K', '科': 'K',
'拉': 'L', '罗': 'L', '兰': 'L', '莱': 'L', '利': 'L', '路': 'L',
'马': 'M', '美': 'M', '牧': 'M', '摩': 'M', '曼': 'M', '米': 'M', '缅': 'M',
'牛': 'N', '纽': 'N', '南': 'N', '尼': 'N', '纳': 'N',
'平': 'P', '帕': 'P', '普': 'P', '皮': 'P',
'秋': 'Q', '奇': 'Q', '丘': 'Q',
'日': 'R', '瑞': 'R', '若': 'R', '热': 'R',
'松': 'S', '萨': 'S', '圣': 'S', '苏': 'S', '斯': 'S', '山': 'S',
'泰': 'T', '土': 'T', '特': 'T', '托': 'T',
'威': 'W', '魏': 'W', '温': 'W', '沃': 'W',
'西': 'X', '喜': 'X', '雪': 'X', '新': 'X',
'约': 'Y', '英': 'Y', '意': 'Y', '伊': 'Y',
'中': 'Z', '藏': 'Z', '芝': 'Z', '泽': 'Z'
}
return pinyinMap[char] || 'A'
},
// 搜索处理
onSearchChange(value) {
this.searchValue = value || ''
if (!this.breedData || !Array.isArray(this.breedData)) {
console.warn('breedData不是有效数组')
this.filteredBreeds = []
this.groupBreedsByLetter()
return
}
if (value && value.trim()) {
this.filteredBreeds = this.breedData.filter(breed =>
breed && typeof breed === 'string' && breed.toLowerCase().includes(value.toLowerCase())
)
} else {
this.filteredBreeds = [...this.breedData]
}
this.groupBreedsByLetter()
},
// 选择品种
selectBreed(breed) {
try {
console.log('选择品种:', breed)
// 使用全局事件总线传递数据,这是最可靠的方式
uni.$emit('breedSelected', {
breed: breed,
petType: this.petType
})
console.log('触发全局事件 breedSelected:', breed)
// 返回上一页
uni.navigateBack()
} catch (error) {
console.error('选择品种时出错:', error)
uni.showToast({
title: '选择失败,请重试',
icon: 'none'
})
// 即使出错也要返回上一页
uni.navigateBack()
}
},
// 滚动到指定字母
scrollToLetter(letter) {
this.currentLetter = letter
this.scrollIntoView = `section-${letter}`
},
// 滚动事件处理
onScroll(e) {
// 暂时禁用滚动检测,避免错误
// const scrollTop = e.detail.scrollTop
// 使用节流来优化性能
// if (this.scrollTimer) {
// clearTimeout(this.scrollTimer)
// }
// this.scrollTimer = setTimeout(() => {
// this.updateCurrentLetter(scrollTop)
// }, 100)
},
// 更新当前字母
updateCurrentLetter(scrollTop) {
// 获取所有字母区域的位置信息
const query = uni.createSelectorQuery().in(this)
query.selectAll('.breed-section').boundingClientRect()
query.exec((res) => {
if (res && res[0] && Array.isArray(res[0])) {
const sections = res[0]
let currentLetter = 'A'
// 找到当前滚动位置对应的字母
for (let i = 0; i < sections.length; i++) {
const section = sections[i]
if (section && typeof section.top === 'number' && typeof section.height === 'number') {
const sectionTop = section.top
const sectionHeight = section.height
// 只有当字母标题被粘性定位悬挂时才激活
// 检查字母标题是否在顶部位置(粘性定位生效)
// 考虑搜索栏的高度,粘性定位是相对于搜索栏的
const searchBarHeight = 120 // 搜索栏高度约120rpx
const stickyTop = searchBarHeight
// sectionTop <= stickyTop 表示字母标题已经到达粘性定位位置
// sectionTop > stickyTop - sectionHeight 表示字母区域还没有完全滚动过去
if (sectionTop <= stickyTop && sectionTop > stickyTop - sectionHeight) {
currentLetter = section.id.replace('section-', '')
break
}
}
}
if (this.currentLetter !== currentLetter) {
this.currentLetter = currentLetter
console.log('当前激活字母:', currentLetter, '滚动位置:', scrollTop)
}
}
})
},
}
}
</script>
<style lang="scss" scoped>
.breed-select-container {
height: 100vh;
background-color: #f5f5f5;
position: relative;
}
.search-container {
background-color: #fff;
padding: 20rpx 30rpx;
position: relative;
z-index: 99;
}
.breed-list-container {
flex: 1;
position: relative;
height: calc(100vh - 120rpx);
background-color: #fff;
}
.breed-list {
height: 100%;
background-color: #fff;
padding: 0 30rpx;
}
.breed-section {
.section-header {
font-size: 36rpx;
font-weight: bold;
color: #333;
padding: 20rpx 0 10rpx 0;
background-color: #fff;
position: sticky;
top: 0;
z-index: 10;
}
.breed-item {
padding: 20rpx 0;
cursor: pointer;
transition: background-color 0.2s;
.breed-name {
font-size: 28rpx;
color: #333;
line-height: 1.5;
font-weight: 400;
}
.breed-divider {
height: 1rpx;
background-color: #f0f0f0;
margin-top: 20rpx;
}
}
}
.letter-index {
position: fixed;
right: 10rpx;
top: 50%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
align-items: center;
z-index: 1000;
.letter-item {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
color: #666;
margin: 2rpx 0;
transition: all 0.3s;
font-weight: 400;
border-radius: 50%;
&.active {
color: #fff;
font-weight: bold;
background-color: #FFBF60;
}
&.has-content {
color: #333;
}
}
}
</style>