前提:
(1) 相關博文地址:
SpringBoot + Vue + ElementUI 實現后台管理系統模板 -- 前端篇(一):搭建基本環境:https://www.cnblogs.com/l-y-h/p/12930895.html SpringBoot + Vue + ElementUI 實現后台管理系統模板 -- 前端篇(二):引入 element-ui 定義基本頁面顯示:https://www.cnblogs.com/l-y-h/p/12935300.html SpringBoot + Vue + ElementUI 實現后台管理系統模板 -- 前端篇(三):引入 js-cookie、axios、mock 封裝請求處理以及返回結果:https://www.cnblogs.com/l-y-h/p/12955001.html
(2)代碼地址:
https://github.com/lyh-man/admin-vue-template.git
一、引入 vuex 進行狀態管理
1、簡介
vuex指的是一種狀態管理模式,集中式管理所有組件的狀態(管理數據)。
【vuex 官方文檔:】 https://vuex.vuejs.org/zh/guide/ 【vuex 參考地址:】 https://www.cnblogs.com/l-y-h/p/11666653.html
使用場景分析:
之前 Home.vue 頁面中,Header 部分有個折疊按鈕,點擊之后,可以折疊與展開 Aside 組件,這之間就設計到數據在組件間的共享。
之前使用 this.$emit 觸發父組件的方法,然后通過 props 屬性傳遞數據來實現的。

上面一種方式,雖然可以實現,但是組件太多的話,數據傳遞起來會很復雜。
使用 vuex 后,數據統一管理,當數據發生變化時,其所有引用的地方均會修改。

2、安裝、模塊化使用 vuex
(1)安裝
項目構建時,已經安裝過了。
可以使用 npm 手動安裝,然后在 vuex 中全局引入。
【npm 安裝:】
npm install vuex
如下圖:項目已經構建好了,在 store 文件夾下進行相關編寫即可。

(2)引入 vuex
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { }, mutations: { }, actions: { }, modules: { } })

(3)根據功能拆分成各個模塊
所有的狀態管理寫在一個 js 里,不方便維護。
可以根據不同的功能,抽取去不同的模塊 js 進行處理。
比如:登錄模塊可以定義 user.js 去處理、公共模塊可以定義 common.js 去處理。
如下:定義一個 user.js,其中 state 管理一個 userName 數據,當登錄成功時,保存該數據,並顯示在主頁面的右上角。
export default { // 開啟命名空間(防止各模塊間命名沖突),訪問時需要使用 模塊名 + 方法名 namespaced: true, // 管理數據(狀態) state: { // 用於保存用戶名 userName: 'Admin' }, // 更改 state(同步) mutations: { updateName(state, data) { if (data) { state.userName = data } else { state.userName = 'Admin' } } }, // 異步觸發 mutations actions: { updateName({commit, state}, data) { commit("updateName", data) } } }

(4)將定義好的模塊,在 index.js 中引入。
import Vue from 'vue' import Vuex from 'vuex' import user from './module/user.js' Vue.use(Vuex) export default new Vuex.Store({ modules: { user } })

(5)使用
在組件中,可以引用 state、action 。
其中:
使用 $store.state 可以引用 state 中的數據,也可使用 mapState 替代 $store.state。
使用 $store.dispatch 可以引用 action 中的方法,也可使用 mapActions 替代·$store.dispatch。
如下:
在 Header.vue 頁面中,使用 mapState 引入 userName,並顯示。
在 computed 中 引入 state 定義的屬性(可以使用 數組 或者 對象的形式)。
由於使用了 模塊進行封裝,所以在引入時,第一個參數需要指定 模塊名。
import {mapState} from 'vuex'
export default {
computed: {
// ...mapState('user', {userName: 'userName'}),
...mapState('user', ['userName'])
}
}

在 Login.vue 中引入 action 方法,用於修改 用戶名。
import {mapActions} from 'vuex'
export default {
data() {
return {
dataForm: {
userName: '',
password: ''
}
},
methods: {
...mapActions('user', ['updateName'])
}
}
}

簡單測試一下:
輸入用戶名,登錄后,右上角顯示 自定義用戶名。
不輸入用戶名,登錄后,右上角顯示 Admin(默認)。

二、引入 vue-i18n 進行國際化管理
1、簡介
一般項目都要求多語言顯示,比如: 中文、英文 快速切換。
使用 vue-i18n 插件可以快捷、方便的進行這種操作。
【官方文檔:】 https://kazupon.github.io/vue-i18n/zh/introduction.html
2、安裝、使用
(1)npm 安裝
npm install vue-i18n

(2)引入 vue-i18n
在 src 下新建一個 i18n 目錄,並創建一個 index.js,用於引入 vue-i18n。
並引入 各語言文件。
import Vue from 'vue' // 引入 VueI18n import VueI18n from 'vue-i18n' // 全局掛載 VueI18n Vue.use(VueI18n) // 創建 i18n 實例,並引入語言文件(可以是 js 文件、也可以為 json 文件) const i18n = new VueI18n({ // locale 為語言標識,通過切換locale的值來實現語言切換( this.$i18n.locale ) locale: 'zh', messages: { 'zh': require('@/i18n/languages/zh.json'), 'en': require('@/i18n/languages/en.json') } }) export default i18n

(3)定義各語言文件
此處以 zh.json、en.json 為例(也可以定義成 js 文件)。
【zh.json】 { "login": { "title": "管理員登錄", "userName": "用戶名", "password": "密碼", "language": "語言選擇", "signIn": "登錄", "userNameNotNull": "用戶名不能為空", "passwordNotNull": "密碼不能為空", "signInSuccess": "登錄成功" }, "language": { "setting": "設置", "languageSettings": "語言設置: ", "zh": "中文", "en": "英語" }, "header": { "foldAside": "折疊側邊欄", "unFoldAside": "展開側邊欄", "setUp": "設置", "help": "幫助", "blogAddress": "博客地址", "codeAddress": "代碼地址", "userSetUp": "用戶設置", "updatePassword": "修改密碼", "logOut": "退出" }, "aside": { "adminCenter": "后台管理中心", "admin": "后台", "homePage": "首頁" } } 【en.json】 { "login": { "title": "Administrator Login", "userName": "UserName", "password": "Password", "language": "Language", "signIn": "SignIn", "userNameNotNull": "UserName Not Null", "passwordNotNull": "Password Not Null", "signInSuccess": "SignIn Success" }, "language": { "setting": "Settings", "languageSettings": "Language Settings: ", "zh": "Chinese", "en": "English" }, "header": { "foldAside": "Fold Aside", "unFoldAside": "Un Fold Aside", "setUp": "SetUp", "help": "Help", "blogAddress": "Blog Address", "codeAddress": "Code Address", "userSetUp": "User SetUp", "updatePassword": "Update Password", "logOut": "LogOut" }, "aside": { "adminCenter": "Admin Center", "admin": "AC", "homePage": "Home Page" } }
(4)在 main.js 中全局引入 定義好的 i18n。
import i18n from '@/i18n/index.js' new Vue({ router, store, i18n, render: h => h(App) }).$mount('#app')

(5)根據 zh.json、en.json 定義好的數據,將各個組件中的中文替換掉。
使用 $t("") 去替換值。形式:{{$t("")}} 或者 this.$t("")。
如下例:
替換 Login.vue 中的數據(留個坑)。

(6)填坑
上面我留了個坑,在 data 中使用 this.$t(""),修改語言后,不會變化。
可以將其寫在 computed 屬性中。
如下例,在 Header.vue 中,使用一個 language 對象進行國際化管理。
computed: { // 定義國際化顯示 language() { return { foldAside: this.$t("header.foldAside"), unFoldAside: this.$t("header.unFoldAside"), setUp: this.$t("header.setUp"), help: this.$t("header.help"), blogAddress: this.$t("header.blogAddress"), codeAddress: this.$t("header.codeAddress"), userSetUp: this.$t("header.userSetUp"), updatePassword: this.$t("header.updatePassword"), logOut: this.$t("header.logOut") } } }

替換后的 Header.vue 如下,其余頁面修改類似修改即可。
<template>
<div class="header">
<!-- 是否展開側邊欄 -->
<div class="header-title" @click="foldOrOpen">
<a class="el-icon-s-fold" v-if="foldAside" :title="language.foldAside" />
<a class="el-icon-s-unfold" v-else :title="language.unFoldAside" />
</div>
<!-- 設置、文檔、用戶設置等 -->
<div class="header-menu">
<el-menu mode="horizontal" class="header-menu-submenu">
<!-- 設置 -->
<el-menu-item :title="language.setUp" index="1" @click="showSetup">
<i class="el-icon-setting"></i>{{language.setUp}}
</el-menu-item>
<!-- 幫助文檔 -->
<el-submenu :title="language.help" index="2">
<template slot="title">
<i class="el-icon-info"></i>{{language.help}}
</template>
<el-menu-item index="2-1">
<a href="https://www.cnblogs.com/l-y-h/" target="_blank" class="header-submenu-a">{{language.blogAddress}}</a>
</el-menu-item>
<el-menu-item index="2-2">
<a href="https://github.com/lyh-man/admin-vue-template.git" target="_blank" class="header-submenu-a">{{language.codeAddress}}</a>
</el-menu-item>
</el-submenu>
<!-- 用戶設置 -->
<el-submenu :title="language.userSetUp" index="3">
<template slot="title">
<span class="header-span">
<img src="~@/assets/avatar.gif" :alt="userName"> {{ userName }}
</span>
</template>
<el-menu-item index="3-1" @click="showPasswordBox">
<i class="el-icon-edit"></i>{{language.updatePassword}}
</el-menu-item>
<el-menu-item index="3-2" @click="logout">
<i class="el-icon-close"></i>{{language.logOut}}
</el-menu-item>
</el-submenu>
</el-menu>
</div>
<!-- 密碼修改框 -->
<UpdatePassword v-if="updatePasswordVisible" ref="updatePassowrd"></UpdatePassword>
<!-- 設置框 -->
<Setup v-if="setUpVisible" ref="setUp"></Setup>
</div>
</template>
<script>
import UpdatePassword from '@/views/home/UpdatePassword.vue'
import Setup from '@/views/home/Setup.vue'
import {mapState} from 'vuex'
export default {
name: 'Header',
data() {
return {
// 是否展開側邊欄
foldAside: true,
// 默認用戶名
// userName: 'admin',
// 是否展開密碼框
updatePasswordVisible: false,
// 是否展開設置框
setUpVisible: false
}
},
computed: {
// ...mapState('user', {userName: 'userName'}),
...mapState('user', ['userName']),
// 定義國際化顯示
language() {
return {
foldAside: this.$t("header.foldAside"),
unFoldAside: this.$t("header.unFoldAside"),
setUp: this.$t("header.setUp"),
help: this.$t("header.help"),
blogAddress: this.$t("header.blogAddress"),
codeAddress: this.$t("header.codeAddress"),
userSetUp: this.$t("header.userSetUp"),
updatePassword: this.$t("header.updatePassword"),
logOut: this.$t("header.logOut")
}
}
},
components: {
// 引入密碼框組件
UpdatePassword,
// 引入設置框組件
Setup
},
methods: {
// 展開設置框
showSetup() {
this.setUpVisible = true;
this.$nextTick(() => {
this.$refs.setUp.init()
})
},
// 展開密碼修改框
showPasswordBox() {
this.updatePasswordVisible = true
// this.$nextTick 表示數據渲染后,執行密碼框初始化
this.$nextTick(() => {
this.$refs.updatePassowrd.init()
})
},
// 展開、折疊側邊欄
foldOrOpen() {
this.foldAside = !this.foldAside
// this.$emit 用於觸發父組件的方法,並傳遞參數值
this.$emit("foldOrOpenAside", this.foldAside)
},
// 退出登錄,回到登錄界面
logout() {
// TODO:退出邏輯待完成
// alert("退出邏輯未完成");
this.$router.push({
name: "Login"
})
}
}
}
</script>
<style>
.header {
padding: 0 10px;
display: flex;
height: 50px;
line-height: 50px;
}
.header-title {
height: 50px;
width: 50px;
float: left;
font-size: 50px;
cursor: pointer;
}
.header-menu {
height: 50px;
width: 100%;
flex: 1;
line-height: 50px;
font-size: 30px;
}
.header-menu-submenu {
float: right;
}
.header-submenu-a {
text-decoration: none;
color: #4CC4B8;
font-weight: bold;
font-size: 16px;
}
.header-submenu-a:hover {
background-color: #2C3E50;
}
.el-menu--horizontal>.el-menu-item,
.el-menu--horizontal>.el-submenu .el-submenu__title {
height: 50px !important;
line-height: 50px !important;
}
.el-menu--collapse .el-menu .el-submenu, .el-menu--popup {
min-width: auto !important;
}
.header-span img {
width: 40px;
height: 40px;
line-height: 40px;
margin: 5px 10px 10px 10px;
}
.header-span {
font-size: 20px;
}
</style>
替換后的 Aside.vue 如下:
<template>
<div>
<!-- 系統 Logo -->
<el-aside class="header-logo" :width="asideWidth">
<div @click="$router.push({ name: 'Home' })">
<a v-if="foldAside">{{language.adminCenter}}</a>
<a v-else>{{language.admin}}</a>
</div>
</el-aside>
<el-aside class="aside" :width="asideWidth" :class='"icon-size-" + iconSize'>
<el-scrollbar style="height: 100%; width: 100%;">
<!--
default-active 表示當前選中的菜單,默認為 home。
collapse 表示是否折疊菜單,僅 mode 為 vertical(默認)可用。
collapseTransition 表示是否開啟折疊動畫,默認為 true。
background-color 表示背景顏色。
text-color 表示字體顏色。
-->
<el-menu :default-active="menuActiveName || 'home'" :collapse="!foldAside" :collapseTransition="false"
background-color="#263238" text-color="#8a979e">
<el-menu-item index="home" @click="$router.push({ name: 'Home' })">
<i class="el-icon-s-home"></i>
<span slot="title">{{language.homePage}}</span>
</el-menu-item>
<el-submenu index="demo">
<template slot="title">
<i class="el-icon-star-off"></i>
<span>demo</span>
</template>
<el-menu-item index="demo-echarts" @click="$router.push({ name: 'Echarts' })">
<i class="el-icon-s-data"></i>
<span slot="title">echarts</span>
</el-menu-item>
<el-menu-item index="demo-ueditor" @click="$router.push({ name: 'Ueditor' })">
<i class="el-icon-document"></i>
<span slot="title">ueditor</span>
</el-menu-item>
</el-submenu>
</el-menu>
</el-scrollbar>
</el-aside>
</div>
</template>
<script>
export default {
name: 'Aside',
props: ['foldAside'],
data() {
return {
// 保存當前選中的菜單
menuActiveName: 'home',
// 保存當前側邊欄的寬度
asideWidth: '200px',
// 用於拼接當前圖標的 class 樣式
iconSize: 'true'
}
},
computed: {
// 國際化
language() {
return {
adminCenter: this.$t("aside.adminCenter"),
admin: this.$t("aside.admin"),
homePage: this.$t("aside.homePage")
}
}
},
watch: {
// 監視是否折疊側邊欄,折疊則寬度為 64px。
foldAside(val) {
this.asideWidth = val ? '200px' : '64px'
this.iconSize = val
}
}
}
</script>
<style>
.aside {
margin-bottom: 0;
height: 100%;
max-height: calc(100% - 50px);
width: 100%;
max-width: 200px;
background-color: #263238;
text-align: left;
right: 0;
}
.header-logo {
background-color: #17b3a3;
text-align: center;
height: 50px;
line-height: 50px;
width: 200px;
font-size: 24px;
color: #fff;
font-weight: bold;
margin-bottom: 0;
cursor: pointer;
}
.el-submenu .el-menu-item {
max-width: 200px !important;
}
.el-scrollbar__wrap {
overflow-x: hidden !important;
}
.icon-size-false i {
font-size: 30px !important;
}
.icon-size-true i {
font-size: 18px !important;
}
</style>
3、在主界面導航欄添加一個設置頁面 Setup.vue
(1)簡介
用於定義系統設置,比如設置語言。
(2)定義頁面內容:
用於之前引入了 vuex,可以使用 vuex 保存 language 的狀態。
添加一個 common.js 模塊,用於保存公共的狀態。
export default { // 開啟命名空間(防止各模塊間命名沖突),訪問時需要使用 模塊名 + 方法名 namespaced: true, // 管理數據(狀態) state: { // 用於保存語言設置(國際化),默認為中文 language: 'zh' }, // 更改 state(同步) mutations: { updateLanguage(state, data) { state.language = data } }, // 異步觸發 mutations actions: { updateLanguage({commit, state}, data) { commit("updateLanguage", data) } } }

將定義好的模塊在 vuex 的入口文件 index.js 中引入。

引入 vuex,通過 ...mapState 以及 ...mapActions 引入。
{{$t("")}} 進行國際化處理。
<template>
<el-dialog :title="setUp.setting" :visible.sync="visible" :append-to-body="true">
<el-row :gutter="20">
<el-col :span="7" :offset="2">
{{setUp.languageSettings}}
</el-col>
<el-col :span="4">
<el-radio v-model="language" label="zh">{{setUp.zh}}</el-radio>
</el-col>
<el-col :span="4">
<el-radio v-model="language" label="en">{{setUp.en}}</el-radio>
</el-col>
</el-row>
</el-dialog>
</template>
<script>
import {mapState, mapActions} from 'vuex'
export default {
name: 'setUp',
data() {
return {
visible: false,
language: 'en'
}
},
computed: {
...mapState('common', {lang: 'language'}),
setUp() {
return {
setting: this.$t("language.setting"),
languageSettings: this.$t("language.languageSettings"),
zh: this.$t("language.zh"),
en: this.$t("language.en")
}
}
},
methods: {
...mapActions('common', ['updateLanguage']),
// 初始化
init() {
this.visible = true
this.language = this.lang
this.$i18n.locale = this.lang
}
},
watch: {
language(val) {
this.updateLanguage(val)
this.$i18n.locale = val
}
}
}
</script>
<style>
</style>

(3)在 Header.vue 中引入,引入方式類似於 UpdatePassword.vue
大概步驟:
Step1:import 導入 vue 組件。
Step2:在 components 中聲明組件。
Step3:添加開關屬性,並指定方法觸發組件。



(4)頁面顯示如下:

4、在登錄界面添加一個語言選擇框
上面在主界面的導航欄上添加了一個語言設置選項,但是登錄界面沒有語言設置選項,看着有點別扭,給登錄界面加上一個語言選擇框,可以將數據傳向后台,控制后台的國際化。
(1)添加一個語言選擇框
<el-form-item>
<el-select v-model="dataForm.language" :placeholder="language" class="login-select">
<el-option :label="zh" value="zh"></el-option>
<el-option :label="en" value="en"></el-option>
</el-select>
</el-form-item>
【樣式顯示有些問題,稍作調整】
.el-scrollbar__wrap {
overflow-x: scroll !important;
}
.login-select {
left: -120px;
width: 120px;
}


(2)同樣引入 vuex 對語言進行管理(寫法類似於 Setup.vue)。
Step1:
通過 mapActions 引入 common 模塊中的方法:,並定義方法進行操作。
import { mapActions } from 'vuex'
...mapActions('common', {updateLang: "updateLanguage"})
updateLanguage() {
this.$i18n.locale = this.dataForm.language
this.updateLang(this.dataForm.language)
}

Step2:
定義觸發方法,通過 el-select 標簽的 change 事件可以觸發。
<el-form-item>
<el-select v-model="dataForm.language" :placeholder="language" class="login-select" @change="updateLanguage">
<el-option :label="zh" value="zh"></el-option>
<el-option :label="en" value="en"></el-option>
</el-select>
</el-form-item>

完整的 Login.vue:
<template>
<div class="login-wrapper">
<div class="login-content">
<div class="login-main">
<h2 class="login-main-title">{{language.title}}</h2>
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" status-icon>
<el-form-item prop="userName">
<el-input v-model="dataForm.userName" :placeholder="language.userName"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="dataForm.password" type="password" :placeholder="language.password"></el-input>
</el-form-item>
<el-form-item>
<el-select v-model="dataForm.language" :placeholder="language.language" class="login-select" @change="updateLanguage">
<el-option :label="language.zh" value="zh"></el-option>
<el-option :label="language.en" value="en"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button class="login-btn-submit" type="primary" @click="dataFormSubmit()">{{language.signIn}}</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script>
import {
mapState,mapActions
} from 'vuex'
export default {
data() {
return {
dataForm: {
userName: '',
password: '',
language: 'zh'
},
dataRule: {
userName: [{
required: true,
message: this.$t("login.userNameNotNull"),
trigger: 'blur'
}],
password: [{
required: true,
message: this.$t("login.passwordNotNull"),
trigger: 'blur'
}]
}
}
},
computed: {
// 國際化
language() {
return {
title: this.$t("login.title"),
userName: this.$t("login.userName"),
password: this.$t("login.password"),
language: this.$t("login.language"),
zh: this.$t("language.zh"),
en: this.$t("language.en"),
signIn: this.$t("login.signIn")
}
}
},
methods: {
...mapActions('user', ['updateName']),
...mapActions('common', {updateLang: "updateLanguage"}),
// 提交表單
dataFormSubmit() {
// TODO:登錄代碼邏輯待完善
// alert("登錄代碼邏輯未完善")
this.$http({
url: '/auth/token',
method: 'get'
}).then(response => {
this.$message({
message: this.$t("login.signInSuccess"),
type: 'success'
})
this.updateName(this.dataForm.userName)
console.log(response)
this.$router.push({
name: 'Home'
})
})
},
updateLanguage() {
this.$i18n.locale = this.dataForm.language
this.updateLang(this.dataForm.language)
}
},
created() {
// 頁面創建時,獲取當前系統語言,並顯示在下拉框中
this.dataForm.language = this.$i18n.locale
}
}
</script>
<style>
.login-wrapper {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
background-color: rgba(38, 50, 56, .6);
background: url(~@/assets/login_bg.jpg) no-repeat;
background-size: 100% 100%;
}
.login-content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
height: 350px;
width: 400px;
background-color: #112234;
opacity: .8;
}
.login-main {
color: beige;
padding: 20px 20px 10px 20px;
}
.el-scrollbar__wrap {
overflow-x: scroll !important;
}
.login-select {
left: -120px;
width: 120px;
}
</style>
(3)頁面顯示

