vue + ElementUI 搭建後臺管理系統記錄
本文件記錄了該系統從零配置的完整過程
專案原始碼請訪問:https://gitee.com/szxio/vue2Admin,如果感覺對你有幫助,請點一個小星星,O(∩_∩)O
新建專案
vue create vueadmin
安裝 less-loader
安裝
這裡是一個小坑,安裝 less-loader
時推薦安裝指定版本,如果安裝預設高版本會導致專案出錯
cnpm i less-loader@6.0.0 -D
使用
<style lang="less" scoped>
div{
b{
span{
color: red;
}
}
}
</style>
引入 ElementUI
安裝
cnpm i element-ui -S
配置
import Vue from 'vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue'
Vue.use(ElementUI);
Vue.config.productionTip = false
new Vue({
render: h => h(App)
}).$mount('#app')
使用
<template>
<div>
<el-row>
<el-button>預設按鈕</el-button>
<el-button type="primary">主要按鈕</el-button>
<el-button type="success">成功按鈕</el-button>
<el-button type="info">資訊按鈕</el-button>
<el-button type="warning">警告按鈕</el-button>
<el-button type="danger">危險按鈕</el-button>
</el-row>
</div>
</template>
配置 VueRouter
npm安裝
- 安裝
npm install vue-router
- 新建
scr/router/index.js
,並新增如下程式碼
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "首頁",
component: () => import("../views/Home.vue"),
},
{
path: "/about",
name: "About",
component: () => import("../views/About.vue"),
},
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
});
// 前置路由攔截器
router.beforeEach((to, from, next) => {
// 設定當前頁簽名稱
document.title = to.name;
next();
});
export default router;
配置前置路由攔截器動態設定每個頁面的瀏覽器頁簽名稱
- 修改
main.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import router from './router'
new Vue({
router,
render: h => h(App)
}).$mount('#app')
- 修改
App.vue
<template>
<div id="app">
<router-view />
</div>
</template>
- 重啟專案,分別訪問如下地址可以檢視頁面效果
VueCli安裝
如果專案是使用vue-cli建立的,則可以使用下面的命令直接生成上述程式碼及兩個示例路由。它也會覆蓋你的 App.vue
,因此請確保在專案中執行以下命令之前備份這個檔案
vue add router
動態生成左側選單
新增layout元件
- 修改路由檔案
首先我們要建立好 router
路由,修改 src\router\index.js
檔案
import Vue from "vue";
import VueRouter from "vue-router";
import Layouts from "../layouts";
Vue.use(VueRouter);
const routes = [
{
path: "",
redirect: "home",
component: Layouts,
children: [
{
path: "/home",
meta: { title: "首頁", icon: "el-icon-s-home" },
component: () => import("../views/home"),
},
{
path: "system",
meta: { title: "系統管理", icon: "el-icon-s-home" },
component: Layouts,
children: [
{
path: "item1",
meta: { title: "使用者管理", icon: "el-icon-s-home" },
component: () => import("../views/system/item1"),
},
{
path: "item2",
meta: { title: "產品管理", icon: "el-icon-s-home" },
component: () => import("../views/system/item2"),
},
],
},
],
},
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
});
// 前置路由攔截器
router.beforeEach((to, from, next) => {
// 設定當前頁簽名稱
document.title = to.meta.title;
next();
});
export default router;
程式碼說明:
- path:路由地址
- redirect:重定向到指定路由
- component:頁面對應的元件
- children:設定子路由,二級選單
- meta:頁面的補充,用來宣告頁面的名稱和圖示等
我們將所有的頁面都放在根路由的 children 下面,如果下面的選單沒有配置 children 屬性,則表示該選單是一級選單,如果設定了則表示二級選單,可以多級巢狀。上面的路由對應的修改views
資料夾下的檔案結構:
- 新建
src\layouts\index.vue
這個檔案用來配置專案頁面的外殼,左側的選單和頂部的麵包屑都會在該資料夾中
頁面結構分成三大部分:
- 左側選單
- 頂部麵包屑
- 內容展示區域
對應成程式碼結構如下
<template>
<div>
<div>左側選單</div>
<div>
<div>頭部麵包屑</div>
<div>內容展示區</div>
</div>
</div>
</template>
我們既然要將頁面在內容展示區顯示,所以我們對應的建立專門用來展示頁面的元件。
所以接下來新建 src\layouts\components\AppContent.vue
元件。元件程式碼如下
<template>
<div>
<router-view/>
</div>
</template>
沒有看錯,很簡單,只要放置一個 router-view
標籤即可。然後將 AppContent
元件註冊到 layouts\index.vue
中
<template>
<div>
<div>左側選單</div>
<div>
<div>頭部麵包屑</div>
<div>
<AppContent />
</div>
</div>
</div>
</template>
<script>
import AppContent from "./components/AppContent.vue";
export default {
components: {
AppContent,
},
};
</script>
- 修改
App.vue
只保留 router-view
<template>
<div>
<router-view/>
</div>
</template>
現在我們開啟頁面看到如下效果
修改頁面樣式
我們首頁雖然已經展示到了 appcontent
元件中,但是樣式並不是我們想要的效果。現在去修改src\layouts\index.vue
檔案,新增如下程式碼
<template>
<div>
<!-- 左側選單 -->
<div class="left-menu-main">左側選單</div>
<!-- 右側操作區域 -->
<div class="right-view-main">
<!-- 頭部麵包屑 -->
<div class="header">頭部麵包屑</div>
<!-- 內容展示區 -->
<AppContent class="content" />
</div>
</div>
</template>
<script>
import AppContent from "./components/AppContent.vue";
export default {
components: {
AppContent,
}
};
</script>
<style scoped>
.left-menu-main {
transition: width 0.28s;
width: 210px !important;
background-color: #304156;
height: 100%;
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 1001;
overflow: auto;
}
.header {
position: fixed;
height: 50px;
width: calc(100vw - 210px);
top: 0;
right: 0;
display: flex;
align-items: center;
border-bottom: 1px solid #ddd;
padding-left: 15px;
box-sizing: border-box;
}
.content {
position: fixed;
overflow: auto;
height: calc(100vh - 50px);
width: calc(100vw - 210px);
bottom: 0;
right: 0;
}
</style>
效果展示
引入左側選單
- 新建
src\layouts\components\ElMenu\index.vue
元件,初始化程式碼
<template>
<div>
<el-menu
default-active="2"
class="el-menu-vertical-demo"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
>
<!-- 可展開選單 -->
<el-submenu index="1">
<template slot="title">
<i class="el-icon-location"></i>
<span>導航一</span>
</template>
<el-menu-item index="3">
<i class="el-icon-document"></i>
<span slot="title">導航三</span>
</el-menu-item>
</el-submenu>
<!-- 點選選單 -->
<el-menu-item index="2">
<i class="el-icon-menu"></i>
<span slot="title">導航二</span>
</el-menu-item>
</el-menu>
</div>
</template>
- 註冊
ElMenu
元件新增到src\layouts\index.vue
中
<template>
<div>
<!-- 左側選單 -->
<ElMenu class="left-menu-main"/>
<!-- 右側操作區域 -->
<div class="right-view-main">
<!-- 頭部麵包屑 -->
<div class="header">頭部麵包屑</div>
<!-- 內容展示區 -->
<AppContent class="content" />
</div>
</div>
</template>
<script>
import AppContent from "./components/AppContent.vue";
import ElMenu from "./components/ElMenu/index.vue";
export default {
components: {
AppContent,
ElMenu,
},
};
</script>
<style lang="less" scoped>
...和上面一樣,這裡省略
</style>
- 此時開啟頁面可以看到左側選單
遞迴選單元件
目前我們看到的只是一個寫死的選單,我們想要的是根據 router
檔案自動生成對應的選單,那麼應該怎麼做呢?
首先左側選單的每一項都可以當做一個元件,然後獲取到 router
中的所有選單,迴圈展示每一項選單即可,那麼就開始做吧!
新建 src\layouts\components\ElMenu\MenuItem.vue
元件,用來展示每一項的選單名稱
修改 src\layouts\components\ElMenu\index.vue
頁面,引入 router.js
獲取定義的路由資料,並且引入 MenuItem
元件去迴圈展示每一項選單
<template>
<div>
<el-menu
:default-active="$route.path"
class="el-menu-vertical-demo"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
>
<MenuItem
v-for="(route, index) in routersList"
:key="index"
:item="route"
:fatherPath="route.path"
></MenuItem>
</el-menu>
</div>
</template>
<script>
import routers from "../../../router";
import MenuItem from "./MenuItem.vue";
export default {
components: {
MenuItem,
},
data() {
return {
routersList: [],
};
},
mounted() {
// 獲取所有定義的一級選單和多級選單
this.routersList = routers.options.routes[0].children;
}
};
</script>
程式碼說明:
在el-menu
標籤中我們定義了 :default-active="$route.path"
,這個含義表示預設選中當前路由選單,如果是子級選單會自動展開並選中,這是因為下面的程式碼中我們會將每一個頁面的 path
作為選單的 index
。
另外程式碼中我們遍歷 MenuItem
元件時傳遞了每個選單的物件 item
和每個選單的路徑 fatherPaht
,現在我們要到 MenuItem
元件去根據這個兩個屬性做遞迴展示根選單和多級選單結構。來到 MenuItem
元件中,編寫如下程式碼
<template>
<div>
<!-- 根選單 -->
<router-link tag="span" :to="resolvePath()" v-if="!item.children">
<el-menu-item :index="resolvePath()">
<i :class="item.meta.icon"></i>
<span slot="title">{{ item.meta.title }}</span>
</el-menu-item>
</router-link>
<!-- 可展開選單 -->
<el-submenu :index="resolvePath()" v-else>
<template slot="title">
<i :class="item.meta.icon"></i>
<span slot="title">{{ item.meta.title }}</span>
</template>
<!-- 這裡遞迴去展示多級選單 -->
<menu-item
v-for="(route, index) in item.children"
:key="index"
:item="route"
:fatherPath="resolvePath(route.path)"
>
</menu-item>
</el-submenu>
</div>
</template>
<script>
// 引入path用來處理路徑
import path from "path";
export default {
// 做元件遞迴時必須定義一個name。然後遞迴時的元件名就是這裡的name值
name: "MenuItem",
props: {
// 上一級的路由資訊
item: {
type: Object,
default: null,
},
// 上一級的路徑
fatherPath: {
type: String,
default: "",
},
},
data() {
return {};
},
methods: {
resolvePath(routePath = "") {
return path.resolve(this.fatherPath, routePath);
},
},
};
</script>
程式碼說明
- 在做元件遞迴時,必須要在遞迴元件內宣告
name
屬性,屬性值就是當前元件的名稱,這樣才能實現多級巢狀迴圈效果。
另外在ElementUI 中的選單分成兩種型別,分別如下
<el-menu-item index="4">
<i class="el-icon-setting"></i>
<span slot="title">導航四</span>
</el-menu-item>
- 這種的表示根選單
<el-submenu index="1">
<template slot="title">
<i class="el-icon-location"></i>
<span>導航一</span>
</template>
<el-menu-item index="4">
<i class="el-icon-setting"></i>
<span slot="title">導航四</span>
</el-menu-item>
</el-submenu>
- 這種的表示一個可展開的選單,我們根據路由有沒有
children
來判斷這個選單是否有子選單
在根選單外層新增了一個 router-link
實現了點選選單跳轉到不同頁面
現在我們來檢視效果
選單上出現了一個根選單和一個二級選單
新增路由自動完成選單巢狀
現在我們已經完成了一個二級選單的展示,那麼我們新增一個三級路由會不會自動出現三級選單呢?
首先新建一個測試頁面,在資料夾 item2
下面新建一個 item2-1
,並且在裡面新增一個 index.vue
檔案,如下圖:
然後去 src\router\index.js
新增這個頁面的路由
新增完成後可以發現產品管理選單自動變成了一個可展開的選單,展開后里面有一個類別列表選單
新增頭部麵包屑
基礎用法
我們想要在頭部新增一個如下的效果,可以很清晰的知道當前瀏覽的是哪個頁面
- 在
layouts
資料夾新增HeaderNav
元件,元件地址:src\layouts\components\HeaderNav.vue
,新增如下初始程式碼
<template>
<div>
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/' }">首頁</el-breadcrumb-item>
<el-breadcrumb-item><a href="/">活動管理</a></el-breadcrumb-item>
<el-breadcrumb-item>活動列表</el-breadcrumb-item>
<el-breadcrumb-item>活動詳情</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
- 然後再
src\layouts\index.vue
檔案中引入HeaderNav
元件
<template>
<div>
<template>
<!-- 左側選單 -->
<ElMenu class="left-menu-main" />
<!-- 右側操作區域 -->
<div class="right-view-main">
<!-- 頭部麵包屑 -->
<HeaderNav class="header" />
<!-- 內容展示區 -->
<AppContent class="content" />
</div>
</template>
</div>
</template>
<script>
import AppContent from "./components/AppContent.vue";
import ElMenu from "./components/ElMenu/index.vue";
import HeaderNav from "./components/HeaderNav.vue";
export default {
components: {
AppContent,
ElMenu,
HeaderNav,
}
};
</script>
<style lang="less" scoped>
樣式省略。。。
</style>
- 此時我們的頁面效果是這樣的
是不是有點感覺了呢
- 接下只需要監聽頁面的變化去實時獲取最新的路由資訊即可,然後迴圈遍歷顯示
實現程式碼:
<template>
<div>
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item
v-for="(route, index) in breadcrumbItems"
:key="index"
>
<i :class="route.icon"></i>
<span>{{ route.title }}</span>
</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<script>
export default {
data() {
return {
breadcrumbItems: [],
};
},
mounted() {
this.geBreadcrumbItems(this.$route);
},
methods: {
geBreadcrumbItems(route) {
// 獲取當前頁面的路由組
this.breadcrumbItems = route.matched;
// 從下標為1的位置開始獲取路由,去除了最外層定義的根路由資訊,並且獲取到的陣列裡面只有meta資料,方便我們取值
this.breadcrumbItems = this.breadcrumbItems
.map((item) => item.meta)
.splice(1);
},
},
watch: {
$route: function (newVal) {
this.geBreadcrumbItems(newVal);
},
},
};
</script>
效果展示
新增首頁快速入口
我們已經實現了基本效果,但是我們還想在麵包屑的首位新增首頁的連線,點選首頁文字快速跳轉到到首頁
修改 src\layouts\components\HeaderNav.vue
程式碼為如下
<template>
<div>
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item
v-for="(route, index) in breadcrumbItems"
:key="index"
>
<!-- 判斷面包屑是否有path屬性,如果有則顯示router-link標籤 -->
<router-link v-if="route.path" :to="route.path">
<i :class="route.icon"></i>
<span>{{ route.title }}</span>
</router-link>
<!-- 如果沒有path屬性則不跳轉 -->
<template v-else>
<i :class="route.icon"></i>
<span>{{ route.title }}</span>
</template>
</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<script>
export default {
data() {
return {
breadcrumbItems: [],
};
},
mounted() {
this.geBreadcrumbItems(this.$route);
},
methods: {
geBreadcrumbItems(route) {
// 獲取當前頁面的路由組
this.breadcrumbItems = route.matched;
// 從下標為1的位置開始獲取路由,去除了最外層定義的根路由資訊,並且獲取到的陣列裡面只有meta資料,方便我們取值
this.breadcrumbItems = this.breadcrumbItems
.map((item) => item.meta)
.splice(1);
// 判斷當前頁面是否已經是首頁
let nowPath = route.path;
// 如果當前頁面不是首頁,則在麵包屑的首位置新增一個首頁連結
if (nowPath !== "/home") {
this.breadcrumbItems.unshift({
title: "首頁",
icon: "el-icon-s-home",
path: "/home",
});
}
},
},
watch: {
$route: function (newVal) {
this.geBreadcrumbItems(newVal);
},
},
};
</script>
修改之後頁面效果是當我們進入非首頁頁面時,麵包屑前面會有一個首頁的快速入口,當進入首頁時不會展示首頁連線
個性化選單配置
配置獨立頁面
現在我們看到的頁面都巢狀在左側選單和麵包屑下面,但是有些頁面時不能在這個巢狀頁面的,例如登入頁面。那麼我們怎麼通過配置路由來實現這樣的效果呢?
首先新增登入頁面,新建 src\views\login\index.vue
,編寫如下程式碼
<template>
<div>
登入頁面
</div>
</template>
新增完登入頁面後前往 src\router\index.js
檔案新增路由資訊,如下圖
我們在登入頁面的路由資訊中的增加一個 oneself:true
的標識,用來標識這個頁面時獨自開啟的,不需要巢狀在選單下
新增完路由後找到 src\layouts\index.vue
頁面修改為如下程式碼
<template>
<div>
<!-- 如果不是獨自頁面則讓頁面巢狀在選單下 -->
<template v-if="!isOneself">
<!-- 左側選單 -->
<ElMenu class="left-menu-main" />
<!-- 右側操作區域 -->
<div class="right-view-main">
<!-- 頭部麵包屑 -->
<HeaderNav class="header" />
<!-- 內容展示區 -->
<AppContent class="content" />
</div>
</template>
<!-- 如果是獨自開啟頁面,則只展示純頁面 -->
<div v-else>
<AppContent />
</div>
</div>
</template>
<script>
import AppContent from "./components/AppContent.vue";
import ElMenu from "./components/ElMenu/index.vue";
import HeaderNav from "./components/HeaderNav.vue";
export default {
components: {
AppContent,
ElMenu,
HeaderNav,
},
data() {
return {
isOneself: false,
};
},
mounted() {
// 獲取當前路由是否是獨自開啟的
this.isOneself = this.$route.meta.oneself;
},
watch: {
// 監聽路由變化,實時獲取路由資訊
$route: function (newVal) {
this.isOneself = newVal.meta.oneself;
},
},
};
</script>
<style lang="less" scoped>
css省略。。。
</style>
修改完成後檢視頁面效果
效果很明顯,點選了登入後左側的選單和麵包屑都沒有了,瀏覽器只會展示登入頁面資訊。
到這裡我們會發現登入頁面作為了一個選單項顯示到了左側選單中,這個問題怎麼解決呢?
配置隱藏選單
找到 src\router\index.js
檔案,為登入頁面新增一個 hide:true
,如下圖,這個屬性用來表示這個頁面不在左側選單中顯示
新增完成後找到 src\layouts\components\ElMenu\MenuItem.vue
檔案,在根標籤上新增一個 v-if
判斷,用來判斷當前選單是否需要被渲染
由於這個功能所新增的程式碼極少,所以就不貼程式碼了。修改完之後檢視頁面
通過動畫可以看到登入頁面已經不在選單中展示,修改頁面地址也會正常的在新頁面中開啟。
配置外部連線
現在我們配置的地址只能配置我們專案中的地址,那麼我需要點選選單直接開啟百度怎麼做呢?
首先新增路由資訊如下
此時我們點選選單並不能正常的開啟百度
這是因為我們並沒有判斷頁面的 path
型別。
接下來新建 src\layouts\components\ElMenu\MenuLink.vue
,編寫如下程式碼
下面程式碼的含義是定義了一個動態元件,根據父元件傳遞過來的路徑型別顯示不同的元件
<template>
<component :is="type" v-bind="linkProps(to)">
<slot />
</component>
</template>
<script>
export default {
props: {
// 接收從父元件傳遞過來的頁面地址
to: {
type: String,
required: true,
},
},
computed: {
isExternal() {
return /^(https?:|mailto:|tel:)/.test(this.to);
},
type() {
// 根據路徑判斷元件型別,如果是外部連線則用a標籤
if (this.isExternal) {
return "a";
}
// 如果不是外部連線則用router-link元件包裹
return "router-link";
},
},
methods: {
// 繫結元件屬性
linkProps(to) {
// 如果是外部連線則設定a標籤的href地址為傳遞過來的地址,並且設定在新標籤開啟
if (this.isExternal) {
return {
href: to,
target: "_blank",
style: {
"text-decoration": "none",
},
};
}
// 如果是內部地址則設定router-link的to屬性值,以及tag屬性值為span
return {
to: to,
tag: "span",
};
},
},
};
</script>
然後找到 src\layouts\components\ElMenu\MenuItem.vue
檔案,引入剛剛新建 MenuLink
元件
修改程式碼如下
<template>
<!-- 判斷當前頁面是否顯示,如果hide為true,則不渲染該選單 -->
<div v-if="!item.meta.hide">
<!-- 根選單 -->
<MenuLink :to="resolvePath()" v-if="!item.children">
<el-menu-item :index="resolvePath()">
<i :class="item.meta.icon"></i>
<span slot="title">{{ item.meta.title }}</span>
</el-menu-item>
</MenuLink>
<!-- 可展開選單 -->
<el-submenu :index="resolvePath()" v-else>
<template slot="title">
<i :class="item.meta.icon"></i>
<span slot="title">{{ item.meta.title }}</span>
</template>
<!-- 這裡遞迴去展示多級選單 -->
<menu-item
v-for="(route, index) in item.children"
:key="index"
:item="route"
:fatherPath="resolvePath(route.path)"
>
</menu-item>
</el-submenu>
</div>
</template>
<script>
// 引入path用來處理路徑
import path from "path";
import MenuLink from "./MenuLink.vue";
export default {
// 做元件遞迴時必須定義一個name。然後遞迴時的元件名就是這裡的name值
name: "MenuItem",
components: {
MenuLink,
},
props: {
// 上一級的路由資訊
item: {
type: Object,
default: null,
},
// 上一級的路徑
fatherPath: {
type: String,
default: "",
},
},
data() {
return {};
},
methods: {
// 判斷路徑是否是外部地址
isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path);
},
// 解析頁面地址
resolvePath(routePath = "") {
// 判斷當前頁面地址是否為外部地址
if (this.isExternal(routePath)) {
return routePath;
}
// 判斷從父元件傳遞過來的地址是否為外部地址
if (this.isExternal(this.fatherPath)) {
return this.fatherPath;
}
// 格式化頁面地址
return path.resolve(this.fatherPath, routePath);
},
},
};
</script>
圖片說明修改點
修改完成後檢視頁面效果
現在可以看到點選百度搜尋選單後在新頁籤開啟了百度。