vue-i18n 实现国际化,支持切换不同语言

news2024/12/24 20:13:55

需求:后台管理系统,可以实现语言切换

实现过程:用的i18n来实现的语言切换,网上能看到好多模板,根据自己的需求,修改一下即可使用,大概都是差不多的,因为涉及到后端,所以要跟后端协商一致决定去写,我的设计思路是跟着后端设计更改的,如下:

1.语言是后端接口返回的,不是前端写死的(eg:中文、English),由于我的切换语言,设计到了两个地方,一个是登录页面,一个是登录之后的页面,后端不能给一个接口,要区分两个接口给前端,所以关于这个需求,前端自己加了判断(如果你们后端给一个接口,则可忽略我写的判断)

2.根据自己选择的哪一种语言,需要通过接口传给后端,后端会将其存到某个用户表里,这个接口也是两个,也需写判断

3.如果用户是从来没有选择过语言的用户,则前端需要规定默认语言,且要与后端的默认语言保持一致,于是和后端协商一致决定,其默认语言是后端返回的语言的第一个

4.如果后端有选择是哪个语言,我们将其传给了后端,后端讲其存入了用户表,用户登录之后,会在用户接口里,返回给我们,我们将其存 localStorage,这时候,就算用户再次退出到登录页面,我们就可以将用户默认语言做一个判断,判断存入localStorage的language是否有值,如果有值,则登录页面的语言取localStorage的language,如果没有,则还是取后端语言接口返回的第一个值,这样,就可以把用户已经选择过的语言,在登录页面也能进行判断用户习惯

文档: vue-i18n

一.安装vue-i18n 

npm install vue-i18n

二.在mian.js引入

//i18n
import i18n from "@/lang";
Vue.use(Element,{
  size:Cookies.get('size') || 'small',
  i18n:(key,value)=>i18n.t(key,value)
})
new Vue({
  el: '#app',
  router,
  store,
  i18n,
  render: h => h(App)
})

三.在src下创建一个lang文件夹,其中包括en.js、zh.js、index.js文件

1.src/lang/en.js

export default {
  login: {
    username:'username',//用户名
    password:'password',//密码
    code:'code',//验证码
    login:'login',//登录
    logging: 'logging...',//登录...
    storage:'remember the password',//记住密码
  },
  home:{
    welcome:'Welcome to use',//欢迎使用
  },
  route:{
    homepage:'Home page',//首页
    profile:'Profile',//个人中心
    系统管理:"system manage",//系统管理
    系统监控:'system monitor',
    系统工具:"system tools",
    创建运单:"create waybill",
    批量运单:'waybill batch',
    运单管理:"waybill manage",
    审核管理:'audits manage',
    充值管理:'recharge manage',
    失败订单详情:'failed order details',
    运费管理:'freight manage',
    账单管理:'bill manage',
    仓库管理:'warehouse manage',
    用户管理:'user query',
    角色管理:'user query',
    菜单管理:'menu manage',
    部门管理:'dept manage',
    岗位管理:'position manage',
    字典管理:'dictionary manage',
    参数设置:'parameter settings',
    通知公告:'notice announcent',
    日志管理:'log manage',
    操作日志:'operation log',
    登录日志:'login log',
    在线用户:'online users',
    定时任务:'scheduled tasks',
    数据监控:'data monitor',
    服务监控:'service monitor',
    运单审核:'audits order',
    充值审核:'audits recharge',
    客户充值:'customer recharge',
    我的充值:'my recharge',
    基础运费:'basic freight',
    其他附加费:'other surcharges',
    燃油附加费:'fuel surcharge',
    旺季附加费:'peak surcharge',
    全部账单:'bill whole',
    我的账单:'bill my',
    订单详情:'order details',
    仓库配置:'warehouse configuration',
    基础成本价:'basic cost',
    其他成本价:'other cost',
    旺季成本价:'peak cost',
    查询:'query',
    新增:'add',
    修改:'alter',
    导入:'import',
    导出:'export',
    删除:'remove',
    重置密码:'reset passwords',
    仓库:'warehouse',
    批量强退:'batch forcing',
    单条强退:'single strong back',
    众拓网通官网:'Zhongtuo Netcom official website',
    下载失败订单:'download failed order',
    下载面单:'download sheet',
    客户查询:'customer query',
    已取消导出:'export cancelled',
    取消审核中导出:'export cancel audit',
    审核:'audit',
    审核平账:'audit and balance accounts',
    审核平账明细:'review the balance of accounts details',
    充值:'top up',
    充值记录:'recharge record',
    消费记录:'expense calendar',
    补交凭证:'resubmit documents',
    回显:'echo',
    审核明细:'audit detail',
    充值明细:'top-up details',
    失败订单详情查询:'failed order details query',
    复制:'copy',
    基础运费列表:'base freight list',
    客户列表:'customer list',
  },
}

 2.src/lang/zh.js

export default {
  login: {
    username:'用户名',//用户名
    password:'密码',//密码
    code:'验证码',//验证码
    login:'登录',//登录
    logging: '登录...',//登录...
    storage:'记住密码',//记住密码
  },
  home:{
    welcome:'欢迎使用',//欢迎使用
  },
  route:{
    homepage:'首页',//首页
    profile:'个人中心',//个人中心
    系统管理:"系统管理",//系统管理
    系统监控:'系统监控',
    系统工具:"系统工具",
    创建运单:"创建运单",
    批量运单:'批量运单',
    运单管理:"运单管理",
    审核管理:'审核管理',
    充值管理:'充值管理',
    失败订单详情:'失败订单详情',
    运费管理:'运费管理',
    账单管理:'账单管理',
    仓库管理:'仓库管理',
    用户管理:'用户管理',
    角色管理:'角色管理',
    菜单管理:'菜单管理',
    部门管理:'部门管理',
    岗位管理:'岗位管理',
    字典管理:'字典管理',
    参数设置:'参数设置',
    通知公告:'通知公告',
    日志管理:'日志管理',
    操作日志:'操作日志',
    登录日志:'登录日志',
    在线用户:'在线用户',
    定时任务:'定时任务',
    数据监控:'数据监控',
    服务监控:'服务监控',
    运单审核:'运单审核',
    充值审核:'充值审核',
    客户充值:'客户充值',
    我的充值:'我的充值',
    基础运费:'基础运费',
    其他附加费:'其他附加费',
    燃油附加费:'燃油附加费',
    旺季附加费:'旺季附加费',
    全部账单:'全部账单',
    我的账单:'我的账单',
    订单详情:'订单详情',
    仓库配置:'仓库配置',
    基础成本价:'基础成本价',
    其他成本价:'其他成本价',
    旺季成本价:'旺季成本价',
    查询:'查询',
    新增:'新增',
    修改:'修改',
    导入:'导入',
    导出:'导出',
    删除:'删除',
    重置密码:'重置密码',
    仓库:'仓库',
    批量强退:'批量强退',
    单条强退:'单条强退',
    众拓网通官网:'众拓网通官网',
    下载失败订单:'下载失败订单',
    下载面单:'下载面单',
    客户查询:'客户查询',
    已取消导出:'已取消导出',
    取消审核中导出:'取消审核中导出',
    审核:'审核',
    审核平账:'审核平账',
    审核平账明细:'审核平账明细',
    充值:'充值',
    充值记录:'充值记录',
    消费记录:'消费记录',
    补交凭证:'补交凭证s',
    回显:'回显',
    审核明细:'审核明细',
    充值明细:'充值明细',
    失败订单详情查询:'失败订单详情查询',
    复制:'复制',
    基础运费列表:'基础运费列表',
    客户列表:'客户列表',
  },
}

  3.src/lang/index.js

import Vue from 'vue'
import VueI18n from 'vue-i18n'
import Cookies from 'js-cookie'
import elementEnLocale from 'element-ui/lib/locale/lang/en' // element-ui lang
import elementZhLocale from 'element-ui/lib/locale/lang/zh-CN'// element-ui lang
import enLocale from './en'
import zhLocale from './zh'

Vue.use(VueI18n)

const messages = {
  en: {
    ...enLocale,
    ...elementEnLocale
  },
  zh: {
    ...zhLocale,
    ...elementZhLocale
  }
}
export function getLanguage() {
  const chooseLanguage = Cookies.get('language')
  if (chooseLanguage) return chooseLanguage
  return 'en'
}
const i18n = new VueI18n({
  locale: getLanguage(),
  messages
})

export default i18n

三.在src/components下创建到LangSelect/index.vue文件(语言切换组件)

<script src="../../api/login.js"></script>
<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  v-for="item in languagelist"  :disabled="$i18n.locale===item.language" :command={languageId:item.languageId,language:item.language}>
        {{item.name}}
      </el-dropdown-item>
    </el-dropdown-menu>
  </el-dropdown>
</template>

<script>
import {checkLanguage, languagelist} from "@/api/menu";
import item from "@/layout/components/Sidebar/Item.vue";
import {languagelistt,checkLanguagee} from '@/api/login'

export default {
  props:{
    //标记他是那个页面过来的,是logo页面,还是登录之后的主页面
    orientation:{
      type:Number,
      default:0
    }
  },
  data(){
    return{
      languagelist:[],
      lang:null,
    }
  },
  computed: {
    item() {
      return item
    },
    language() {
      return this.$store.getters.language
    }
  },
  created() {
    this.getlanguelist()
  },
  methods: {
    //这个是获取后端返回的语言接口
    getlanguelist(){
      //login页面语言接口,接口有两个,但是接口性质是一样的,都是返回语言接口,只不过是一个在登录页面的,一个是登录之后主页面的(如果你们后端返回一个接口,九不用去写这个判断了)
      if(this.orientation==1){
        languagelistt().then((res)=>{
          this.languagelist=res.data
          //拿取user接口存的language,如果有就拿user存的language,如果没有就取后端接口返回的第一个
          const language=localStorage.getItem('language') ||res.data[0].language
          this.$store.dispatch('app/setLanguage', language)
          this.$i18n.locale=language
        })
      }else {
        //主页面语言接口
        languagelist().then((res)=>{
          this.languagelist=res.data
          const language=localStorage.getItem('language')
          this.$store.dispatch('app/setLanguage', language)
          this.$i18n.locale=localStorage.getItem('language')
        })
      }
    },
    handleSetLanguage(lang) {//点击切换事件
      this.$i18n.locale = lang.language
      this.$store.dispatch('app/setLanguage', lang.language)
      this.checkLanguage(lang.languageId)
      this.$message({
        message: 'Switch Language Success',
        type: 'success'
      })
    },
    //传给后端,后端标记是什么语言
    checkLanguage(item){
      //login页面
      if(this.orientation==1){
        checkLanguagee(item).then((res)=>{
          // 传值给前端,前端再把获取到的值,传到login接口里面
          this.$emit("languageid",res.data.languageId)
        })
      }else {
        //主页面
        checkLanguage(item).then((res)=>{
          this.updatePermission()
        })
      }
    },
  }
}
</script>

四.在src/store/modules/app.js 里将language存入vuex和cookie

import Cookies from 'js-cookie'
import { getLanguage } from '@/lang/index'

const state = {
  sidebar: {
    opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
    withoutAnimation: false,
    hide: false
  },
  device: 'desktop',
  language: getLanguage(),
  size: Cookies.get('size') || 'medium'
}

const mutations = {
  TOGGLE_SIDEBAR: state => {
    if (state.sidebar.hide) {
      return false;
    }
    state.sidebar.opened = !state.sidebar.opened
    state.sidebar.withoutAnimation = false
    if (state.sidebar.opened) {
      Cookies.set('sidebarStatus', 1)
    } else {
      Cookies.set('sidebarStatus', 0)
    }
  },
  CLOSE_SIDEBAR: (state, withoutAnimation) => {
    Cookies.set('sidebarStatus', 0)
    state.sidebar.opened = false
    state.sidebar.withoutAnimation = withoutAnimation
  },
  TOGGLE_DEVICE: (state, device) => {
    state.device = device
  },
//语言
  SET_LANGUAGE: (state, language) => {
    state.language = language
    Cookies.set('language', language)
  },
  SET_SIZE: (state, size) => {
    state.size = size
    Cookies.set('size', size)
  },
  SET_SIDEBAR_HIDE: (state, status) => {
    state.sidebar.hide = status
  }
}

const actions = {
  toggleSideBar({ commit }) {
    commit('TOGGLE_SIDEBAR')
  },
  closeSideBar({ commit }, { withoutAnimation }) {
    commit('CLOSE_SIDEBAR', withoutAnimation)
  },
  toggleDevice({ commit }, device) {
    commit('TOGGLE_DEVICE', device)
  },
//语言
  setLanguage({ commit }, language) {
    commit('SET_LANGUAGE', language)
  },
  setSize({ commit }, size) {
    commit('SET_SIZE', size)
  },
  toggleSideBarHide({ commit }, status) {
    commit('SET_SIDEBAR_HIDE', status)
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

五.src/modules/user.js,将后端用户信息接口返回的language存入localStorage里

import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'

const user = {
  state: {
    token: getToken(),
    name: '',
    avatar: '',
    roles: [],
    permissions: [],
  },

  mutations: {
    SET_TOKEN: (state, token) => {
      state.token = token
    },
    SET_NAME: (state, name) => {
      state.name = name
    },
    SET_AVATAR: (state, avatar) => {
      state.avatar = avatar
    },
    SET_ROLES: (state, roles) => {
      state.roles = roles
    },
    SET_PERMISSIONS: (state, permissions) => {
      state.permissions = permissions
    },
  },

  actions: {
    // 登录
    Login({ commit }, userInfo) {
      const username = userInfo.username.trim()
      const password = userInfo.password
      const code = userInfo.code
      const uuid = userInfo.uuid
      //语言
      const languageId=userInfo.languageId
      return new Promise((resolve, reject) => {
        login(username, password, code, uuid,languageId).then(res => {
          setToken(res.token)
          commit('SET_TOKEN', res.token)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 获取用户信息
    GetInfo({ commit, state }) {
      return new Promise((resolve, reject) => {
        getInfo(state.token).then(res => {
          const user = res.user
          const avatar = user.avatar == "" ? require("@/assets/image/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar;
          if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
            commit('SET_ROLES', res.roles)
            commit('SET_PERMISSIONS', res.permissions)
            sessionStorage.setItem('infoCustomers',JSON.stringify(res.customers))
            sessionStorage.setItem('infouser',JSON.stringify(res.user.roles))
            //注:用户已进入页面,调用到用户接口,将用户接口里面的语言存储到localStorage,可以方便用户在登录页面的时候判断语言是中文还是英文(登录页面语言应与user接口保持一直)
            localStorage.setItem('language',JSON.stringify(res.user.language.language).replace(/\"/g, ""))
          } else {
            commit('SET_ROLES', ['ROLE_DEFAULT'])
          }
          commit('SET_NAME', user.userName)
          commit('SET_AVATAR', avatar)
          resolve(res)
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 退出系统
    LogOut({ commit, state }) {
      return new Promise((resolve, reject) => {
        logout(state.token).then(() => {
          commit('SET_TOKEN', '')
          commit('SET_ROLES', [])
          commit('SET_PERMISSIONS', [])
          removeToken()
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 前端 登出
    FedLogOut({ commit }) {
      return new Promise(resolve => {
        commit('SET_TOKEN', '')
        removeToken()
        resolve()
      })
    }
  }
}

export default user

 六.登录页面

 效果图:

1.en:

2.English:

 

<template>
  <div class="login">
    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
      <div class="titlebox">
        <h3 class="title">{{setName}}</h3>
        <lang-select class="set-language"  @languageid="handlelanguage" :orientation='orientation'/>
      </div>
      <el-form-item prop="username">
        <el-input v-model="loginForm.username" type="text" auto-complete="off" :placeholder="$t('login.username')">
          <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
        </el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input
          v-model="loginForm.password"
          type="password"
          auto-complete="off"
          :placeholder="$t('login.password')"
          @keyup.enter.native="handleLogin"
        >
          <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
        </el-input>
      </el-form-item>
      <el-form-item prop="code">
        <el-input
          v-model="loginForm.code"
          auto-complete="off"
          :placeholder="$t('login.code')"
          style="width: 63%"
          @keyup.enter.native="handleLogin"
        >
          <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
        </el-input>
        <div class="login-code">
          <img :src="codeUrl" @click="getCode" class="login-code-img"/>
        </div>
      </el-form-item>
      <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">{{$t('login.storage')}}</el-checkbox>
      <el-form-item style="width:100%;">
        <el-button
          :loading="loading"
          size="medium"
          type="primary"
          style="width:100%;"
          @click.native.prevent="handleLogin"
        >
          <span v-if="!loading">{{$t('login.login')}}</span>
          <span v-else>{{$t('login.logging')}}</span>
        </el-button>
      </el-form-item>
    </el-form>
    <!--  底部  -->
    <div class="el-login-footer">
      <span>Copyright © 2021-2022 xxx All Rights Reserved 增值电信业务经营许可证:<a
        href="http://beian.miit.gov.cn" target="_blank">xxxxx</a></span>
    </div>
  </div>
</template>

<script>
import {getCodeImg, login} from "@/api/login";
import Cookies from "js-cookie";
import {decrypt, encrypt} from '@/utils/jsencrypt'
import langSelect from "@/components/LangSelect/index.vue";
import {config} from "@/api/menu";
import {mapGetters, mapState} from "vuex";

export default {
  name: "Login",
  components:{
    langSelect
  },
  data() {
    return {
      orientation:1,
      codeUrl: "",
      cookiePassword: "",
      loginForm: {
        username: "admin",
        password: "",
        rememberMe: false,
        code: "",
        uuid: "",
        languageId:"",
      },
      loginRules: {
        username: [
          { required: true, trigger: "blur", message: this.$t('rules.rulesusername') }
        ],
        password: [
          { required: true, trigger: "blur", message: this.$t('rules.rulespassword')  }
        ],
        code: [{ required: true, trigger: "change", message: this.$t('rules.rulescode') }]
      },
      loading: false,
      redirect: undefined,
      title:"",
    };
  },
  watch: {
    $route: {
      handler: function(route) {
        this.redirect = route.query && route.query.redirect;
      },
      immediate: true
    },
    //监听语言的改变,实时改变校验
    language(newVal, oldVal){
      this.loginRules={
        username: [
          { required: true, trigger: "blur", message: this.$t('rules.rulesusername') }
        ],
          password: [
          { required: true, trigger: "blur", message: this.$t('rules.rulespassword')  }
        ],
          code: [{ required: true, trigger: "change", message: this.$t('rules.rulescode') }]
      }
    }
  },
  created() {
    this.getCode();
    this.getCookie();
    this.getproject()
  },
  computed:{
    ...mapGetters(['getSetName']),
    setName() {
      return this.getSetName || this.title
    },
    ...mapState({
      language: state => state.app.language
    })
  },
  methods: {
    //获取项目名字接口
    getproject(){
      config().then((res)=>{
        this.title=res.msg
      })
    },
    login,
    handlelanguage(name){
      this.loginForm.languageId=name
    },
    //获取验证码
    getCode() {
      getCodeImg().then(res => {
        this.codeUrl = "data:image/gif;base64," + res.img;
        this.loginForm.uuid = res.uuid;
      });
    },
    getCookie() {
      const username = Cookies.get("username");
      const password = Cookies.get("password");
      const rememberMe = Cookies.get('rememberMe')
      this.loginForm = {
        username: username === undefined ? this.loginForm.username : username,
        password: password === undefined ? this.loginForm.password : decrypt(password),
        rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
      };
    },
    handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true;
          if (this.loginForm.rememberMe) {
            Cookies.set("username", this.loginForm.username, { expires: 30 });
            Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
            Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
          } else {
            Cookies.remove("username");
            Cookies.remove("password");
            Cookies.remove('rememberMe');
          }
          this.$store
            .dispatch("Login", this.loginForm)
            .then(() => {
              this.$router.push({ path: this.redirect || "/" });
            })
            .catch(() => {
              this.loading = false;
              this.getCode();
            });
        }
      });
    }
  }
};
</script>

  七.在utils里面新建一个i18n.js

export function generateTitle(title) {
  const hasKey = this.$te('route.' + title)
  if (hasKey) {
    // $t :this method from vue-i18n, inject in @/lang/index.js
    const translatedTitle = this.$t('route.' + title)
    return translatedTitle
  }
  return title
}

 七.根据语言的切换,面包屑的语言也随之更新,src/components/Breadcrumb文件里

1.zh: 

 

2.en: 

 

<template>
<!--  这个是上面的面包屑-->
  <el-breadcrumb class="app-breadcrumb" separator="/">
    <transition-group name="breadcrumb">
      <el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
        <span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">
<!--          {{ $t(`route.${item.meta.title}`) }}-->
          {{generateTitle(item.meta.title)}}
        </span>
        <a v-else @click.prevent="handleLink(item)">
<!--          {{ $t(`route.${item.meta.title}`)}}-->
          {{generateTitle(item.meta.title)}}
        </a>
      </el-breadcrumb-item>
    </transition-group>
  </el-breadcrumb>
</template>

<script>
import pathToRegexp from 'path-to-regexp'
import { generateTitle } from '@/utils/i18n'
export default {
  inject:['reload'],
  data() {
    return {
      levelList: null
    }
  },
  watch: {
    $route(route) {
      // if you go to the redirect page, do not update the breadcrumbs
      if (route.path.startsWith('/redirect/')) {
        return
      }
      this.getBreadcrumb()
    },
  },
  created() {
    this.getBreadcrumb()
  },

  methods: {
    generateTitle,
    getBreadcrumb() {
      // only show routes with meta.title
      let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
      const first = matched[0]
      if (!this.isDashboard(first)) {
        matched = [{ path: '/index', meta: { title: 'homepage' }}].concat(matched)
      }
      this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
    },
    isDashboard(route) {
      const name = route && route.name
      if (!name) {
        return false
      }
      return name.trim() === 'Home page'
    },
    pathCompile(path) {
      const { params } = this.$route
      var toPath = pathToRegexp.compile(path)
      return toPath(params)
    },
    handleLink(item) {
      const { redirect, path } = item
      if (redirect) {
        this.$router.push(redirect)
        return
      }
      this.$router.push(this.pathCompile(path))
    }
  }
}
</script>

<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
  display: inline-block;
  font-size: 14px;
  line-height: 50px;
  margin-left: 8px;

  .no-redirect {
    color: #97a8be;
    cursor: text;
  }
}
</style>

八.src\layout\components\Sidebar\SidebarItem.vue文件

1.zh                                                                                  

2.en

我用的是若依后台管理系统框架,对于路由是后端接口返回的,所以,路由语言切换只有两种实现方式 ,后端接口返回,缺点,每次新增修改路由,后端都要在数据库里面新增、修改两种语言,较为麻烦,所以路由的语言切换就纯前端进行控制。

<template>
  <div v-if="!item.hidden">
    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="generateTitle(onlyOneChild.meta.title)" />
        </el-menu-item>
      </app-link>
    </template>

    <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
      <template slot="title">
        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="generateTitle(item.meta.title)" />
      </template>
      <sidebar-item
        v-for="child in item.children"
        :key="child.path"
        :is-nest="true"
        :item="child"
        :base-path="resolvePath(child.path)"
        class="nest-menu"
      />
    </el-submenu>
  </div>
</template>

<script>
import path from 'path'
import { generateTitle } from '@/utils/i18n'
import { isExternal } from '@/utils/validate'
import Item from './Item'
import AppLink from './Link'
import FixiOSBug from './FixiOSBug'

export default {
  name: 'SidebarItem',
  components: { Item, AppLink },
  mixins: [FixiOSBug],
  props: {
    // route object
    item: {
      type: Object,
      required: true
    },
    isNest: {
      type: Boolean,
      default: false
    },
    basePath: {
      type: String,
      default: ''
    }
  },
  data() {
    this.onlyOneChild = null
    return {}
  },
  methods: {
    hasOneShowingChild(children = [], parent) {
      const showingChildren = children.filter(item => {
        if (item.hidden) {
          return false
        } else {
          // Temp set(will be used if only has one showing child)
          this.onlyOneChild = item
          return true
        }
      })

      // When there is only one child router, the child router is displayed by default
      if (showingChildren.length === 1) {
        return true
      }

      // Show parent if there are no child router to display
      if (showingChildren.length === 0) {
        this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
        return true
      }

      return false
    },
    resolvePath(routePath) {
      if (isExternal(routePath)) {
        return routePath
      }
      if (isExternal(this.basePath)) {
        return this.basePath
      }
      return path.resolve(this.basePath, routePath)
    },
    generateTitle
  }
}
</script>

 需要注意的是,对于首页,是前端写的路由,其余的页面路由都是后端接口返回的,所以在前端,针对首页,需要改的是router/index.js

九.菜单管理,和路由同理,都是后端接口返回,接口返回的语言切换,纯前端处理

1.zh

2.en 

 

<script>
  computed: {
    ...mapState({
      language: state => state.app.language
    })
  },
  watch:{
    language(newVal, oldVal){
      this.getList()
    }
  },
methods:{
     getList() {
        this.loading = true;
        listMenu(this.queryParams).then(response => {
          this.menuList = this.handleTree(response.data, "menuId").map(item=>{
            const newItem= {
              ...item,
              menuName:this.$t(`route.${item.menuName}`)
            }
            if (newItem.children && newItem.children.length > 0) {
              newItem.children = this.translateChildren(newItem.children);
            }
            return newItem;
          });
          this.loading = false;
        });
      },
    translateChildren(children) {
      return children.map(item => {
        const newItem = {
          ...item,
          menuName: this.$t(`route.${item.menuName}`),
        };
        if (newItem.children && newItem.children.length > 0) {
          newItem.children = this.translateChildren(newItem.children);
        }
        return newItem;
      });
    },
}
</script>

 十.样式处理,可能中文的长度很短,英文长度很长,样式就会错乱,对于这种情况进行处理

  <el-dialog :title="title" :visible.sync="open" width="850px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" :label-width="getLabelWidth()">
        <el-row>
          <el-col :span="12">
<!--            公告标题-->
            <el-form-item :label="$t('notice.announcementtitle')" prop="noticeTitle" >
              <el-input v-model="form.noticeTitle" :placeholder="$t('notice.titleipt')" 
              style="width: 260px"/>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">{{$t('operation.confirm')}}</el-button>
        <el-button @click="cancel">{{$t('operation.cancel')}}</el-button>
      </div>
    </el-dialog>
getLabelWidth(){
      let labelWidth = '80px';
      if(this.language=='en'){
        labelWidth = '150px';
      }
      return labelWidth;
    },

十一.对于校验的地方,语言切换之后,并不会再次触发校验的语言切换 ,关于这个点,我是进行了监听,语言是否改变,如果语言改变了,我就再次触发校验,缺点,每次切换语言就会出发校验,并不是,我点击了某个提交按钮,进行的校验触发,例如我上面写的登录页面上的校验

 以上就是整个实现过程啦,可能有点地方忘记写了,等想到了,再进行完善

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/715593.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何在 javascript 中交换数组元素

文章目录 在 JavaScript 中使用临时变量交换数组元素使用 ES6 析构函数赋值在 JavaScript 中交换数组元素使用按位异或和数组迭代在 JavaScript 中交换数组元素 交换两个元素的传统方法是使用临时变量。 在 JavaScript 中&#xff0c;我们可以轻松地将数组对象重新分配给默认设…

实验一(7 3)

实验分析&#xff1a; 1.使用合理IP地址规划网络&#xff0c;各自创建环回接口&#xff0c;R1-R2-R3-R4-R5 RIP 100运行版本2&#xff0c;R6-R7 RIP 200 运行版本1 方法&#xff1a;要使得两个不同rip版本互通&#xff0c;需要在r4连通r6&#xff08;或r6连通r4&#xff09;的…

android 如何分析应用的内存(十)——malloc统计和libmemunreachable

android 如何分析应用的内存&#xff08;十&#xff09; 接下来介绍native heap内存的第四个板块————malloc统计和libmemunreachable malloc统计 malloc统计是标准c库提供的接口。他有两个调用接口如下&#xff1a; #include <malloc.h>struct mallinfo mallinfo…

敏捷开发Scrum

目录 1 Scrum概览1.2 理论基础1.2.1 透明性&#xff08;Transparency&#xff09;1.2.2 检验&#xff08;Inspection&#xff09;1.2.3 适应&#xff08;Adaptation&#xff09; 2 三个角色2.1 产品负责人&#xff08;Product Owner&#xff09;2.1.1 职责2.1.2 人选 2.2 流程管…

JDK多版本管理工具jenv

JENV mac jdk版本管理工具 Mac 安装jenv可以使用brew brew install jenv配置jenv zsh配置方式&#xff1a; echo export PATH"$HOME/.jenv/bin:$PATH" >> ~/.zshrc echo eval "$(jenv init -)" >> ~/.zshrcbash配置方式&#xff1a; ech…

海上明月共潮生【InsCode Stable Diffusion 美图活动一期】

一、 Stable Diffusion 模型在线使用地址&#xff1a; https://inscode.csdn.net/inscode/Stable-Diffusion 购买 右下角点击 Stable Diffusion WebUI 进入工作界面 二、模型相关版本和参数配置&#xff1a; 模型&#xff1a;chilloutmix_NiPrunerdFp32Fix.safetensors…

解决Python的SyntaxError: Non-UTF-8 code starting with ‘\xbb‘问题

文章目录 一、报错二、分析三、解决3.1 方法一3.2 方法二3.3 方法三 一、报错 在程序中&#xff0c;line 8为含有中文的注释&#xff0c;编译后出现了SyntaxError: Non-UTF-8 code starting with \xbb的报错&#xff0c;具体如下图。 二、分析 这个错误是由于Python解释器无…

教你如何快速批量添加滚动文字水印

在视频中添加滚动的文字水印可以增加视频的专业感和吸引力&#xff0c;但手动给大量视频添加滚动文字水印是一项费时费力的任务。下面是一个快速批量给大量视频添加滚动文字水印的方法&#xff0c;有需要的小伙伴们可以进来学习一下&#xff01; 今天分享的方法会使用到一个剪辑…

IDEA 中 gradle面板显示重复工程

使用 IDEA 构建多模块工程时&#xff0c;每新建一个模块&#xff0c;gradle 构建后&#xff0c;面板上就会出现一个重复的根工程&#xff0c;如下图红框部分&#xff0c;怎么处理下

ASIC数字设计:前端设计、验证、后端实现

前端设计 数字系统设计中有三个重要的设计级别概念&#xff1a;行为级&#xff08;Behavior Level&#xff09;、寄存器传输级&#xff08;Register Transfer Level&#xff09;和门级&#xff08;Gate level&#xff09;。其中&#xff0c; 行为级通过行为级算法描述数字系统&…

zabbix 报警测试报错

一、报错描述 在测试报警媒介时&#xff0c;报错如下: main.py脚本是在windows上的pycharm上编写的&#xff0c;在windows上运行没有问题&#xff0c;放在linux服务器上使用python3 main.py 运行也没有问题&#xff0c;但是使用./main.py执行就报错 [rootp0-tkhijbs-broadco-…

十、Docker虚悬镜像

学习参考&#xff1a;尚硅谷Docker实战教程、Docker官网、其他优秀博客(参考过的在文章最后列出) 目录 前言一、介绍二、如何产生的&#xff1f;三、尝试弄一个三、查看、删除虚悬镜像3.1 查看虚悬镜像3.2 删除虚悬镜像 总结 前言 什么是虚悬镜像&#xff1f; 一、介绍 虚悬…

【LEAP模型】能源供应转换、需求及碳排放预测、平衡表核算、模型框架构建、操作、情景设计、结果分析、优化、预测结果不确定性分析等

模型简介&#xff1a; 中文名&#xff1a;LEAP模型 外文名&#xff1a;Long Range Energy Alternatives Planning System/ Low emission analysis platform LEAP模型[1]允许研究者根据研究目的、数据可获取度、研究对象特点等灵活构建模型结构&#xff0c;十分适用于能源数据…

如何修改Jupyter Notebook的默认目录和默认浏览器

一、修改默认目录 Jupyter Notebook的文件默认保存目录是C:\Users\Administrator&#xff0c;默认目录可在黑窗口中查看&#xff0c;如下图所示&#xff1a; 为了方便文档的管理&#xff0c;可将默认目录修改成自己想保存的地方。修改方法如下&#xff1a; 1、找到config文件 …

centos搭建ftp服务器window使用ftp命令

part 1、centos搭建ftp服务器 参考链接&#xff1a;Linux搭建FTP&#xff0c;并使用Windows和IE浏览器访问FTP服务_一二三&#xff0c;开花的博客-CSDN博客 part 2、window使用ftp服务器 参考链接&#xff1a;Windows命令之ftp命令「建议收藏」-腾讯云开发者社区-腾讯云 备…

深度学习准确率提升之天花板分析

案例1 OCR文字识别流水线主要分为三个模块&#xff1a;文字检测->字符分割->字符识别 训练完成后整个系统的准确率是72%&#xff0c;需要进一步提升准确率就需要单独分析每个模块的提升空间。 1&#xff09;对于文件检测模块&#xff0c;把训练集的图像人工确保标注准…

分享一份不算优秀的HTML简历模版

一份算不上优秀的HTML简历模版 Demo http://sylvanding.github.io/cv Repo https://github.com/sylvanding/cv 示例 总结了一下本科四年都做了些什么&#xff0c;把这套模版分享给大家。

【C】数据在内存中的存储

前言 > 在内存中&#xff0c;整型和浮点型存储的方式是不同的&#xff0c;从内存中读取的方式也是有所差异的&#xff0c;这篇文章主要介绍整型和浮点型在内存中存储的方式。 整型在内存中的存储 计算机中有符号数有3种表示方式&#xff1a; 原码&#xff1a;直接将二进制按…

Material —— RBD(Houdini To UE)

目录 一&#xff0c;ABC to UE 二&#xff0c;FBX to UE 三&#xff0c;APEX to UE 四&#xff0c;VAT to UE Houdini刚体碎块导入UE&#xff0c;有两个方面需还原&#xff0c;一是材质还原&#xff0c;一是动态还原&#xff1b; 一&#xff0c;ABC to UE 材质方面&#…

医学图像超分辨率的多模态不同核大小的多头卷积注意

文章目录 Multimodal Multi-Head Convolutional Attention with Various Kernel Sizes for Medical Image Super-Resolution摘要本文方法实验结果 Multimodal Multi-Head Convolutional Attention with Various Kernel Sizes for Medical Image Super-Resolution 摘要 超分辨…