feat: 优化字母索引列表 1.优化UI样式 2.添加滚动联动效果 3.添加指示器动画 4.添加拼音转换工具

This commit is contained in:
davy 2025-04-08 01:07:18 +08:00
parent f5132c277a
commit ea9c7580c1
2 changed files with 351 additions and 172 deletions

View File

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