引入语言包
This commit is contained in:
16
src/hooks/useI18n.js
Normal file
16
src/hooks/useI18n.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useI18n as useVueI18n } from 'vue-i18n'
|
||||
import { useI18nStore } from '@/stores/modules/i18n'
|
||||
|
||||
export function useI18n() {
|
||||
const { t, locale, availableLocales } = useVueI18n()
|
||||
const i18nStore = useI18nStore()
|
||||
|
||||
return {
|
||||
t,
|
||||
locale,
|
||||
availableLocales,
|
||||
setLocale: i18nStore.setLocale,
|
||||
currentLocale: i18nStore.currentLocale,
|
||||
localeLabel: i18nStore.localeLabel
|
||||
}
|
||||
}
|
||||
15
src/i18n/index.js
Normal file
15
src/i18n/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import zh from './locales/zh-CN'
|
||||
import en from './locales/en-US'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'zh-CN',
|
||||
fallbackLocale: 'en-US',
|
||||
messages: {
|
||||
'zh-CN': zh,
|
||||
'en-US': en
|
||||
}
|
||||
})
|
||||
|
||||
export default i18n
|
||||
103
src/i18n/locales/en-US.js
Normal file
103
src/i18n/locales/en-US.js
Normal file
@@ -0,0 +1,103 @@
|
||||
export default {
|
||||
common: {
|
||||
welcome: 'Welcome',
|
||||
login: 'Login',
|
||||
logout: 'Logout',
|
||||
register: 'Register',
|
||||
username: 'Username',
|
||||
password: 'Password',
|
||||
confirmPassword: 'Confirm Password',
|
||||
email: 'Email',
|
||||
phone: 'Phone',
|
||||
rememberMe: 'Remember Me',
|
||||
forgotPassword: 'Forgot Password?',
|
||||
submit: 'Submit',
|
||||
cancel: 'Cancel',
|
||||
save: 'Save',
|
||||
edit: 'Edit',
|
||||
delete: 'Delete',
|
||||
add: 'Add',
|
||||
search: 'Search',
|
||||
reset: 'Reset',
|
||||
confirm: 'Confirm',
|
||||
back: 'Back',
|
||||
next: 'Next',
|
||||
previous: 'Previous',
|
||||
refresh: 'Refresh',
|
||||
export: 'Export',
|
||||
import: 'Import',
|
||||
download: 'Download',
|
||||
upload: 'Upload',
|
||||
view: 'View',
|
||||
detail: 'Detail',
|
||||
settings: 'Settings',
|
||||
profile: 'Profile',
|
||||
language: 'Language',
|
||||
theme: 'Theme',
|
||||
dark: 'Dark',
|
||||
light: 'Light',
|
||||
loading: 'Loading...',
|
||||
noData: 'No Data',
|
||||
success: 'Operation Successful',
|
||||
error: 'Operation Failed',
|
||||
warning: 'Warning',
|
||||
info: 'Info',
|
||||
confirmDelete: 'Are you sure you want to delete?',
|
||||
confirmLogout: 'Are you sure you want to logout?',
|
||||
required: 'This field is required',
|
||||
operation: 'Operation',
|
||||
time: 'Time',
|
||||
status: 'Status',
|
||||
enabled: 'Enabled',
|
||||
disabled: 'Disabled',
|
||||
yes: 'Yes',
|
||||
no: 'No'
|
||||
},
|
||||
menu: {
|
||||
dashboard: 'Dashboard',
|
||||
userManagement: 'User Management',
|
||||
roleManagement: 'Role Management',
|
||||
permissionManagement: 'Permission Management',
|
||||
systemSettings: 'System Settings',
|
||||
logManagement: 'Log Management'
|
||||
},
|
||||
login: {
|
||||
title: 'User Login',
|
||||
subtitle: 'Welcome back, please login to your account',
|
||||
loginButton: 'Login',
|
||||
loginSuccess: 'Login Successful',
|
||||
loginFailed: 'Login Failed',
|
||||
usernamePlaceholder: 'Please enter username',
|
||||
passwordPlaceholder: 'Please enter password',
|
||||
noAccount: "Don't have an account?",
|
||||
registerNow: 'Register Now'
|
||||
},
|
||||
layout: {
|
||||
toggleSidebar: 'Toggle Sidebar',
|
||||
collapse: 'Collapse',
|
||||
expand: 'Expand',
|
||||
logout: 'Logout'
|
||||
},
|
||||
table: {
|
||||
total: 'Total {total} items',
|
||||
selected: '{selected} items selected',
|
||||
actions: 'Actions',
|
||||
noData: 'No Data',
|
||||
sort: 'Sort',
|
||||
filter: 'Filter'
|
||||
},
|
||||
pagination: {
|
||||
goTo: 'Go to',
|
||||
page: 'Page',
|
||||
total: 'Total {total} items',
|
||||
itemsPerPage: '{size} items per page'
|
||||
},
|
||||
form: {
|
||||
required: 'This field is required',
|
||||
invalidEmail: 'Please enter a valid email address',
|
||||
invalidPhone: 'Please enter a valid phone number',
|
||||
passwordMismatch: 'Passwords do not match',
|
||||
minLength: 'Minimum {min} characters required',
|
||||
maxLength: 'Maximum {max} characters allowed'
|
||||
}
|
||||
}
|
||||
103
src/i18n/locales/zh-CN.js
Normal file
103
src/i18n/locales/zh-CN.js
Normal file
@@ -0,0 +1,103 @@
|
||||
export default {
|
||||
common: {
|
||||
welcome: '欢迎使用',
|
||||
login: '登录',
|
||||
logout: '退出登录',
|
||||
register: '注册',
|
||||
username: '用户名',
|
||||
password: '密码',
|
||||
confirmPassword: '确认密码',
|
||||
email: '邮箱',
|
||||
phone: '手机号',
|
||||
rememberMe: '记住我',
|
||||
forgotPassword: '忘记密码?',
|
||||
submit: '提交',
|
||||
cancel: '取消',
|
||||
save: '保存',
|
||||
edit: '编辑',
|
||||
delete: '删除',
|
||||
add: '添加',
|
||||
search: '搜索',
|
||||
reset: '重置',
|
||||
confirm: '确认',
|
||||
back: '返回',
|
||||
next: '下一步',
|
||||
previous: '上一步',
|
||||
refresh: '刷新',
|
||||
export: '导出',
|
||||
import: '导入',
|
||||
download: '下载',
|
||||
upload: '上传',
|
||||
view: '查看',
|
||||
detail: '详情',
|
||||
settings: '设置',
|
||||
profile: '个人资料',
|
||||
language: '语言',
|
||||
theme: '主题',
|
||||
dark: '暗色',
|
||||
light: '亮色',
|
||||
loading: '加载中...',
|
||||
noData: '暂无数据',
|
||||
success: '操作成功',
|
||||
error: '操作失败',
|
||||
warning: '警告',
|
||||
info: '提示',
|
||||
confirmDelete: '确定要删除吗?',
|
||||
confirmLogout: '确定要退出登录吗?',
|
||||
required: '此项为必填项',
|
||||
operation: '操作',
|
||||
time: '时间',
|
||||
status: '状态',
|
||||
enabled: '启用',
|
||||
disabled: '禁用',
|
||||
yes: '是',
|
||||
no: '否'
|
||||
},
|
||||
menu: {
|
||||
dashboard: '仪表板',
|
||||
userManagement: '用户管理',
|
||||
roleManagement: '角色管理',
|
||||
permissionManagement: '权限管理',
|
||||
systemSettings: '系统设置',
|
||||
logManagement: '日志管理'
|
||||
},
|
||||
login: {
|
||||
title: '用户登录',
|
||||
subtitle: '欢迎回来,请登录您的账户',
|
||||
loginButton: '登录',
|
||||
loginSuccess: '登录成功',
|
||||
loginFailed: '登录失败',
|
||||
usernamePlaceholder: '请输入用户名',
|
||||
passwordPlaceholder: '请输入密码',
|
||||
noAccount: '还没有账户?',
|
||||
registerNow: '立即注册'
|
||||
},
|
||||
layout: {
|
||||
toggleSidebar: '切换侧边栏',
|
||||
collapse: '折叠',
|
||||
expand: '展开',
|
||||
logout: '退出登录'
|
||||
},
|
||||
table: {
|
||||
total: '共 {total} 条',
|
||||
selected: '已选择 {selected} 项',
|
||||
actions: '操作',
|
||||
noData: '暂无数据',
|
||||
sort: '排序',
|
||||
filter: '筛选'
|
||||
},
|
||||
pagination: {
|
||||
goTo: '前往',
|
||||
page: '页',
|
||||
total: '共 {total} 条',
|
||||
itemsPerPage: '每页 {size} 条'
|
||||
},
|
||||
form: {
|
||||
required: '此项为必填项',
|
||||
invalidEmail: '请输入有效的邮箱地址',
|
||||
invalidPhone: '请输入有效的手机号',
|
||||
passwordMismatch: '两次输入的密码不一致',
|
||||
minLength: '最少需要 {min} 个字符',
|
||||
maxLength: '最多允许 {max} 个字符'
|
||||
}
|
||||
}
|
||||
91
src/layouts/components/LanguageSwitcher.vue
Normal file
91
src/layouts/components/LanguageSwitcher.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useI18nStore } from '@/stores/modules/i18n'
|
||||
|
||||
const i18nStore = useI18nStore()
|
||||
const showDropdown = ref(false)
|
||||
|
||||
const toggleDropdown = () => {
|
||||
showDropdown.value = !showDropdown.value
|
||||
}
|
||||
|
||||
const handleLanguageChange = (locale) => {
|
||||
i18nStore.setLocale(locale)
|
||||
showDropdown.value = false
|
||||
}
|
||||
|
||||
const closeDropdown = () => {
|
||||
showDropdown.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="language-switcher" @click="toggleDropdown">
|
||||
<span class="language-dropdown">
|
||||
{{ i18nStore.localeLabel }}
|
||||
<svg class="arrow-icon" :class="{ 'rotate': showDropdown }" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3 0.1-12.7-6.4-12.7z" />
|
||||
</svg>
|
||||
</span>
|
||||
<div v-show="showDropdown" class="dropdown-menu">
|
||||
<div v-for="locale in i18nStore.availableLocales" :key="locale.value" class="dropdown-item"
|
||||
@click.stop="handleLanguageChange(locale.value)">
|
||||
{{ locale.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.language-switcher {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.language-dropdown {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.language-dropdown:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.arrow-icon.rotate {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
right: 0;
|
||||
min-width: 120px;
|
||||
background: white;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 8px 16px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
</style>
|
||||
@@ -3,10 +3,17 @@ import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import pinia from './stores'
|
||||
import i18n from './i18n'
|
||||
import { useI18nStore } from './stores/modules/i18n'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(router)
|
||||
app.use(pinia)
|
||||
app.use(i18n)
|
||||
|
||||
// 初始化 i18n store,从 localStorage 读取保存的语言设置
|
||||
const i18nStore = useI18nStore()
|
||||
i18nStore.initLocale()
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from '@/hooks/useI18n'
|
||||
import LanguageSwitcher from '@/layouts/components/LanguageSwitcher.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const loginForm = ref({
|
||||
username: '',
|
||||
@@ -16,46 +20,51 @@ const handleLogin = () => {
|
||||
console.log('登录信息:', loginForm.value)
|
||||
loading.value = false
|
||||
// 这里可以添加实际的登录逻辑
|
||||
alert('登录成功!(模拟)')
|
||||
alert(t('login.loginSuccess') + '!(模拟)')
|
||||
}, 1000)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<div class="language-switcher-wrapper">
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
<div class="login-card">
|
||||
<div class="login-header">
|
||||
<h2>用户登录</h2>
|
||||
<p>欢迎回来,请登录您的账户</p>
|
||||
<h2>{{ t('login.title') }}</h2>
|
||||
<p>{{ t('login.subtitle') }}</p>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="handleLogin" class="login-form">
|
||||
<div class="form-group">
|
||||
<label for="username">用户名</label>
|
||||
<input id="username" v-model="loginForm.username" type="text" placeholder="请输入用户名" required />
|
||||
<label for="username">{{ t('common.username') }}</label>
|
||||
<input id="username" v-model="loginForm.username" type="text"
|
||||
:placeholder="t('login.usernamePlaceholder')" required />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">密码</label>
|
||||
<input id="password" v-model="loginForm.password" type="password" placeholder="请输入密码" required />
|
||||
<label for="password">{{ t('common.password') }}</label>
|
||||
<input id="password" v-model="loginForm.password" type="password"
|
||||
:placeholder="t('login.passwordPlaceholder')" required />
|
||||
</div>
|
||||
|
||||
<div class="form-options">
|
||||
<label class="remember-me">
|
||||
<input type="checkbox" />
|
||||
<span>记住我</span>
|
||||
<span>{{ t('common.rememberMe') }}</span>
|
||||
</label>
|
||||
<a href="#" class="forgot-password">忘记密码?</a>
|
||||
<a href="#" class="forgot-password">{{ t('common.forgotPassword') }}</a>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="login-btn" :disabled="loading">
|
||||
<span v-if="!loading">登录</span>
|
||||
<span v-else>登录中...</span>
|
||||
<span v-if="!loading">{{ t('login.loginButton') }}</span>
|
||||
<span v-else>{{ t('common.loading') }}</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="login-footer">
|
||||
<p>还没有账户? <a href="#">立即注册</a></p>
|
||||
<p>{{ t('login.noAccount') }} <a href="#">{{ t('login.registerNow') }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -73,6 +82,13 @@ const handleLogin = () => {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.language-switcher-wrapper {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 网格背景 */
|
||||
.login-container::before {
|
||||
content: '';
|
||||
|
||||
34
src/stores/modules/i18n.js
Normal file
34
src/stores/modules/i18n.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import i18n from '@/i18n'
|
||||
|
||||
export const useI18nStore = defineStore('i18n', {
|
||||
state: () => ({
|
||||
currentLocale: 'zh-CN',
|
||||
availableLocales: [
|
||||
{ label: '简体中文', value: 'zh-CN' },
|
||||
{ label: 'English', value: 'en-US' }
|
||||
]
|
||||
}),
|
||||
|
||||
getters: {
|
||||
localeLabel: (state) => {
|
||||
const locale = state.availableLocales.find((item) => item.value === state.currentLocale)
|
||||
return locale ? locale.label : ''
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
setLocale(locale) {
|
||||
this.currentLocale = locale
|
||||
i18n.global.locale.value = locale
|
||||
localStorage.setItem('locale', locale)
|
||||
},
|
||||
|
||||
initLocale() {
|
||||
const savedLocale = localStorage.getItem('locale')
|
||||
if (savedLocale && this.availableLocales.some((item) => item.value === savedLocale)) {
|
||||
this.setLocale(savedLocale)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -6,13 +6,13 @@ import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user