144 lines
4.3 KiB
Vue
144 lines
4.3 KiB
Vue
|
|
<template>
|
|||
|
|
<div class="language-switcher">
|
|||
|
|
<div class="relative">
|
|||
|
|
<button
|
|||
|
|
@click="toggleDropdown"
|
|||
|
|
:class="[
|
|||
|
|
'flex items-center px-4 py-2 rounded-md transition-colors duration-300 shadow-sm border',
|
|||
|
|
isDark ? 'bg-white/10 text-white hover:bg-white/20 border-white/20' : 'bg-gray-50 text-gray-700 hover:bg-gray-100 border-gray-200'
|
|||
|
|
]"
|
|||
|
|
>
|
|||
|
|
<span class="font-medium">{{ getLanguageName(locale) }}</span>
|
|||
|
|
<svg
|
|||
|
|
xmlns="http://www.w3.org/2000/svg"
|
|||
|
|
class="h-4 w-4 ml-2 transition-transform duration-200"
|
|||
|
|
:class="{ 'rotate-180': isOpen }"
|
|||
|
|
fill="none"
|
|||
|
|
viewBox="0 0 24 24"
|
|||
|
|
stroke="currentColor"
|
|||
|
|
>
|
|||
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
|||
|
|
</svg>
|
|||
|
|
</button>
|
|||
|
|
|
|||
|
|
<div
|
|||
|
|
v-if="isOpen"
|
|||
|
|
class="absolute mt-2 w-40 right-0 bg-white rounded-md shadow-xl py-1 z-10 border border-gray-200 overflow-hidden transition-opacity duration-200 ease-in-out"
|
|||
|
|
>
|
|||
|
|
<button
|
|||
|
|
v-for="loc in availableLocales"
|
|||
|
|
:key="loc"
|
|||
|
|
@click="switchLanguage(loc)"
|
|||
|
|
class="w-full text-left px-4 py-3 text-sm text-gray-700 hover:bg-gray-100 hover:text-primary transition-colors duration-150 flex items-center"
|
|||
|
|
:class="{ 'bg-gray-100 text-primary font-medium': loc === currentLocale }"
|
|||
|
|
:disabled="isChangingLanguage"
|
|||
|
|
>
|
|||
|
|
<span class="w-5 h-5 inline-flex items-center justify-center mr-3">
|
|||
|
|
<span v-if="loc === currentLocale" class="w-2 h-2 rounded-full bg-primary"></span>
|
|||
|
|
</span>
|
|||
|
|
{{ getLanguageName(loc) }}
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<span v-if="showLabel" class="ml-2 text-xs text-gray-400">当前语言: {{ getLanguageName(currentLocale) }}</span>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { useI18n } from 'vue-i18n'
|
|||
|
|
import { computed, ref, onMounted, onUnmounted } from 'vue'
|
|||
|
|
import { useLanguageTransition } from '~/composables/useLanguageTransition'
|
|||
|
|
import { useRoute, useRouter } from 'vue-router'
|
|||
|
|
|
|||
|
|
const props = defineProps({
|
|||
|
|
isDark: {
|
|||
|
|
type: Boolean,
|
|||
|
|
default: true
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const route = useRoute()
|
|||
|
|
const router = useRouter()
|
|||
|
|
const { locale, availableLocales } = useI18n()
|
|||
|
|
const currentLocale = computed(() => locale.value)
|
|||
|
|
const isOpen = ref(false)
|
|||
|
|
const showLabel = ref(false) // 是否显示当前语言的标签,根据需要可以设置为true
|
|||
|
|
|
|||
|
|
// 使用语言过渡效果
|
|||
|
|
const { smoothSwitchLanguage, isChangingLanguage } = useLanguageTransition()
|
|||
|
|
|
|||
|
|
function getLanguageName(localeCode) {
|
|||
|
|
const languageNames = {
|
|||
|
|
'cn': '简体中文',
|
|||
|
|
'zh': 'English',
|
|||
|
|
'zh-TW': '繁體中文'
|
|||
|
|
}
|
|||
|
|
return languageNames[localeCode] || localeCode
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function switchLanguage(newLocale) {
|
|||
|
|
if (isChangingLanguage.value) return
|
|||
|
|
|
|||
|
|
// 获取当前路径,去掉语言前缀
|
|||
|
|
const currentPath = route.path
|
|||
|
|
const parts = currentPath.split('/')
|
|||
|
|
|
|||
|
|
// 如果当前URL已经包含语言代码,则去掉它
|
|||
|
|
let pathWithoutLocale = currentPath
|
|||
|
|
if (availableLocales.includes(parts[1])) {
|
|||
|
|
pathWithoutLocale = currentPath.substring(parts[1].length + 1) || '/'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 构建带有新语言代码的路径
|
|||
|
|
const newPath = newLocale === 'zh'
|
|||
|
|
? `/${newLocale}${pathWithoutLocale === '/' ? '' : pathWithoutLocale}`
|
|||
|
|
: `/${newLocale}${pathWithoutLocale === '/' ? '' : pathWithoutLocale}`
|
|||
|
|
|
|||
|
|
// 使用平滑过渡方法切换语言并导航到新路径
|
|||
|
|
smoothSwitchLanguage(newLocale)
|
|||
|
|
|
|||
|
|
// 导航到新路径
|
|||
|
|
router.push(newPath)
|
|||
|
|
|
|||
|
|
isOpen.value = false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function toggleDropdown() {
|
|||
|
|
if (isChangingLanguage.value) return
|
|||
|
|
isOpen.value = !isOpen.value
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 点击外部关闭下拉菜单
|
|||
|
|
function closeDropdown(e) {
|
|||
|
|
if (!e.target.closest('.language-switcher')) {
|
|||
|
|
isOpen.value = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 在组件挂载时添加事件监听
|
|||
|
|
onMounted(() => {
|
|||
|
|
document.addEventListener('click', closeDropdown)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 在组件卸载时移除事件监听
|
|||
|
|
onUnmounted(() => {
|
|||
|
|
document.removeEventListener('click', closeDropdown)
|
|||
|
|
})
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.language-switcher {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes fadeIn {
|
|||
|
|
from { opacity: 0; transform: translateY(-10px); }
|
|||
|
|
to { opacity: 1; transform: translateY(0); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.absolute {
|
|||
|
|
animation: fadeIn 0.2s ease-out;
|
|||
|
|
}
|
|||
|
|
</style>
|