花裤衩前辈的vue-element-admin模块在4.x的大版本中去除了对i18n国际化的支持,本次因项目需要,在一个基于 vue-element-admin V4.2.1
版本模板开发的项目中,需要加入中英文切换的支持,此处添加参考了 vue-element-admin 模板早期支持国际化版本的语言切换组件,具体步骤如下。
安装 vue-i18n 插件
npm install --save vue-i18n
安装后 package.json
中如下:
node_modules中,如下:
添加自定义配置
创建 src/lang/index.js,其中 elementEnLocale
和 elementZhLocale
是 Element UI 自带的多语言配置,enLocale
和 zhLocale
是自定义的中英文配置,与 src/lang/index.js
在同一目录下。
// 进行多语言支持配置
import Vue from 'vue' // 引入Vue
import VueI18n from 'vue-i18n' // 引入国际化的插件包
import Cookies from 'js-cookie' // 引入 Cookies 保存当前默认语言选项
import elementEnLocale from 'element-ui/lib/locale/lang/en' // element-ui 英文包
import elementZhLocale from 'element-ui/lib/locale/lang/zh-CN'// element-ui 中文包
// 自定义的中英文配置
import enLocale from './en'
import zhLocale from './zh'
Vue.use(VueI18n); // 全局注册国际化包
// 创建国际化插件的实例
const i18n = new VueI18n({
// 指定语言类型 zh表示中文 en表示英文 set locale 设置默认初始化的语言 i18n
locale: Cookies.get('language') || 'zh',
// 将将elementUI语言包 和自定义语言包 加入到插件语言数据里 set locale messages
messages: {
// 英文环境下的语言数据
en: {
...enLocale,
...elementEnLocale
},
// 中文环境下的语言数据
zh: {
...zhLocale,
...elementZhLocale
}
}
});
export default i18n
en.js示例
export default {
route:{
dashboard: 'Dashboard',
},
// 登录页
loginPage:{
username: 'Username',
password: 'Password',
login:'Login',
}
}
zh.js示例
export default {
route: {
dashboard: '首页',
},
loginPage:{
username: '请输入用户名',
password: '请输入密码',
login:'登录',
}
}
在 main.js 中挂载 i18n 插件
import i18n from "@/lang/i18n";
Vue.use(Element, {
// set element-ui default size
size: Cookies.get('size') || 'medium',
// 配置elementUI 语言转换关系
i18n: (key, value) => i18n.t(key, value)
});
new Vue({
el: '#app',
router,
store,
i18n,
render: h => h(App)
});
修改模板(组件)渲染
当我们引入VueI18n语言插件之后,每个组件实例都拥有了一个
$t()
方法,这个方法可以帮助我们进行语言转换,可以根据当前的语言类型,使用传入的key去寻找当前key对应的文本。
用$t('属性名')
来访问配置文件(en.js和ch.js)对象里的属性。这里的$t()
是引入了i18n之后,自动挂载在vue实例上的功能。
vuex组件 的store模块添加封装方法
src/store/modules/app.js 中添加以下内容
import Cookies from 'js-cookie'
const state = {
// ...其他配置项省略...
// set locale 设置默认初始化的语言 i18n
language: Cookies.get('language') || 'zh'
}
const mutations = {
// ...其他配置项省略...
SET_LANGUAGE: (state, language) => {
state.language = language
Cookies.set('language', language)
}
}
const actions = {
// ...其他配置项省略...
setLanguage({ commit }, language) {
commit('SET_LANGUAGE', language)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
注意:
app.js 的属性中中配置了命名空间 namespaced: true
,其他组件在通过 this.$store.dispatch()
方法调用时,要在 action 方法名之前加上 前缀路径 app/
,不然会报错 unknown action type:XXX(未知的操作类型:)
。
src/store/getters.js 中添加以下内容
const getters = {
sidebar: state => state.app.sidebar,
size: state => state.app.size,
language: state => state.app.language,
// ...其他配置项省略...
}
export default getters
添加实现中英文切换的封装组件
组件名:LangSelect
<template>
<el-dropdown trigger="click" class='international' @command="handleSetLanguage">
<div>
<svg-icon class-name='international-icon' icon-class="language"/>
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="zh" :disabled="language==='zh'">中文</el-dropdown-item>
<el-dropdown-item command="en" :disabled="language==='en'">English</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
name: "LangSelect",
computed: {
language() {
return this.$store.getters.language
}
},
methods: {
handleSetLanguage(lang) {
this.$i18n.locale = lang
this.$store.dispatch('app/setLanguage', lang)
this.$message.success('switch language success')
// 重新刷新页面更改语言
location.reload()
}
}
}
</script>
<style scoped>
.international-icon {
font-size: 20px;
cursor: pointer;
vertical-align: -5px!important;
}
</style>
登录页面添加中英文切换组件
登录页位置 src/views/login/index.vue
,添加中英文切换后,登录页面全局代码
<template>
<div class="fullscreen" :style="'background-color: red;background: url('+loginImgJpgUrl+') ;background-size: cover'">
<!--左上角logo-->
<div class="login-logo" :style="'background: url('+loginLogoImgPngUrl+') no-repeat 10px center'"></div>
<div class="login-box" :style="'background: url('+loginImgPngUrl+') no-repeat center'">
<el-form ref="loginFormRef" :model="loginForm" :rules="loginRules" class="login-form" autocomplete="on"
label-position="left" style="display: block;">
<div class="m-list-group" style="">
<el-form-item prop="username">
<el-input ref="usernameRef" v-model="loginForm.username"
:placeholder="$t('loginPage.username')" name="username" type="text"
tabindex="1" autocomplete="on" style="width: 280px;margin-left: 412px">
<template slot="prepend">
<svg-icon style="height: 18px;width: 15px;" color="green" icon-class="user"></svg-icon>
</template>
</el-input>
</el-form-item>
<el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="bottom" manual
style="width: 280px;margin-left: 412px">
<el-form-item prop="password">
<el-input ref="passwordRef" :key="passwordType" v-model="loginForm.password" :type="passwordType"
:placeholder="$t('loginPage.password')"
name="password" tabindex="2" autocomplete="on"
@keyup.native="checkCapslock"
@blur="capsTooltip = false"
@keyup.enter.native="handleLogin"
>
<template slot="prepend">
<svg-icon style="height: 18px;width: 15px;" color="green" icon-class="password"></svg-icon>
</template>
<template slot="suffix">
<div style="color: black;margin-top: 8px;margin-right: 5px" @click="showPwd">
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"></svg-icon>
</div>
</template>
</el-input>
</el-form-item>
</el-tooltip>
<el-form-item>
<el-button :loading="loading" type="primary" style="width: 200px;margin-left: 422px;margin-top: 40px"
@click.native.prevent="handleLogin">{{$t('loginPage.login')}}
</el-button>
<lang-select class="set-language"></lang-select>
</el-form-item>
</div>
</el-form>
</div>
<!--密码修改对话框-->
<user-update-pwd
ref="UserUpdatePwdRef"
:first-login="ifFirstLogin"
></user-update-pwd>
</div>
</template>
<script>
import {validUsername} from '@/utils/validate'
import UserUpdatePwd from "@/components/UpdatePwd/UserUpdatePwd";
import LangSelect from '@/components/LangSelect'
export default {
name: 'Login',
components: {
UserUpdatePwd,
LangSelect
},
data() {
// 用户名校验
const validateUsername = (rule, value, callback) => {
if (!validUsername(value)) {
callback(new Error('Please enter the correct user name'))
} else {
callback()
}
}
// 密码校验
const validatePassword = (rule, value, callback) => {
if (value.length < 1) {
callback(new Error('The password can not be less than 1 digits'))
} else {
callback()
}
}
return {
loginImgJpgUrl: require('./components/login.jpg'),
loginImgPngUrl: require('./components/login.png'),
loginLogoImgPngUrl: require('./components/logo.png'),
// 当前登录账户是否是首次登录
ifFirstLogin: false,
// 登录表单
loginForm: {
username: '',
password: ''
},
loginRules: {
username: [{required: true, trigger: 'blur', validator: validateUsername}],
password: [{required: true, trigger: 'blur', validator: validatePassword}]
},
passwordType: 'password',
capsTooltip: false,
loading: false,
redirect: undefined,
otherQuery: {}
}
},
watch: {
$route: {
handler: function (route) {
const query = route.query
if (query) {
this.redirect = query.redirect
this.otherQuery = this.getOtherQuery(query)
}
},
immediate: true
}
},
created() {
// window.addEventListener('storage', this.afterQRScan)
},
mounted() {
if (this.loginForm.username === '') {
this.$refs.usernameRef.focus()
} else if (this.loginForm.password === '') {
this.$refs.passwordRef.focus()
}
},
destroyed() {
// window.removeEventListener('storage', this.afterQRScan)
},
methods: {
checkCapslock(e) {
// console.log('KeyboardEvent-----', e)
const {key} = e
this.capsTooltip = key && key.length === 1 && (key >= 'A' && key <= 'Z')
},
showPwd() {
if (this.passwordType === 'password') {
this.passwordType = ''
} else {
this.passwordType = 'password'
}
this.$nextTick(() => {
this.$refs.passwordRef.focus()
})
},
handleLogin() {
this.$refs.loginFormRef.validate(valid => {
if (valid) {
this.loading = true
// 调用vuex的 store 中的 对应的action
this.$store.dispatch('user/login', this.loginForm)
.then(res => {
// 此处的返回值没有用到,可以不返回
console.log('login.vue--user/login--:', res)
// console.log('this.redirect---', this.redirect)
// console.log('this.otherQuery---', this.otherQuery)
let account = res.account
this.ifFirstLogin = res.ifFirstLogin
if (this.ifFirstLogin) {
this.$refs.UserUpdatePwdRef.showDialog(account)
} else {
let ifNotifyUpdatePwd = res.ifNotifyUpdatePwd
if (ifNotifyUpdatePwd) {
// this.$message.warning('账户长时间未修改密码,请定时更新,确保账户安全!')
this.$notify({
title: '密码更新提示',
dangerouslyUseHTMLString: true,
message: '账户' + '<span style="color: red;font-weight: bolder">' + '长时间未修改密码' + '</span>' + ',请定时更新,确保账户安全!',
type: 'warning',
offset: 100,
duration: 30000 // 单位:毫秒,从显示到关闭的时间间隔
})
}
this.$router.push({path: this.redirect || '/', query: this.otherQuery})
this.loading = false
}
}).catch(error => {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})
},
getOtherQuery(query) {
return Object.keys(query).reduce((acc, cur) => {
if (cur !== 'redirect') {
acc[cur] = query[cur]
}
return acc
}, {})
}
}
}
</script>
<style lang="scss">
.fullscreen {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.m-list-group {
border-radius: 3px;
padding: 0;
margin: 120px 0 20px;
}
.set-language {
/*color: #e3e3e3;*/
/*position: absolute;*/
/*top: 5px;*/
left: 30px;
/*right: 0px;*/
}
.login-box {
float: right;
position: relative;
width: 720px;
height: 420px;
margin: 0 auto;
padding: 0px 15px;
}
.login-logo {
position: absolute;
width: 80px;
height: 35px;
top: 80px;
left: 120px;
}
.login-box .m-input {
padding: 10px;
border: none;
outline: none;
box-sizing: border-box;
}
.login-box .m-btn {
border-radius: 5px;
font-size: 15px;
height: 35px;
color: #fff;
background-color: #256ddb;
display: inline-block;
text-align: center;
cursor: pointer;
outline: none;
border: 1px solid #256ddb;
box-sizing: border-box;
text-decoration: none;
}
.login-box .m-btn:hover {
background-color: #1e4bae;
}
.login-box .m-btn:active {
opacity: 0.8;
}
@media (max-width: 768px) {
.login-box {
width: auto;
}
}
</style>
至此,登录页面添加中英文切换组件完成, 可正常替换,全局的自定义中英文对照文档zh.js
和en.js
需要一点点的完善。原理是将设置的语言种类存储在浏览器的Cookies中作为全局配置参数,每次渲染页面之前i18n插件从浏览器Cookies中获取此配置参数,转换成对应是语言。
同理登录之后,导航栏的右上角页也需要添加一个中英文切换组件,此处就不再赘述了。