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