<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>
|