springboot2.x+security+vue2.x后台管理框架---前端搭建(二)
前端搭建
一、安装全局cli脚手架
npm install -g @vue/cli
二、使用vue ui创建项目
三、vue基础配置
1,baseConfig.js配置
const baseConfig = () =>{
return {
// 生产环境 TODO
// context: "/longzy",
// basePath: "/longzy"
// 开发环境
context: "/longzy",
basePath: "http://localhost:8087/longzy",
}
}
module.exports = baseConfig()
2、vue.config.js配置
'use strict'
const { defineConfig } = require('@vue/cli-service')
const baseConfig = require('./baseConfig')
module.exports = defineConfig({
// 上下文
publicPath: baseConfig.context,
// 输出文件目录
outputDir: 'dist',
// 静态资源目录
assetsDir: '',
// 指定生成的 index.html 的输出路径 (相对于 outputDir)。也可以是一个绝对路径
indexPath: 'index.html',
// 默认在生成的静态资源文件名中包含hash以控制缓存
filenameHashing: true,
// 是否在保存的时候使用 `eslint-loader` 进行检查
lintOnSave: false,
// 是否使用包含运行时编译器的 Vue 构建版本
runtimeCompiler: false,
// dev环境下,webpack-dev-server 相关配置
devServer: {
host: '0.0.0.0',
port: '8080',
https: false,
open: false,
proxy: {
[baseConfig.basePath]: {
target: baseConfig.basePath,
changeOrigin: true,
pathRewrite: {
['^/' + baseConfig.basePath]: ''
}
}
}
},
//默认情况下 babel-loader 会忽略所有 node_modules 中的文件
transpileDependencies: true
})
3、axios配置
(1)、引入依赖
npm install axios
npm insatll element-ui -S
(2)、axios.js封装
import axios from 'axios' //引入
import { Message, MessageBox } from 'element-ui'
import baseConfig from '../../baseConfig'
import loading from '@/util/loading'
let config = {
baseURL: baseConfig.basePath,
timeout: 30000, //设置最大请求时间
headers: {
'Content-Type': "application/x-www-form-urlencoded; charset=utf-8"
}
}
const _axios = axios.create(config)
/* 请求拦截器(请求之前的操作) */
_axios.interceptors.request.use(
config => {
//如果有需要在这里开启请求时的loading动画效果
if (config.boolean){
loading.showLoading(config.boolean)
}
config.headers['Authorization'] = localStorage.getItem("token") //添加token,需要结合自己的实际情况添加,
return config;
},
err => {
loading.hideLoading()
Promise.reject(err)
}
);
/* 请求之后的操作 */
_axios.interceptors.response.use(
res => {
//在这里关闭请求时的loading动画效果
loading.hideLoading()
if (res.data.code != 200 ) {
Message({
message: res.data.message,
type: 'error',
showClose: true,
duration: 5 * 1000
})
}
return res;
},
err => {
if (err) {
// token过期处理
if (err.response.status == '401'){
localStorage.clear()
sessionStorage.clear()
window.location.href = '/login'
}
//在这里关闭请求时的loading动画效果
loading.hideLoading()
Message({
message: err.message,
showClose: true,
type: 'error',
duration: 5 * 1000
})
}
return Promise.reject(err);
}
);
export default _axios
(3)、request.js
// 导入封装好的axios实例
import axios from './axios'
import qs from 'qs'
const request = {
/**
* methods: get请求
* @param url 请求地址
* @param params 请求参数
* @param boolean 是否显示loading
*/
get(url, params, boolean){
const config = {
method: 'get',
url:url
}
if(params) config.params = qs.stringify(params)
if(boolean) config.boolean = boolean
return axios(config)
},
/**
* methods: post请求
* @param url 请求地址
* @param data 请求参数
* @param boolean 是否显示loading
*/
post(url,params, boolean){
const config = {
method: 'post',
url:url
}
if(params) config.data = qs.stringify(params)
if(boolean) config.boolean = boolean
return axios(config)
}
}
//导出
export default request
四、vue框架总体架构
效果图如下图:
1、Layout.vue
<template>
<div class="app-wrapper">
<div class="layout-aside" :class="{collapse:isCollapse}">
<div class="layout-logo">
<router-link to="/" style="text-decoration-line: none;">
<img src="@/assets/logo.png" alt="logo"/>
<span class="layout-font" v-show="!isCollapse">VUE 后台管理系统</span>
</router-link>
</div>
<SideBar />
</div>
<div class="layout-container" :class="{collapse:isCollapse}">
<div class="layout-header" :class="{collapse:isCollapse}">
<Header />
</div>
<div class="layout-main">
<AppMain />
</div>
</div>
</div>
</template>
<script>
import Header from './components/Header'
import SideBar from './components/SideBar'
import AppMain from './components/AppMain'
import { mapState } from 'vuex'
export default {
name: 'layout',
components: {Header, SideBar, AppMain},
data(){
return{
}
},
computed: {
...mapState([
'isCollapse'
])
},
methods: {
}
}
</script>
<style lang="less" scoped>
.app-wrapper{
position:relative;
}
.layout-aside{
position:fixed;
left:0;
top:0;
height:100%;
width:300px;
transition:all 0.3s;
background-color:#304156;
.layout-logo {
height:60px;
background-color:#2b2f3a;
a{
display:flex;
width:100%;
height:60px;
justify-content:center;
align-items:center;
}
img{
width:32px;
height:32px;
}
span{
font-size:16px;
line-height:60px;
color:#fff;
margin-left:30px;
}
}
}
.layout-aside.collapse{
width:64px;
}
.layout-container{
position: absolute;
width: calc(100% - 300px);
overflow:hidden;
left: 300px;
}
.layout-container.collapse{
transition:all 0.1s;
width: calc(100% - 70px);
left: 68px;
}
.layout-header{
position:fixed;
z-index:1;
top:0;
right:0;
width:calc(100% - 300px);
height:60px;
box-shadow:0 1px 3px rgba(0,21,41,0.08);
background-color:#fff;
border-bottom: 5px solid rgb(0 21 41 / 8%);
}
.layout-header.collapse{
width:calc(100% - 64px);
transition:all 0.1s;
}
.layout-main{
min-height:calc(100% - 100px);
margin:70px 15px 10px 10px;
background-color:#fff;
padding-top: 20px;
}
</style>
2、Header.vue
<template>
<div class="header-wrapper">
<div class="header-left">
<div class="open-icon">
<i :class="!isCollapse ? 'el-icon-s-fold' : 'el-icon-s-unfold'" @click="handleCollapse"></i>
</div>
<el-breadcrumb separator="/">
<template v-for="(item,index) in breadcrumbList">
<el-breadcrumb-item :key="index" v-if="item.meta.title" :to="{path:item.path}">
{{item.meta.title}}
</el-breadcrumb-item>
</template>
</el-breadcrumb>
</div>
<div class="header-right">
<el-avatar size="small" icon="el-icon-user-solid"></el-avatar>
<el-dropdown trigger="click">
<span class="el-dropdown-link">
<span class="header-user">{{userInfo.name}}</span>
<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item icon="el-icon-user">信息编辑</el-dropdown-item>
<el-dropdown-item icon="el-icon-circle-plus-outline">修改密码</el-dropdown-item>
<el-dropdown-item icon="el-icon-remove-outline" @click.native="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default{
name:'Header',
data(){
return {
breadcrumbList:[],
}
},
computed: {
...mapState([
'userInfo',
'isCollapse'
])
},
created() {
this.getBreadCrumb()
},
//这里必须使用监听,否则无法实时获取路由变动信息。
//监听后路由会实时变动,不然需要手动刷新路径才会改变
watch:{
$route(to,from) {
this.getBreadCrumb()
}
},
methods:{
handleCollapse(){
this.$store.commit("SET_COLLAPSE", !this.isCollapse)
},
// 退出
logout() {
this.$request.post('/logout').then(res =>{
localStorage.clear()
sessionStorage.clear()
this.$router.push("/login")
})
},
// 面包屑
getBreadCrumb() {
let matched = this.$route.matched
// 判断是否首页
if (matched[0].name!= undefined && matched[0].name != '/home'){
matched = [{path: '/home', meta: {title: '首页'}}].concat(matched)
}
this.breadcrumbList = matched
}
}
}
</script>
<style lang="less" scoped>
.header-wrapper{
display:flex;
justify-content:space-between;
align-content:center;
padding:0 20px;
height:60px;
.header-left{
display:flex;
align-items:center;
.open-icon{
font-size:20px;
margin-right:15px;
cursor:pointer;
}
}
.header-right{
display:flex;
align-items:center;
margin-right: 20px;
.header-user{
margin-left:15px;
font-size: 18px;
}
}
}
.el-dropdown-link{
cursor:pointer;
color:#409eff;
img{
width:40px;
height:40px;
border-radius:5px;
}
}
.el-icon-arrow-down{
font-size:12px;
}
</style>
3、SideBar.vue
<template>
<!-- <el-scrollbar class="sidebar-scroll"> -->
<el-menu class="el-menu-vertical-demo"
router
:unique-opened="true"
:collapse="isCollapse"
background-color="#304156"
text-color="#fff"
active-text-color="#409eff"
style="border:none"
:collapse-transition="false">
<template>
<el-submenu index="1">
<template slot="title">
<i class="el-icon-setting"></i>
<span>系统管理</span>
</template>
<el-menu-item index="/home">
<template>
<i class="el-icon-user-solid"></i>
<span >用户管理</span>
</template>
</el-menu-item>
<el-menu-item index="/menuManagement">
<template>
<i class="el-icon-menu"></i>
<span >菜单管理</span>
</template>
</el-menu-item>
</el-submenu>
<el-submenu index="2">
<template slot="title">
<i class="el-icon-folder"></i>
<span>资源管理</span>
</template>
<el-menu-item index="/dictManagement">
<template>
<i class="el-icon-reading"></i>
<span >字典管理</span>
</template>
</el-menu-item>
</el-submenu>
</template>
</el-menu>
<!-- </el-scrollbar> -->
</template>
<script>
import { mapState } from 'vuex'
export default{
name:'SideBar',
data(){
return{
}
},
computed: {
...mapState([
'isCollapse'
])
},
methods: {
}
}
</script>
<style lang="less" scoped>
.el-menu-vertical-demo{
text-align: left;
}
// .sidebar-scroll{
// height:calc(100% - 60px);
// }
// .sidebar{
// height:100%;
// text-align:left;
// border-right:none;
// }
</style>
4、AppMain.vue
<template>
<div class="app-main">
<transition name="fade-transfrom" mode="out-in">
<keep-alive>
<router-view />
</keep-alive>
</transition>
</div>
</template>
<script>
export default{
name:'AppMain',
data(){
return{
}
}
}
</script>