feat: 优化字母索引列表 1.优化UI样式 2.添加滚动联动效果 3.添加指示器动画 4.添加拼音转换工具
This commit is contained in:
parent
f5132c277a
commit
ea9c7580c1
@ -1,19 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page">
|
<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 v-if="loading" class="loading-state">
|
||||||
<view class="loading-skeleton" v-for="i in 5" :key="i">
|
<view class="loading-skeleton" v-for="i in 5" :key="i">
|
||||||
@ -31,9 +17,11 @@
|
|||||||
scroll-y
|
scroll-y
|
||||||
class="content"
|
class="content"
|
||||||
:scroll-into-view="currentLetter ? 'letter-' + currentLetter : ''"
|
:scroll-into-view="currentLetter ? 'letter-' + currentLetter : ''"
|
||||||
|
@scroll="onScroll"
|
||||||
|
:scroll-with-animation="true"
|
||||||
>
|
>
|
||||||
<block v-for="(group, letter) in groupedBreweries" :key="letter">
|
<block v-for="(group, letter) in groupedBreweries" :key="letter">
|
||||||
<view :id="'letter-' + letter" class="letter-section">
|
<view :id="'letter-' + letter" class="letter-section" :data-letter="letter">
|
||||||
<view class="letter-title">{{letter}}</view>
|
<view class="letter-title">{{letter}}</view>
|
||||||
<view
|
<view
|
||||||
class="brewery-item hover-effect"
|
class="brewery-item hover-effect"
|
||||||
@ -42,13 +30,13 @@
|
|||||||
@click="navigateToBrewery(brewery)"
|
@click="navigateToBrewery(brewery)"
|
||||||
>
|
>
|
||||||
<image
|
<image
|
||||||
:src="brewery.logo || '/static/images/default-logo.png'"
|
:src="brewery.brandLogo || '/static/images/default-logo.png'"
|
||||||
class="brewery-logo"
|
class="brewery-logo"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
:lazy-load="true"
|
:lazy-load="true"
|
||||||
/>
|
/>
|
||||||
<view class="brewery-info">
|
<view class="brewery-info">
|
||||||
<text class="brewery-name text-ellipsis">{{brewery.breweryName}}</text>
|
<text class="brewery-name text-ellipsis">{{brewery.brandName}}</text>
|
||||||
<text class="beer-count">{{brewery.beerCount || 0}}款在售</text>
|
<text class="beer-count">{{brewery.beerCount || 0}}款在售</text>
|
||||||
</view>
|
</view>
|
||||||
<image
|
<image
|
||||||
@ -68,6 +56,7 @@
|
|||||||
|
|
||||||
<!-- 右侧字母导航 -->
|
<!-- 右侧字母导航 -->
|
||||||
<view class="letter-nav">
|
<view class="letter-nav">
|
||||||
|
<view class="indicator" :style="indicatorStyle"></view>
|
||||||
<view
|
<view
|
||||||
v-for="letter in letters"
|
v-for="letter in letters"
|
||||||
:key="letter"
|
:key="letter"
|
||||||
@ -84,15 +73,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getBreweries, getBeerList } from '@/api/bar.js'
|
import { getBreweries, getBeerList } from '@/api/bar.js'
|
||||||
|
import Pinyin from '@/utils/js-pinyin.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HotLabel',
|
name: 'HotLabel',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// 搜索相关
|
|
||||||
searchKey: '',
|
|
||||||
searchTimer: null,
|
|
||||||
|
|
||||||
// 数据相关
|
// 数据相关
|
||||||
breweries: [],
|
breweries: [],
|
||||||
groupedBreweries: {},
|
groupedBreweries: {},
|
||||||
@ -100,12 +86,33 @@
|
|||||||
letters: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
|
letters: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
|
||||||
|
|
||||||
// 加载状态
|
// 加载状态
|
||||||
loading: false
|
loading: false,
|
||||||
|
scrollTimer: null,
|
||||||
|
indicatorTop: 0,
|
||||||
|
observer: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
indicatorStyle() {
|
||||||
|
return {
|
||||||
|
transform: `translateY(${this.indicatorTop}px)`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
Pinyin.setOptions({ checkPolyphone: false, charCase: 1 })
|
||||||
|
},
|
||||||
onShow() {
|
onShow() {
|
||||||
this.initData()
|
this.initData()
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.setupIntersectionObserver()
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.observer) {
|
||||||
|
this.observer.disconnect()
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 初始化数据
|
// 初始化数据
|
||||||
async initData() {
|
async initData() {
|
||||||
@ -113,15 +120,13 @@
|
|||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: '加载中...'
|
title: '加载中...'
|
||||||
})
|
})
|
||||||
const res = await getBreweries()
|
|
||||||
console.log('获取品牌列表响应:', res)
|
|
||||||
|
|
||||||
if (res && res.data) {
|
const res = await getBreweries()
|
||||||
this.breweries = res.data.map(item => ({
|
if (res && res.code === 200 && res.rows) {
|
||||||
id: item.id,
|
// 直接使用后端返回的原始字段
|
||||||
breweryName: item.breweryName || '',
|
this.breweries = res.rows.map(item => ({
|
||||||
logo: item.logo || '',
|
...item, // 保留所有原始字段
|
||||||
beerCount: 0
|
beerCount: 0, // 仅添加前端所需的计数字段
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// 获取每个品牌的酒款数量
|
// 获取每个品牌的酒款数量
|
||||||
@ -129,12 +134,12 @@
|
|||||||
// 按首字母分组
|
// 按首字母分组
|
||||||
this.groupBreweries()
|
this.groupBreweries()
|
||||||
} else {
|
} else {
|
||||||
throw new Error('获取品牌列表失败')
|
throw new Error(res?.msg || '获取品牌列表失败')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('初始化数据失败:', error)
|
console.error('初始化数据失败:', error)
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '加载失败,请重试',
|
title: error.message || '加载失败,请重试',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
@ -164,9 +169,12 @@
|
|||||||
const grouped = {}
|
const grouped = {}
|
||||||
|
|
||||||
this.breweries.forEach(brewery => {
|
this.breweries.forEach(brewery => {
|
||||||
if (!brewery.breweryName) return
|
if (!brewery.brandName) return
|
||||||
|
|
||||||
let firstLetter = brewery.breweryName.charAt(0).toUpperCase()
|
let firstLetter = brewery.brandName.charAt(0).toUpperCase()
|
||||||
|
if (/[\u4e00-\u9fa5]/.test(firstLetter)) {
|
||||||
|
firstLetter = this.getFirstPinYinLetter(brewery.brandName)
|
||||||
|
}
|
||||||
if (!/[A-Z]/.test(firstLetter)) {
|
if (!/[A-Z]/.test(firstLetter)) {
|
||||||
firstLetter = '#'
|
firstLetter = '#'
|
||||||
}
|
}
|
||||||
@ -179,7 +187,7 @@
|
|||||||
|
|
||||||
Object.keys(grouped).forEach(letter => {
|
Object.keys(grouped).forEach(letter => {
|
||||||
grouped[letter].sort((a, b) =>
|
grouped[letter].sort((a, b) =>
|
||||||
a.breweryName.localeCompare(b.breweryName, 'zh')
|
a.brandName.localeCompare(b.brandName, 'zh')
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -192,46 +200,89 @@
|
|||||||
this.groupedBreweries = grouped
|
this.groupedBreweries = grouped
|
||||||
},
|
},
|
||||||
|
|
||||||
// 搜索处理
|
// 设置交叉观察器
|
||||||
handleSearch() {
|
setupIntersectionObserver() {
|
||||||
if (this.searchTimer) {
|
if (typeof uni.createIntersectionObserver !== 'function') return
|
||||||
clearTimeout(this.searchTimer)
|
|
||||||
|
this.observer = uni.createIntersectionObserver(this)
|
||||||
|
this.observer
|
||||||
|
.relativeTo('.content')
|
||||||
|
.observe('.letter-section', (res) => {
|
||||||
|
if (res.intersectionRatio > 0) {
|
||||||
|
const letter = res.dataset.letter
|
||||||
|
this.updateCurrentLetter(letter, false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 滚动事件处理
|
||||||
|
onScroll(e) {
|
||||||
|
if (this.scrollTimer) {
|
||||||
|
clearTimeout(this.scrollTimer)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.searchTimer = setTimeout(() => {
|
this.scrollTimer = setTimeout(() => {
|
||||||
const searchKey = this.searchKey.trim().toLowerCase()
|
const query = uni.createSelectorQuery().in(this)
|
||||||
|
query.selectAll('.letter-section').boundingClientRect()
|
||||||
|
query.select('.content').boundingClientRect()
|
||||||
|
query.exec(([sections, content]) => {
|
||||||
|
if (!sections || !content) return
|
||||||
|
|
||||||
if (!searchKey) {
|
const contentTop = content.top
|
||||||
this.groupBreweries()
|
let currentSection = null
|
||||||
return
|
let minDistance = Infinity
|
||||||
}
|
|
||||||
|
|
||||||
const filteredBreweries = this.breweries.filter(brewery =>
|
// 找到距离顶部最近的section
|
||||||
brewery.breweryName.toLowerCase().includes(searchKey)
|
sections.forEach(section => {
|
||||||
)
|
const distance = Math.abs(section.top - contentTop)
|
||||||
|
if (distance < minDistance) {
|
||||||
const grouped = {}
|
minDistance = distance
|
||||||
filteredBreweries.forEach(brewery => {
|
currentSection = section
|
||||||
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
|
if (currentSection) {
|
||||||
}, 300)
|
const letter = currentSection.dataset.letter
|
||||||
|
this.updateCurrentLetter(letter, false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, 50) // 降低延迟以提高响应速度
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新当前字母
|
||||||
|
updateCurrentLetter(letter, shouldScroll = true) {
|
||||||
|
if (this.currentLetter === letter) return
|
||||||
|
|
||||||
|
this.currentLetter = letter
|
||||||
|
|
||||||
|
// 更新指示器位置
|
||||||
|
const query = uni.createSelectorQuery().in(this)
|
||||||
|
query.selectAll('.letter-item').boundingClientRect()
|
||||||
|
query.select('.letter-nav').boundingClientRect()
|
||||||
|
query.exec(([items, nav]) => {
|
||||||
|
if (!items || !nav) return
|
||||||
|
|
||||||
|
const index = this.letters.indexOf(letter)
|
||||||
|
if (index > -1) {
|
||||||
|
const item = items[index]
|
||||||
|
// 调整指示器位置,确保准确对齐
|
||||||
|
this.indicatorTop = item.top - nav.top + (item.height - 48) / 2 // 48是指示器的高度
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 是否需要滚动到对应位置
|
||||||
|
if (shouldScroll) {
|
||||||
|
uni.pageScrollTo({
|
||||||
|
selector: `#letter-${letter}`,
|
||||||
|
duration: 300
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 滚动到指定字母
|
// 滚动到指定字母
|
||||||
scrollToLetter(letter) {
|
scrollToLetter(letter) {
|
||||||
this.currentLetter = letter
|
this.updateCurrentLetter(letter)
|
||||||
|
// 已有的滚动逻辑保持不变
|
||||||
},
|
},
|
||||||
|
|
||||||
// 导航到品牌详情
|
// 导航到品牌详情
|
||||||
@ -243,15 +294,13 @@
|
|||||||
|
|
||||||
// 获取中文首字母
|
// 获取中文首字母
|
||||||
getFirstPinYinLetter(str) {
|
getFirstPinYinLetter(str) {
|
||||||
const pinyin = require('pinyin')
|
|
||||||
if (!str) return '#'
|
if (!str) return '#'
|
||||||
const firstChar = str.charAt(0)
|
const firstChar = str.charAt(0)
|
||||||
if (!/[\u4e00-\u9fa5]/.test(firstChar)) return firstChar.toUpperCase()
|
if (!/[\u4e00-\u9fa5]/.test(firstChar)) return firstChar.toUpperCase()
|
||||||
const pinyinArr = pinyin(firstChar, {
|
|
||||||
style: pinyin.STYLE_FIRST_LETTER,
|
// 使用js-pinyin获取拼音首字母
|
||||||
heteronym: false
|
const pinyin = Pinyin.getFullChars(firstChar)
|
||||||
})
|
return pinyin ? pinyin.charAt(0).toUpperCase() : '#'
|
||||||
return (pinyinArr[0] || ['#'])[0].toUpperCase()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -260,47 +309,7 @@
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.page {
|
.page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #FFFFFF;
|
background: #F8F9FC;
|
||||||
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 {
|
.loading-state {
|
||||||
@ -309,17 +318,18 @@
|
|||||||
.loading-skeleton {
|
.loading-skeleton {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 24rpx;
|
padding: 32rpx;
|
||||||
margin-bottom: 24rpx;
|
margin-bottom: 24rpx;
|
||||||
background: #FFFFFF;
|
background: #FFFFFF;
|
||||||
border-radius: 12rpx;
|
border-radius: 16rpx;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
|
||||||
|
|
||||||
.skeleton-logo {
|
.skeleton-logo {
|
||||||
width: 80rpx;
|
width: 88rpx;
|
||||||
height: 80rpx;
|
height: 88rpx;
|
||||||
background: #F5F5F5;
|
background: linear-gradient(90deg, #F5F5F5, #FAFAFA, #F5F5F5);
|
||||||
border-radius: 12rpx;
|
border-radius: 16rpx;
|
||||||
margin-right: 24rpx;
|
margin-right: 32rpx;
|
||||||
animation: skeleton-loading 1.5s infinite;
|
animation: skeleton-loading 1.5s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,18 +338,18 @@
|
|||||||
|
|
||||||
.skeleton-title {
|
.skeleton-title {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
height: 32rpx;
|
height: 36rpx;
|
||||||
background: #F5F5F5;
|
background: linear-gradient(90deg, #F5F5F5, #FAFAFA, #F5F5F5);
|
||||||
border-radius: 4rpx;
|
border-radius: 8rpx;
|
||||||
margin-bottom: 12rpx;
|
margin-bottom: 16rpx;
|
||||||
animation: skeleton-loading 1.5s infinite;
|
animation: skeleton-loading 1.5s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton-subtitle {
|
.skeleton-subtitle {
|
||||||
width: 40%;
|
width: 40%;
|
||||||
height: 24rpx;
|
height: 28rpx;
|
||||||
background: #F5F5F5;
|
background: linear-gradient(90deg, #F5F5F5, #FAFAFA, #F5F5F5);
|
||||||
border-radius: 4rpx;
|
border-radius: 6rpx;
|
||||||
animation: skeleton-loading 1.5s infinite;
|
animation: skeleton-loading 1.5s infinite;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -349,7 +359,8 @@
|
|||||||
// 字母索引列表
|
// 字母索引列表
|
||||||
.index-list {
|
.index-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: calc(100vh - 120rpx);
|
height: 100vh;
|
||||||
|
background: #FFFFFF;
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@ -357,54 +368,93 @@
|
|||||||
|
|
||||||
.letter-section {
|
.letter-section {
|
||||||
.letter-title {
|
.letter-title {
|
||||||
padding: 16rpx 24rpx;
|
padding: 20rpx 32rpx;
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #999999;
|
color: #666666;
|
||||||
background: #F9F9F9;
|
background: #F8F9FC;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brewery-item {
|
.brewery-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 24rpx;
|
padding: 32rpx;
|
||||||
margin: 0 24rpx;
|
margin: 0 32rpx;
|
||||||
border-bottom: 2rpx solid #F5F5F5;
|
border-bottom: 2rpx solid #F5F7FA;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background: #F9F9F9;
|
background: #F8F9FC;
|
||||||
transform: scale(0.98);
|
transform: scale(0.99);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 32rpx;
|
||||||
|
right: 32rpx;
|
||||||
|
bottom: 0;
|
||||||
|
height: 2rpx;
|
||||||
|
background: #F5F7FA;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child::after {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brewery-logo {
|
.brewery-logo {
|
||||||
width: 80rpx;
|
width: 88rpx;
|
||||||
height: 80rpx;
|
height: 88rpx;
|
||||||
border-radius: 12rpx;
|
border-radius: 16rpx;
|
||||||
margin-right: 24rpx;
|
margin-right: 32rpx;
|
||||||
background: #F5F5F5;
|
background: #F8F9FC;
|
||||||
box-shadow: 0 4rpx 8rpx rgba(0,0,0,0.05);
|
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.06);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.brewery-info {
|
.brewery-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
.brewery-name {
|
.brewery-name {
|
||||||
font-size: 28rpx;
|
font-size: 30rpx;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
margin-bottom: 8rpx;
|
margin-bottom: 12rpx;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.beer-count {
|
.beer-count {
|
||||||
font-size: 24rpx;
|
font-size: 26rpx;
|
||||||
color: #999999;
|
color: #666666;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 12rpx;
|
||||||
|
height: 12rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #19C375;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow-icon {
|
.arrow-icon {
|
||||||
width: 32rpx;
|
width: 36rpx;
|
||||||
height: 32rpx;
|
height: 36rpx;
|
||||||
opacity: 0.3;
|
opacity: 0.4;
|
||||||
|
margin-left: 24rpx;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active .arrow-icon {
|
||||||
|
transform: translateX(4rpx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -413,17 +463,19 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 96rpx 0;
|
padding: 120rpx 0;
|
||||||
|
|
||||||
.empty-image {
|
.empty-image {
|
||||||
width: 240rpx;
|
width: 280rpx;
|
||||||
height: 240rpx;
|
height: 280rpx;
|
||||||
margin-bottom: 32rpx;
|
margin-bottom: 40rpx;
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-text {
|
.empty-text {
|
||||||
font-size: 28rpx;
|
font-size: 30rpx;
|
||||||
color: #999999;
|
color: #666666;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -433,12 +485,27 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 24rpx 12rpx;
|
padding: 24rpx 16rpx;
|
||||||
background: #FFFFFF;
|
background: #FFFFFF;
|
||||||
|
box-shadow: -4rpx 0 16rpx rgba(0,0,0,0.03);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.indicator {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(25, 54, 122, 0.1);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.letter-item {
|
.letter-item {
|
||||||
width: 32rpx;
|
width: 40rpx;
|
||||||
height: 32rpx;
|
height: 40rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -446,11 +513,17 @@
|
|||||||
color: #666666;
|
color: #666666;
|
||||||
margin: 4rpx 0;
|
margin: 4rpx 0;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
font-weight: 500;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
background: #D42E78;
|
background: #19367A;
|
||||||
|
font-weight: 600;
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 4rpx 8rpx rgba(25, 54, 122, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
@ -463,23 +536,20 @@
|
|||||||
|
|
||||||
@keyframes skeleton-loading {
|
@keyframes skeleton-loading {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0.6;
|
background-position: -200% 0;
|
||||||
}
|
|
||||||
50% {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
opacity: 0.6;
|
background-position: 200% 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加按键反馈效果
|
// 添加按键反馈效果
|
||||||
.hover-effect {
|
.hover-effect {
|
||||||
transition: all 0.2s ease;
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.96);
|
transform: scale(0.98);
|
||||||
opacity: 0.8;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
109
utils/js-pinyin.js
Normal file
109
utils/js-pinyin.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/**
|
||||||
|
* js-pinyin
|
||||||
|
* 一个轻量级的汉字转拼音工具
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Pinyin = {
|
||||||
|
/**
|
||||||
|
* 拼音首字母字典
|
||||||
|
*/
|
||||||
|
_pyFirstLetters: {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拼音字典
|
||||||
|
*/
|
||||||
|
_pyDict: {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置选项
|
||||||
|
*/
|
||||||
|
_options: {
|
||||||
|
checkPolyphone: false, // 是否检查多音字
|
||||||
|
charCase: 0 // 0-小写, 1-大写
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置配置选项
|
||||||
|
* @param {Object} options 配置选项
|
||||||
|
*/
|
||||||
|
setOptions(options) {
|
||||||
|
Object.assign(this._options, options)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取汉字的拼音首字母
|
||||||
|
* @param {String} str 需要转换的汉字
|
||||||
|
* @returns {String} 拼音首字母
|
||||||
|
*/
|
||||||
|
getFirstLetter(str) {
|
||||||
|
if (typeof str !== 'string') return str
|
||||||
|
const result = this.getFullChars(str)
|
||||||
|
return result.charAt(0)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取汉字的完整拼音
|
||||||
|
* @param {String} str 需要转换的汉字
|
||||||
|
* @returns {String} 完整拼音
|
||||||
|
*/
|
||||||
|
getFullChars(str) {
|
||||||
|
if (typeof str !== 'string') return str
|
||||||
|
let result = ''
|
||||||
|
for (let i = 0, len = str.length; i < len; i++) {
|
||||||
|
const ch = str.charAt(i)
|
||||||
|
if (ch.charCodeAt(0) > 127) {
|
||||||
|
// 汉字处理
|
||||||
|
result += this._getChar(ch)
|
||||||
|
} else {
|
||||||
|
// 非汉字处理
|
||||||
|
result += this._options.charCase ? ch.toUpperCase() : ch.toLowerCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个汉字的拼音
|
||||||
|
* @private
|
||||||
|
* @param {String} ch 单个汉字
|
||||||
|
* @returns {String} 拼音
|
||||||
|
*/
|
||||||
|
_getChar(ch) {
|
||||||
|
const hash = ch.charCodeAt(0)
|
||||||
|
// 处理空格
|
||||||
|
if (hash === 32) return ' '
|
||||||
|
// 处理特殊字符
|
||||||
|
if (hash < 127) return ch
|
||||||
|
|
||||||
|
// 使用常用汉字拼音映射表
|
||||||
|
const pinyinMap = {
|
||||||
|
'阿': 'a', '啊': 'a', '埃': 'ai', '艾': 'ai', '爱': 'ai', '安': 'an', '奥': 'ao', '澳': 'ao',
|
||||||
|
'巴': 'ba', '白': 'bai', '百': 'bai', '班': 'ban', '邦': 'bang', '包': 'bao', '北': 'bei', '本': 'ben', '必': 'bi', '冰': 'bing', '博': 'bo', '不': 'bu',
|
||||||
|
'草': 'cao', '册': 'ce', '测': 'ce', '层': 'ceng', '茶': 'cha', '查': 'cha', '长': 'chang', '成': 'cheng', '城': 'cheng', '池': 'chi', '出': 'chu', '川': 'chuan',
|
||||||
|
'大': 'da', '达': 'da', '答': 'da', '代': 'dai', '带': 'dai', '单': 'dan', '但': 'dan', '东': 'dong', '都': 'du', '度': 'du', '端': 'duan', '对': 'dui',
|
||||||
|
'饿': 'e', '恩': 'en', '而': 'er', '尔': 'er', '耳': 'er',
|
||||||
|
'发': 'fa', '法': 'fa', '番': 'fan', '方': 'fang', '风': 'feng', '封': 'feng', '福': 'fu', '府': 'fu', '富': 'fu', '复': 'fu', '父': 'fu', '付': 'fu',
|
||||||
|
'改': 'gai', '干': 'gan', '甘': 'gan', '刚': 'gang', '高': 'gao', '格': 'ge', '工': 'gong', '古': 'gu', '谷': 'gu', '光': 'guang', '广': 'guang', '贵': 'gui',
|
||||||
|
'哈': 'ha', '海': 'hai', '含': 'han', '汉': 'han', '好': 'hao', '号': 'hao', '河': 'he', '黑': 'hei', '恒': 'heng', '红': 'hong', '后': 'hou', '湖': 'hu', '华': 'hua', '环': 'huan',
|
||||||
|
'击': 'ji', '及': 'ji', '极': 'ji', '急': 'ji', '集': 'ji', '几': 'ji', '己': 'ji', '家': 'jia', '建': 'jian', '江': 'jiang', '交': 'jiao', '金': 'jin', '京': 'jing', '九': 'jiu', '居': 'ju',
|
||||||
|
'卡': 'ka', '开': 'kai', '看': 'kan', '康': 'kang', '科': 'ke', '可': 'ke', '空': 'kong', '口': 'kou', '快': 'kuai', '宽': 'kuan',
|
||||||
|
'拉': 'la', '来': 'lai', '蓝': 'lan', '老': 'lao', '乐': 'le', '雷': 'lei', '冷': 'leng', '里': 'li', '立': 'li', '联': 'lian', '良': 'liang', '龙': 'long', '路': 'lu', '露': 'lu',
|
||||||
|
'妈': 'ma', '马': 'ma', '买': 'mai', '卖': 'mai', '满': 'man', '猫': 'mao', '么': 'me', '美': 'mei', '梦': 'meng', '米': 'mi', '面': 'mian', '民': 'min', '明': 'ming',
|
||||||
|
'拿': 'na', '那': 'na', '奶': 'nai', '南': 'nan', '能': 'neng', '你': 'ni', '年': 'nian', '宁': 'ning', '农': 'nong', '女': 'nv',
|
||||||
|
'哦': 'o', '藕': 'ou',
|
||||||
|
'爬': 'pa', '拍': 'pai', '盘': 'pan', '乓': 'pang', '跑': 'pao', '配': 'pei', '朋': 'peng', '品': 'pin', '平': 'ping', '普': 'pu',
|
||||||
|
'七': 'qi', '起': 'qi', '气': 'qi', '千': 'qian', '前': 'qian', '桥': 'qiao', '亲': 'qin', '青': 'qing', '轻': 'qing', '清': 'qing', '情': 'qing', '庆': 'qing', '秋': 'qiu', '区': 'qu', '全': 'quan',
|
||||||
|
'然': 'ran', '让': 'rang', '热': 're', '人': 'ren', '日': 'ri', '容': 'rong', '如': 'ru', '瑞': 'rui', '润': 'run',
|
||||||
|
'撒': 'sa', '赛': 'sai', '三': 'san', '色': 'se', '森': 'sen', '杀': 'sha', '山': 'shan', '上': 'shang', '尚': 'shang', '少': 'shao', '深': 'shen', '生': 'sheng', '时': 'shi', '世': 'shi', '市': 'shi', '事': 'shi', '是': 'shi', '首': 'shou', '水': 'shui', '顺': 'shun',
|
||||||
|
'他': 'ta', '她': 'ta', '台': 'tai', '太': 'tai', '谈': 'tan', '汤': 'tang', '套': 'tao', '特': 'te', '天': 'tian', '田': 'tian', '通': 'tong',
|
||||||
|
'哇': 'wa', '外': 'wai', '完': 'wan', '王': 'wang', '为': 'wei', '文': 'wen', '我': 'wo', '屋': 'wu', '五': 'wu', '武': 'wu',
|
||||||
|
'西': 'xi', '息': 'xi', '夏': 'xia', '先': 'xian', '香': 'xiang', '想': 'xiang', '小': 'xiao', '新': 'xin', '信': 'xin', '星': 'xing', '兴': 'xing', '雪': 'xue', '学': 'xue',
|
||||||
|
'亚': 'ya', '烟': 'yan', '燕': 'yan', '羊': 'yang', '样': 'yang', '要': 'yao', '也': 'ye', '一': 'yi', '以': 'yi', '意': 'yi', '益': 'yi', '英': 'ying', '永': 'yong', '优': 'you', '游': 'you', '渝': 'yu', '元': 'yuan',
|
||||||
|
'杂': 'za', '在': 'zai', '咋': 'za', '早': 'zao', '泽': 'ze', '怎': 'zen', '增': 'zeng', '扎': 'zha', '展': 'zhan', '张': 'zhang', '章': 'zhang', '招': 'zhao', '真': 'zhen', '正': 'zheng', '之': 'zhi', '中': 'zhong', '州': 'zhou', '主': 'zhu', '专': 'zhuan', '子': 'zi', '自': 'zi'
|
||||||
|
}
|
||||||
|
|
||||||
|
return pinyinMap[ch] || 'unknown'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Pinyin
|
Loading…
x
Reference in New Issue
Block a user