SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(二):引入 element-ui 定义基本页面显示
前提:
(1) 相关博文地址:
SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(一):搭建基本环境:https://www.cnblogs.com/l-y-h/p/12930895.html
(2)代码地址:
https://github.com/lyh-man/admin-vue-template.git
一、定义公共组件页面
简单的页面效果如下所示:(做的比较粗糙,大致理解页面即可)

1、安装 element-ui
(1)简介
一款 ui 框架。使用 element-ui 用于实现页面的绘制。
【官网:】 https://element.eleme.cn/#/zh-CN 【文档:】 https://element.eleme.cn/#/zh-CN/component/installation
(2)安装
可以通过 npm 或者 cdn 方式使用,此处使用 npm 方式安装。
【安装方式一:(npm 安装)】 npm install element-ui 【安装方式二:(CDN 方式引入)】 <!-- 引入样式 --> <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> <!-- 引入组件库 --> <script src="https://unpkg.com/element-ui/lib/index.js"></script>

(3)引入 element-ui
在 main.js 中引入(也可以自定义一个 js,然后在 main.js 中引入自定义的 js)。
// 引入 element-ui import ElementUI from 'element-ui' // 引入 element-ui 的 css 文件 import 'element-ui/lib/theme-chalk/index.css'; // 声明使用 element-ui Vue.use(ElementUI);

2、修改 App.vue
(1)简介
页面主入口。
通过 router 将组件 显示在 router-view 标签处。(基本路由规则到本文末尾可以看)

(2)修改页面内容
<template>
<div id="app">
<router-view/>
</div>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
/* 解决子组件中 height: 100% 不生效问题 */
html,body,#app{
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
3、404.vue
(1)简介
定义错误页面。
当错误发生时,用于跳转到 404 页面。

(2)定义页面内容
<template>
<div class="error-wrapper">
<h2 class="not-found-title">404</h2>
<p class="not-found-desc">抱歉!您访问的页面<em>失联</em>啦 ...</p>
<el-button @click="$router.go(-1)">返回上一页</el-button>
<el-button type="primary" class="not-found-btn-gohome" @click="$router.push({ name: 'Home' })">进入首页</el-button>
</div>
</template>
<script>
export default {}
</script>
<style>
.error-wrapper {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
}
.not-found-title {
margin: 20px 0 15px;
font-size: 10em;
font-weight: 400;
color: rgb(55, 71, 79);
}
.not-found-desc {
margin: 0 0 30px;
font-size: 26px;
color: rgb(118, 131, 143);
}
.not-found-desc>em {
font-style: normal;
color: #ee8145;
}
.not-found-btn-gohome {
margin-left: 30px;
}
</style>
(3)页面显示如下:

4、Login.vue
(1)简介
定义登陆页面。
访问系统时,用于跳转到登录界面。

背景图(来源于网络):

(2)定义页面内容:
<template>
<div class="login-wrapper">
<div class="login-content">
<div class="login-main">
<h2 class="login-main-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="帐号"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="dataForm.password" type="password" placeholder="密码"></el-input>
</el-form-item>
<el-form-item>
<el-button class="login-btn-submit" type="primary" @click="dataFormSubmit()">登录</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
dataForm: {
userName: '',
password: ''
},
dataRule: {
userName: [{
required: true,
message: '帐号不能为空',
trigger: 'blur'
}],
password: [{
required: true,
message: '密码不能为空',
trigger: 'blur'
}]
}
}
},
methods: {
// 提交表单
dataFormSubmit() {
// TODO:登录代码逻辑待完善
alert("登录代码逻辑未完善")
this.$router.replace({name: 'Home'})
}
}
}
</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: 300px;
width: 400px;
background-color: #112234;
opacity: .8;
}
.login-main {
color: beige;
padding: 20px 20px 10px 20px;
}
</style>
(3)页面显示如下:

二、定义主页面
主页面 可以拆分成多个组件,每个组件负责一部分页面的显示。
拆分成 Header、Aside、Content 三个页面。
其中:
Header 用于定义导航栏信息
Aside 用于定义菜单栏信息
Content 用于显示各个菜单选项的页面
1、Home.vue
(1)简介
定义主界面。
通过 Login 登录系统后,需要跳转到 主页面。

(2)定义页面内容
<template>
<!--
v-loading、element-loading-text、element-loading-background、element-loading-spinner 用于定义加载的样式
-->
<el-container class="container" v-loading="false" element-loading-text="拼命加载中" element-loading-background="rgba(0, 0, 0, 0.8)"
element-loading-spinner="el-icon-loading">
<!-- 侧边栏 -->
<Aside :foldAside="foldAside" />
<!--
direction="vertical" 用于垂直布局
-->
<el-container direction="vertical">
<!-- 头部导航栏 -->
<Header @foldOrOpenAside="foldOrOpen" />
<!-- 内容 -->
<Content />
</el-container>
</el-container>
</template>
<script>
import Header from '@/views/home/Header.vue'
import Aside from '@/views/home/Aside.vue'
import Content from '@/views/home/Content.vue'
export default {
name: 'Home',
components: {
Header,
Aside,
Content
},
data() {
return {
foldAside: true
}
},
methods: {
foldOrOpen(data) {
this.foldAside = data
}
}
}
</script>
<style>
.container {
height: 100%;
}
</style>
(3)页面显示如下:

2、Header.vue
(1)简介
用于定义主页面的导航栏。
通过导航栏,可以进行一些操作。比如:折叠侧边栏、修改密码、退出登录等。

(2)定义页面内容
<template>
<div class="header">
<!-- 是否展开侧边栏 -->
<div class="header-title" @click="foldOrOpen">
<a class="el-icon-s-fold" v-if="foldAside" title="折叠侧边栏" />
<a class="el-icon-s-unfold" v-else title="展开侧边栏" />
</div>
<!-- 设置、文档、用户设置等 -->
<div class="header-menu">
<el-menu mode="horizontal" class="header-menu-submenu">
<!-- 设置 -->
<el-menu-item title="设置" index="1">
<i class="el-icon-setting"></i>设置
</el-menu-item>
<!-- 帮助文档 -->
<el-submenu title="帮助" index="2">
<template slot="title">
<i class="el-icon-info"></i>帮助
</template>
<el-menu-item index="2-1">
<a href="https://www.cnblogs.com/l-y-h/" target="_blank" class="header-submenu-a">博客地址</a>
</el-menu-item>
<el-menu-item index="2-2">
<a href="https://www.cnblogs.com/l-y-h/" target="_blank" class="header-submenu-a">代码地址</a>
</el-menu-item>
</el-submenu>
<!-- 用户设置 -->
<el-submenu title="用户设置" 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>修改密码
</el-menu-item>
<el-menu-item index="3-2" @click="logout">
<i class="el-icon-close"></i>退出
</el-menu-item>
</el-submenu>
</el-menu>
</div>
<!-- 密码修改框 -->
<UpdatePassword v-if="UpdatePasswordVisible" ref="updatePassowrd"></UpdatePassword>
</div>
</template>
<script>
import UpdatePassword from '@/views/home/UpdatePassword.vue'
export default {
name: 'Header',
data() {
return {
// 是否展开侧边栏
foldAside: true,
// 默认用户名
userName: 'admin',
// 是否展开密码框
UpdatePasswordVisible: false
}
},
components: {
// 引入密码框组件
UpdatePassword
},
methods: {
// 展开密码修改框
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>
(3)页面显示如下

3、UpdatePassword.vue
(1)简介
定义密码修改框,用于修改用户密码。

(2)定义页面内容
<template>
<el-dialog title="修改密码" :visible.sync="visible" :append-to-body="true">
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="100px">
<el-form-item label="账号">
<span>{{ userName }}</span>
</el-form-item>
<el-form-item label="原密码" prop="password">
<el-input type="password" v-model="dataForm.password" placeholder="原密码"></el-input>
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input type="password" v-model="dataForm.newPassword" placeholder="新密码"></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input type="password" v-model="dataForm.confirmPassword" placeholder="确认密码"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
</span>
</el-dialog>
</template>
<script>
export default {
data() {
return {
userName: 'admin',
visible: false,
dataForm: {
password: '',
newPassword: '',
confirmPassword: ''
},
dataRule: {
password: [{
required: true,
message: '原密码不能为空',
trigger: 'blur'
}],
newPassword: [{
required: true,
message: '新密码不能为空',
trigger: 'blur'
}],
confirmPassword: [{
required: true,
message: '确认密码不能为空',
trigger: 'blur'
}]
}
}
},
methods: {
// 初始化
init() {
this.visible = true
// 初始化清空表单内容
this.$nextTick(() => {
this.$refs['dataForm'].resetFields()
})
},
// 表单提交,回到登录界面
dataFormSubmit() {
// TODO: 表达提交逻辑待完成
alert("表达提交逻辑未完成")
this.visible = false;
this.$nextTick(() => {
this.$router.push({
name: "Login"
})
})
}
}
}
</script>
(3)页面显示如下

4、this.$nextTick 与 this.$emit 简单介绍
(1)this.$nextTick
其用于数据渲染之后执行。
比如:
修改了某个数据,这个数据需要 dom 更新之后才会显示出来,此时就可以使用 this.$nextTick。其传递一个回调函数,在数据渲染之后执行。
在 Header.vue 中,就使用到了这个。如下所示:
密码修改框通过 UpdatePasswordVisible 来控制是否显示。
showPasswordBox() 方法被执行时,UpdatePassword 组件开始加载,数据渲染完成后触发 this.$nextTick 的回调函数,进行密码框的初始化。
<!-- 密码修改框 --> <UpdatePassword v-if="UpdatePasswordVisible" ref="updatePassowrd"></UpdatePassword> // 展开密码修改框 showPasswordBox() { this.UpdatePasswordVisible = true // this.$nextTick 表示数据渲染后,执行密码框初始化 this.$nextTick(() => { this.$refs.updatePassowrd.init() }) },

若立即使用 this.$refs.updatePassowrd.init() 调用 UpdatePassword 的 init 方法,会报错,因为此时的 UpdatePassword 数据还未渲染,若想成功执行,需要使用 this.$nextTick,表示在数据渲染成功后执行。
// 展开密码修改框 showPasswordBox() { this.UpdatePasswordVisible = true this.$refs.updatePassowrd.init() },

(2)this.$emit
用于子组件向父组件传递数据,并触发父组件的方法。
在Home.vue 与 Header.vue 中,就使用到了这个。如下所示:
在 Home 组件里引入了 Header 组件,并定义了 @foldOrOpenAside 方法,
在 Header 组件里通过 this.$emit 调用 foldOrOpenAside 方法,并传递数据(可选)。
【Home.vue】 <Header @foldOrOpenAside="foldOrOpen" /> foldOrOpen(data) { this.foldAside = data } 【Header.vue】 <div class="header-title" @click="foldOrOpen"></div> // 展开、折叠侧边栏 foldOrOpen() { this.foldAside = !this.foldAside // this.$emit 用于触发父组件的方法,并传递参数值 this.$emit("foldOrOpenAside", this.foldAside) },

5、Aside.vue
(1)简介
用于定义侧边栏,显示菜单。

(2)定义页面内容
<template>
<div>
<!-- 系统 Logo -->
<el-aside class="header-logo" :width="asideWidth">
<div @click="$router.push({ name: 'Home' })">
<a v-if="foldAside">后台管理中心</a>
<a v-else>后台</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">首页</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'
}
},
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)页面显示如下

6、props 简单介绍
其用于父组件向子组件传递数据。
在 Home.vue 和 Aside.vue 中,就使用到了这个。如下所示:
在 Home.vue 中引入 Aside.vue 组件,并定义了 :foldAside 属性。
在 Aside.vue 组件中,使用 props 可以获取到 这个 属性。
【Home.vue】 <!-- 侧边栏 --> <Aside :foldAside="foldAside" /> 【Aside.vue】 props: ['foldAside'],

7、Content.vue
(1)简介
用于定义各个菜单点击后的页面显示。
同样需要使用 router 进行页面跳转(嵌套路由,通过 children 中的规则定义跳转路径),基本路由规则本文最后有介绍。

(2)定义页面内容
<template>
<el-main class="content">
<el-card class="card" shadow="hover">
<keep-alive>
<router-view />
</keep-alive>
</el-card>
</el-main>
</template>
<script>
export default {
name: 'Content'
}
</script>
<style>
.content {
background-color: #f1f4f5;
}
.card {
height: 100%;
}
</style>
(3)页面显示如下

8、定义 content 显示页面(仅供测试)
【Ueditor.vue】 <template> <div> <h1>Ueditor</h1> </div> </template> <script> </script> <style> </style> 【HomePage.vue】 <template> <div style="height: 800px;"> <h1>HomePage</h1> </div> </template> <script> </script> <style> </style> 【Echarts.vue】 <template> <div> <h1>Echarts</h1> </div> </template> <script> </script> <style> </style>
点击 Aside 中各个菜单,在 Content 会显示不同的 页面。


9、基本路由问题
(1)简介
想要各个组件页面间能够顺利跳转,就需要 router 来操作了。
此处简单写了路由跳转规则,后续会进行修改。
(2)定义路由跳转规则
path 指的是 路径。
redirect 指的是 需要跳转的路径。
name 指的是 路由的名字(此项目中,均使用 name 进行路由跳转)。
component 指的是 路由的组件,用于显示页面(<router-view /> 会加载组件)。
children 指的是 子路由(路由中显示另一个路由)。
import Vue from 'vue' import VueRouter from 'vue-router' import Home from '../views/Home.vue' Vue.use(VueRouter) const routes = [{ path: '/', redirect: { name: "Login" } }, { path: '/404', name: '404', component: () => import('@/components/common/404.vue') }, { path: '/Login', name: 'Login', component: () => import('@/components/common/Login.vue') }, { path: '/Home', name: 'Home', component: () => import('@/views/Home.vue'), redirect: { name: 'HomePage' }, children: [{ path: '/Home/Page', name: 'HomePage', component: () => import('@/views/menu/HomePage.vue') }, { path: '/Home/Demo/Echarts', name: 'Echarts', component: () => import('@/views/menu/Echarts.vue') }, { path: '/Home/Demo/Ueditor', name: 'Ueditor', component: () => import('@/views/menu/Ueditor.vue') } ] }, ] const router = new VueRouter({ // routes 用于定义 路由跳转 规则 routes, // mode 用于去除地址中的 # mode: 'history', // scrollBehavior 用于定义路由切换时,页面滚动。 scrollBehavior: () => ({ y: 0 }) }) // 解决相同路径跳转报错 const routerPush = VueRouter.prototype.push; VueRouter.prototype.push = function push(location, onResolve, onReject) { if (onResolve || onReject) return routerPush.call(this, location, onResolve, onReject) return routerPush.call(this, location).catch(error => error) }; export default router
浙公网安备 33010602011771号