liveh5-nuxt/app/pages/countryRegion/index.vue

225 lines
6.5 KiB
Vue
Raw Normal View History

<script setup>
import {ref, computed, watch} from 'vue';
import pinyin from 'pinyin';
import countryCode from './data/index.js';
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
definePageMeta({
i18n: 'countryRegion.title',
})
const router = useRouter()
const { t, locale } = useI18n()
const value = ref('');
2025-02-19 03:49:36 +00:00
const alphabet = computed(() => {
if (!groupedCountries.value) return ['#'];
// 获取所有实际存在的分组字母
const letters = Object.keys(groupedCountries.value)
.filter(key => key !== '#')
.sort();
// 确保 # 永远在最前
return ['#', ...letters];
});
// 常用国家的代码列表
const frequentCountryCodes = ['CN', 'TW', 'JP', 'US'];
function groupByPinyinInitial(data) {
const grouped = {};
// 先处理常用国家
grouped['#'] = [];
data.forEach(country => {
if (frequentCountryCodes.includes(country.code)) {
const countryName = locale.value === 'zh-CN' ? country.cn :
locale.value === 'zh-TW' ? country.tw :
locale.value === 'ja-JP' ? country.ja :
country.en;
grouped['#'].push({
...country,
displayName: countryName
});
}
});
// 处理其他国家
data.forEach(country => {
if (!frequentCountryCodes.includes(country.code)) {
const countryName = locale.value === 'zh-CN' ? country.cn :
locale.value === 'zh-TW' ? country.tw :
locale.value === 'ja-JP' ? country.ja :
country.en;
2025-02-19 03:49:36 +00:00
// 根据语言环境决定使用拼音还是英文首字母
let initial;
if (locale.value === 'zh-CN' || locale.value === 'zh-TW') {
// 中文和繁体使用拼音首字母
const pinyinName = locale.value === 'zh-CN' ? country.cn : country.tw;
initial = pinyin(pinyinName, {style: pinyin.STYLE_FIRST_LETTER})[0][0].toUpperCase();
} else if (locale.value === 'ja-JP') {
initial = '';
} else {
// 英文使用 en 字段首字母
initial = country.en.charAt(0).toUpperCase();
}
if (!grouped[initial]) {
grouped[initial] = [];
}
grouped[initial].push({
...country,
displayName: countryName
});
}
});
2025-02-19 03:49:36 +00:00
// 对每个分组内的国家按照对应语言排序
Object.keys(grouped).forEach(key => {
grouped[key].sort((a, b) => {
if (locale.value === 'zh-CN' || locale.value === 'zh-TW') {
// 中文和繁体使用拼音排序
const pinyinA = pinyin(locale.value === 'zh-CN' ? a.cn : a.tw, {style: pinyin.STYLE_NORMAL}).join('');
const pinyinB = pinyin(locale.value === 'zh-CN' ? b.cn : b.tw, {style: pinyin.STYLE_NORMAL}).join('');
return pinyinA.localeCompare(pinyinB);
} else if (locale.value === 'ja-JP') {
return a.displayName.localeCompare(b.displayName, 'ja-JP');
} else {
return a.en.localeCompare(b.en);
}
});
});
if (locale.value === 'ja-JP') {
2025-02-19 03:49:36 +00:00
return grouped;
} else {
// 按字母顺序返回排序后的对象
const sortedGrouped = {};
// 确保 # 永远在最前
sortedGrouped['#'] = grouped['#'];
// 其他字母按顺序排序
Object.keys(grouped)
.filter(key => key !== '#')
.sort()
.forEach(key => {
sortedGrouped[key] = grouped[key];
});
return sortedGrouped;
}
}
2025-01-10 05:23:11 +00:00
const groupedCountries = ref([])
const initData = () => {
2025-01-10 05:23:11 +00:00
groupedCountries.value = groupByPinyinInitial(countryCode);
}
2025-01-10 05:23:11 +00:00
const searchCountry = computed(() => {
if (!value.value) {
return groupedCountries.value;
}
return Object.keys(groupedCountries.value).reduce((filtered, initial) => {
const countries = groupedCountries.value[initial].filter(country =>
country.displayName.toLowerCase().includes(value.value.toLowerCase())
2025-01-10 05:23:11 +00:00
);
2025-02-19 03:49:36 +00:00
2025-01-10 05:23:11 +00:00
if (countries.length > 0) {
filtered[initial] = countries;
}
return filtered;
}, {});
});
const showIndexBar = computed(() => locale.value !== 'ja-JP')
const route = useRoute()
const handleCountrySelect = (country) => {
router.replace({
path: window.history.state.back,
query: {
zone: country.zone,
countryName: country.displayName
}
})
}
2025-01-10 05:23:11 +00:00
initData()
// 监听语言变化,重新初始化数据
watch(locale, () => {
initData()
})
</script>
<template>
<div>
<van-sticky>
<van-search v-model="value" :placeholder="t('countryRegion.searchPlaceholder')"/>
</van-sticky>
<van-index-bar
v-if="showIndexBar"
sticky
:sticky-offset-top="55"
:index-list="alphabet"
>
<!-- 常用国家分类 -->
<van-index-anchor index="#">{{ t('countryRegion.frequentCountry') }}</van-index-anchor>
<van-cell
v-for="country in searchCountry['#']"
:key="country.code"
:title="country.displayName"
@click="handleCountrySelect(country)"
clickable
>
<div class="pr-[25px]"> +{{ country.zone }}</div>
</van-cell>
<!-- 其他国家按字母分类 -->
2025-01-10 05:23:11 +00:00
<template v-for="(countries, index) in searchCountry" :key="index">
<template v-if="index !== '#'">
<van-index-anchor
:index="index"
></van-index-anchor>
<van-cell
v-for="country in countries"
:key="country.code"
:title="country.displayName"
@click="handleCountrySelect(country)"
clickable
>
<div class="pr-[25px]"> +{{ country.zone }}</div>
</van-cell>
</template>
2025-01-10 05:23:11 +00:00
</template>
</van-index-bar>
2025-01-10 05:23:11 +00:00
<div v-else>
<div class="mb-4">
<div class="px-4 py-2 text-gray-600">{{ t('countryRegion.frequentCountry') }}</div>
<van-cell
v-for="country in searchCountry['#']"
:key="country.code"
:title="country.displayName"
@click="handleCountrySelect(country)"
clickable
>
<div class="pr-[25px]"> +{{ country.zone }}</div>
</van-cell>
</div>
<van-cell
v-for="country in Object.values(searchCountry).flat().filter(c => !frequentCountryCodes.includes(c.code))"
:key="country.code"
:title="country.displayName"
@click="handleCountrySelect(country)"
clickable
>
<div class="pr-[25px]"> +{{ country.zone }}</div>
</van-cell>
</div>
2025-01-10 05:23:11 +00:00
<van-back-top v-if="showIndexBar" right="15vw" bottom="10vh"/>
</div>
</template>
<style scoped>
2025-01-10 05:23:11 +00:00
</style>