赞
踩
一个前后端分离的管理系统。前端使用vue框架,后端使用Java的springboot框架。
项目框架:SpringBoot+SpringSecurity+jwt+mybatis-plus+swagger2
环境:JDK1.8
软件: idea
后端教程:Java后端管理系统
前端框架:vue ,vue_cli,element
软件: HBuilder x
cmd中输入命令:
vue create 项目名
结构:
结构描述见:Vue之vue-cli新和旧版本代码生成的项目区别
官方地址:https://element.eleme.cn/#/zh-CN/component/installation
安装element插件:
npm i element-ui -S
在main.js中引入:
//引入element
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
相关资料:【Vue-cli3】路由基础
修改App.vue使用router-view
<template>
<div id="app">
<!-- router-view路由页面组件展示区域 -->
<router-view></router-view>
</div>
</template>
<script>
</script>
<style>
</style>
在main.js中新增路由配置
import Vue from 'vue'
import App from './App.vue'
import router from "./router/index.js" //引入路由配置 在router文件下的index.js
Vue.config.productionTip = false
new Vue({
render: h => h(App),
router //添加路由
}).$mount('#app')
创建路由 src-》router-》index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
// 配置First的路由
{
path: '/',
name: 'Login',
component: () => import("@/components/views/Login.vue"),
},
]
})
安装:
npm install axios --save-dev
npm install qs
main.js添加axios配置:
import axios from 'axios' //引入axios import qs from 'qs' Vue.prototype.$axios = axios var base="http://localhost:8090" export const POST = (url, params) => { axios.defaults.transformRequest = [ function (data) { return qs.stringify(data) } ]; console.log(url); return axios.post(`${base}${url}`, params).then(res => { console.log(res); if (res.status == 200) return res.data; else console.log("操作失败,服务端出现异常错误!") }) } Vue.prototype.$ajax = { POST(url, params) { return POST(url, params); }, }
页面使用:
submitForm() {
var _this=this
this.$ajax
.POST("/login", {
username: this.loginForm.username,
password: this.loginForm.password
}).then((data) => {
console.info(data)
if(data.success){
//处理
_this.$router.push("/home") //跳转
}
})
},
登录页面Login.vue
在 assets-》css文件下创建global.css
html,body,#app{
height: 100%;
margin:0 ;
padding: 0;
}
在man.js中引入:
import './assets/css/global.css' //引入全局样式
使用滑块验证插件,使用教程SlideVerify。
<template> <!-- 登录布局 --> <div class="login_container"> <!-- 登录区域 --> <div class="login_box"> <!-- 头像 --> <div class="avatar_box"> <img src="../assets/img/timg.gif" /> </div> <!-- 表单 --> <el-form :model="loginForm" :rules="loginRules" ref="loginForm" label-width="0px" class="login_Form"> <el-form-item prop="username"> <el-input v-model="loginForm.username" prefix-icon="el-icon-user-solid"></el-input> </el-form-item> <el-form-item prop="password"> <el-input type="password" v-model="loginForm.password" autocomplete="off" prefix-icon="el-icon-lock"></el-input> </el-form-item> <el-form-item prop="verifyCode"> <div class="verify_box"> <el-popover placement="top-start" width="380" trigger="click" v-model="SlideVerify"> <slide-verify ref="slideblock" :l="42" :r="10" :w="380" :h="185" @success="onSuccess" @fail="onFail" @refresh="onRefresh" @again="onAgain" :slider-text="text" ></slide-verify> <el-button style="width: 410px;" :type="codeType" @click="handleClick" :disabled="disabled" slot="reference">{{TipMsg}}</el-button> </el-popover> </div> </el-form-item> <el-form-item class="login_btn"> <el-button type="primary" @click="submitForm()">提交</el-button> <el-button @click="resetForm('loginForm')">重置</el-button> </el-form-item> </el-form> </div> </div> </template> <script> export default{ name: 'Login', data() { return { fullscreenLoading: false, SlideVerify:false, text: '向右滑', codeType:"", TipMsg:"点击验证", disabled:false, loginForm: { username: '', password: '', verifyCode: '' }, loginRules: { username: [ { required:true , message: "请输入用户名称",trigger: 'blur' }, { min :3 ,max:5, message: "长度在 3 到 5 个字符",trigger: 'blur'} ], password: [ { required:true , message: "请输入密码",trigger: 'blur' }, { min :3 ,max:5, message: "长度在 3 到 5 个字符",trigger: 'blur'} ], verifyCode:[ { required:true , message: "请输入验证码",trigger: 'blur'} ] } }; }, methods: { onSuccess(){ console.info('成功!') this.loginForm.verifyCode=true; this.SlideVerify=false; this.codeType="success"; this.TipMsg="验证成功"; // this.disabled=true; }, onFail(){ console.info('失败!') this.loginForm.verifyCode=false; this.codeType="danger"; this.TipMsg="验证失败"; }, onRefresh(){ console.info('刷新!') this.loginForm.verifyCode=false; this.codeType=""; this.TipMsg="点击验证"; // this.disabled=false; }, onAgain(){ console.log("检测到非人为操作的哦!"); this.$refs.slideblock.reset(); this.onRefresh(); }, handleClick() { // 父组件直接可以调用刷新方法 this.$refs.slideblock.reset(); this.TipMsg="验证中"; this.codeType=""; }, submitForm() { var _this=this if(_this.loginForm.verifyCode==true){ console.info("验证成功!") //登录时加载样式 const loading = this.$loading({ lock: true, // text: 'Loading', // spinner: 'el-icon-loading', background: 'rgba(255, 255, 255, 0.7)' }); this.$ajax .POST("/login", { username: this.loginForm.username, password: this.loginForm.password }) .then((data) => { console.info(data) if(data.success){ _this.$message.success("登录成功!"); console.info("存储token") localStorage.setItem("token",data.data.token) loading.close();//关闭加载样式 _this.$router.push("/") } else{ _this.$message.error("登录失败!账号密码错误!"); _this.onRefresh(); } }) }else{ this.$message.info("请点击验证!"); } }, resetForm(formName) { this.$refs[formName].resetFields(); } } } </script> <style> .el-loading-spinner { /* // 这个是自己想设置的 gif 加载动图 */ background-image:url('../assets/img/loading.gif'); /* //设置背景图 不重复 */ background-repeat: no-repeat; height:10%; /* //设置背景 定位 为居中 */ background-position:center; /* //覆盖 element-ui 默认的 50% 因为此处设置了height:100%,所以不设置的话,会只显示一半,因为被top顶下去了 */ top:0; } .el-loading-spinner .circular { /* //隐藏 之前 element-ui 默认的 loading 动画 */ display: none; } </style> <!-- 内部样式 --> <style lang="less" scoped > .login_container{ width: 100%; height: 100%; background-color:aquamarine; } .login_box{ width: 450px; height: 380px; background-color: #FFFFFF; border-radius: 3px; position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); } .avatar_box{ width: 130px; height: 130px; border: 1px solid #EEEEEE; border-radius: 50%; padding: 10px; box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1); margin: -65px auto; background-color: #FFFFFF; img{ width: 100%; height: 100%; border-radius: 50%; background-color: #eeeeee; } } .login_Form{ position: absolute; bottom: 0px; width: 100%; padding: 0px 20px; box-sizing: border-box; .login_btn{ display: flex; justify-content: flex-end; } } .verify_box{ display: flex; .verify_code{ width: 70%; justify-content: left; } .verify_img{ width: 30%; height: 45px; justify-content: flex-end; } } </style>
使用localStorage.setItem(“token”,token) 存储token,利用拦截器每次请求带上token ,过期删除跳转登录页面。
首先在路由router-》index.js中配置
export default new Router({ routes: [ // 配置First的路由 { path: '/', name: 'Login', component: () => import("@/components/views/Login.vue"), }, { path: '/home', name: 'Home', component: () => import("@/components/views/Home.vue"), }, ] })
使用element的Container 布局容器
<template> <el-container class="el-container"> <!-- 头部布局 --> <el-header> <!-- logo和项目名 --> <div class="left_box"> <img src="../../assets/img/timg.gif" /> <span>集元库房管理系统</span> </div> <!-- 用户登录展示头像 --> <div class="right_box"> <i class="el-icon-full-screen" @click="toggleFullscreen"></i> <!-- 下拉菜单 --> <el-dropdown> <img src="../../assets/img/timg.gif" /> <el-dropdown-menu slot="dropdown"> <el-dropdown-item icon="el-icon-user">个人信息</el-dropdown-item> <el-dropdown-item icon="el-icon-edit-outline">修改密码</el-dropdown-item> <el-dropdown-item icon="el-icon-switch-button"><a href="/">退出登录</a></el-dropdown-item> </el-dropdown-menu> </el-dropdown> </div> </el-header> <el-container> <el-aside :width="isCollapse?'60px':'200px'" > <!--展开/收起--> <div class="toggle_box" @click="toggleCollapse">|||</div> <el-menu class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose" background-color="#001529" text-color="#fff" active-text-color="#ffd04b" :collapse="isCollapse" :default-active="activePath" :collapse-transition="false" :unique-opened="true" :router="true"> <el-submenu index="1"> <template slot="title"> <i class="el-icon-s-tools"></i> <span>系统管理</span> </template> <el-menu-item-group> <el-menu-item index="/user"><i class="el-icon-user" ></i>用户管理</el-menu-item> <el-menu-item index="1-2"><i class="el-icon-star-off"></i>权限管理</el-menu-item> </el-menu-item-group> </el-submenu> <el-submenu index="2"> <template slot="title"> <i class="el-icon-message-solid"></i> <span>日志管理</span> </template> <el-menu-item-group> <el-menu-item index="2-1"><i class="el-icon-view"></i>sql日志</el-menu-item> <el-menu-item index="2-2"><i class="el-icon-edit"></i>用户日志</el-menu-item> </el-menu-item-group> </el-submenu> </el-menu> </el-aside> <el-main> <!-- 路由视图 --> <router-view></router-view> </el-main> </el-container> </el-container> </template> <script> export default{ name: 'Main', data(){ return { isCollapse: false, activePath: '', } }, components: { }, methods: { toggleCollapse () { this.isCollapse = !this.isCollapse }, handleOpen (key, keyPath) { console.log(key, keyPath) }, handleClose (key, keyPath) { console.log(key, keyPath) }, toggleFullscreen() { let _this = this; let el = document.documentElement; if (document.fullscreenElement === null) { _this.openFullscreen(el); } else { _this.quitFullscreen(); } }, openFullscreen(element) { if (element.requestFullscreen) { element.requestFullscreen(); } else if (element.webkitRequestFullScreen) { element.webkitRequestFullScreen(); } else if (element.mozRequestFullScreen) { element.mozRequestFullScreen(); } else if (element.msRequestFullscreen) { // IE11 element.msRequestFullscreen(); } }, quitFullscreen() { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.webkitCancelFullScreen) { document.webkitCancelFullScreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if (document.msExitFullscreen) { document.msExitFullscreen(); } }, getKeyDown() { let _this = this; if (event.keyCode === 122) { event.preventDefault() || (event.returnValue = false); _this.toggleFullscreen(); // 触发全屏的按钮 } else if (event.keyCode === 27) { if (document.fullscreenElement !== null) { _this.quitFullScreen(); } } } }, mounted() { window.addEventListener("keydown", this.getKeyDown); } } </script> <style lang="less" scoped> /*设置整个容器*/ .el-container{ width: 100%; height: 100%; } /*头部布局*/ .el-header { background-color: #001529; display: flex; justify-content: space-between; padding-left:0 ; color: #FFFFFF; align-items: center; font-size: 20px; } /*侧边栏*/ .el-aside { background-color: #001529; color: #333; //去掉边框 .el-menu { border-right: none; } /*展开/收起*/ .toggle_box { background-color: chartreuse; font-size: 15px; font-weight: bold; line-height: 24px; color: #FFFFFF; letter-spacing: 0.2em; text-align: center; cursor: pointer; } } /*内容*/ .el-main { background-color: #E9EEF3; } body > .el-container { margin-bottom: 40px; } /*左边logo和标题*/ .left_box{ display: flex; align-items: center; /*logo*/ img{ width: 60px; height: 60px; margin: 0px 0px 10px 15px; } /*标题*/ span{ margin-left: 15px; } } /*右边的登录头像*/ .right_box{ /*全屏按钮、主题换色*/ // el-icon-full-screen // 头像 .el-dropdown > img{ height: 60px; width: 60px; border-radius:50% ; background-color: #FFFFFF; margin:0px 15px 0px 0px ; } } // 下拉菜单 .el-dropdown-link { cursor: pointer; color: #409EFF; } .el-icon-arrow-down { font-size: 12px; } a { text-decoration: none; color: #000000; } </style>
登录成功跳转home页面
在登录成功后使用localStorage.setItem(“token”,token) 存储,每次请求由拦截器带上token。
在main.js中
let token=""; axios.defaults.headers.common['token'] = token; // 添加请求拦截器 axios.interceptors.request.use(function (config) { let token=localStorage.getItem('token') console.info("每次带token") console.info(token) if(token) config.headers.common['token'] = token; return config; }, function (error) { // Do something with request error Message.error('网络连接异常,请稍后再试!'); console.info("error: "); console.info(error); return Promise.reject(error); }); // 添加一个响应拦截器 axios.interceptors.response.use(function (response) { return response; }, function (error) { // Do something with response error if (error.response.status == 404) { //找不到请求的服务 Message.error('找不到请求的服务!'); } else if (error.response.status == 401) { //没有token需要登录 Message.error('登录凭证失效,请重新登录!'); window.location.href = "/login"; } else if (error.response.status == 403) { Message.error('token过期,请重新登录!'); localStorage.removeItem("token"); window.location.href = "/login"; } return Promise.reject(error); })
进行登录:
这里是在配置了初始token为空,可以选择不带
在路由router-》index.js中配置user路由
放在home下为子标签, Container 布局容器的main部分会改变显示
{
path: '/home',
name: 'Home',
component: () => import("@/components/views/Home.vue"),
// 子标签:children。 页面使用<router-view></router-view> 显示
children :[
{
path: '/', //默认显示
name: 'User',
//使用() => import('xx')引入
component: () => import('../components/users/User.vue')
},
]
}
User.vue
<template> <div> <!-- 面包屑 --> <el-breadcrumb separator-class="el-icon-arrow-right" style="padding-left: 10px;padding-bottom: 10px;"> <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item> <el-breadcrumb-item>系统管理</el-breadcrumb-item> <el-breadcrumb-item>用户管理</el-breadcrumb-item> </el-breadcrumb> <!-- 用户列表卡片 --> <el-card class="box-card"> <el-form :inline="true" :model="UserInfoVo" class="demo-form-inline"> <el-form-item label="部门" label-width="70px" > <el-select v-model="UserInfoVo.depId" clearable placeholder="请选择" > <el-option v-for="item in departments" :key="item.dpId" :label="item.departmentName" :value="item.dpId" > <span style="float:left">{{item.departmentName}}</span> <span style="float: right;color: #409EFF;font-size: 13px;"> <span class="el-tag el-tag--success el-tag--mini el-tag--plain">{{item.deptCount}}</span> </span> </el-option> </el-select> </el-form-item> <!-- <el-form-item label="用户名" label-width="70px"> <el-input clearable v-model="UserInfoVo.userName" placeholder="请输入用户名"></el-input> </el-form-item> --> <el-form-item label="邮箱" label-width="70px"> <el-input v-model="UserInfoVo.email" clearable placeholder="请输入邮箱"></el-input> </el-form-item> <el-radio-group v-model="UserInfoVo.sex" label-width="70px"> <el-radio :label="0">男</el-radio> <el-radio :label="1">女</el-radio> <el-radio label="">全部</el-radio> </el-radio-group> <br /> <el-form-item label="昵称" label-width="70px"> <el-input clearable v-model="UserInfoVo.nickname" placeholder="请输入昵称"></el-input> </el-form-item> <!-- 按钮 --> <el-form-item style="margin-left: 10px;"> <el-row> <el-button icon="el-icon-refresh" @click="resetUserInfoVO()">重置</el-button> <el-button type="primary" icon="el-icon-search" @click="findLists()">查询</el-button> <el-button type="success" icon="el-icon-plus" @click="show">添加</el-button> <el-button type="warning" icon="el-icon-download">导出</el-button> </el-row> </el-form-item> </el-form> <!-- // 表格显示 --> <el-table :data="userList" border style="width: 100%;height:600px;"> <el-table-column prop="userId" label="#" width="150"> </el-table-column> <el-table-column sortable prop="departmentName" label="部门" width="150"> </el-table-column> <el-table-column sortable prop="nickname" label="昵称" width="150"> </el-table-column> <el-table-column prop="sex" label="性别" width="150"> <template slot-scope="scope">{{scope.row.sex==0?'男':(scope.row.sex==1?'女':'保密')}}</template> </el-table-column> <el-table-column prop="birthday" label="生日" width="150" > <template slot-scope="scope">{{scope.row.birthday| dateYMDFormat}}</template> </el-table-column> <el-table-column prop="phone" label="手机号" width="150"> </el-table-column> <el-table-column prop="email" label="邮箱" width="150"> </el-table-column> <el-table-column prop="delflag" label="是否禁用" width="100"> <template slot-scope="scope"> <el-switch v-model="scope.row.delflag" active-color="#13ce66" inactive-color="#ff4949" > </el-switch> </template> </el-table-column> <el-table-column label="操作"> <el-button type="primary" size="mini" icon="el-icon-edit"></el-button> <el-button type="danger" size="mini" icon="el-icon-delete"></el-button> <el-button type="warning" size="mini" icon="el-icon-s-tools"></el-button> </el-table-column> </el-table> <!-- 新增编辑弹框子组件 --> <!--<user-info-add :addOrUpdateVisible="addOrUpdateVisible" @changeShow="showAddOrUpdate" ref="UserInfoAdd"></user-info-add> --> <!-- 分页 --> <div class="block"> <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[10, 20, 30, 40]" :page-size="pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total"> </el-pagination> </div> </el-card> </div> </template> <script> // import Home from "../views/Home" import UserInfoAdd from './UserInfoAdd.vue' export default{ name: 'Main', data() { return { value:'', //每页显示条数 pageSize:10, //总记录条数 total:7, //初始页数 currentPage:1, // 用户信息集合 静态数据 userList: [{ userId: '1', departmentName: '研发部', nickname: '张小虎', sex:1, birthday:"2016-05-02", phone:"13452850922", email:"13452850922@qq.com" }], departments: [], UserInfoVo:{ "depId": "", "departmentName": "", "email": "", "nickname": "", "phone": "", "sex": "", "userName": "" }, // 控制新增编辑弹窗的显示与隐藏 addOrUpdateVisible: false } }, //创建组件时调用方法 created(){ this.findLists(); this.findDeptAndCount(); }, // 使用子组件 components:{ UserInfoAdd }, methods: { //当每页条数改变时 handleSizeChange(val) { this.pageSize=val; this.findLists(); console.log(`每页 ${val} 条`); }, // 当页码改变时 handleCurrentChange(val) { this.currentPage=val; this.findLists(); console.log(`当前页: ${val}`); }, // 异步 async findLists(){ var _this=this console.log(this.UserInfoVo); this.$ajax .POST("/department/findLists", { currentPage: this.currentPage, pageSize: this.pageSize, UserInfoVo:this.UserInfoVo }) .then((data) => { console.info(data) if(data.success){ _this.userList = data.data.userInfoList; _this.total=data.data.total; } }) }, async findDeptAndCount(){ var _this=this; this.$ajax .POST("/department/findDeptAndCount", { currentPage: this.currentPage, pageSize: this.pageSize }) .then((data) => { console.info(data) if(data.success){ _this.departments = data.data.departments; } }) }, // 重置查询条件 resetUserInfoVO(){ this.UserInfoVo.depId="", this.UserInfoVo.departmentName= "", this.UserInfoVo.email= "", this.UserInfoVo.nickname= "", this.UserInfoVo.phone= "", this.UserInfoVo.sex= "", this.UserInfoVo.userName= "" this.findLists(); }, // 按钮点击事件 显示新增编辑弹窗组件 show(){ this.addOrUpdateVisible = true }, // 监听 子组件弹窗关闭后触发,有子组件调用 showAddOrUpdate(data){ if(data === 'false'){ this.addOrUpdateVisible = false }else{ this.addOrUpdateVisible = true } } } } </script> <style> </style>
配置:Vue element el-table-column标签中对日期进行格式化(全局过滤器)
相关文章:
1.elementui tab标签管理路由页面。
2.Vue之标签页。
3.Vue之Element标签页保留用户操作缓存。
效果:
import Vue from 'vue' import Vuex from 'vuex' import store from './store' import router from './router.js' Vue.use(Vuex) export default new Vuex.Store({ state: { openTab:[],//所有打开的路由 activeIndex:'/main', //当前激活状态 curContextTabId:"", //右击当前标签页id excludeList:[],//keep-alive的黑名单 includeList:[]//keep-alive的白名单 }, mutations: { // 添加tabs add_tabs (state, data) { this.state.openTab.push(data); }, // 删除tabs delete_tabs (state, route) { let index = 0; for (let option of state.openTab) { if (option.route === route) { break; } index++; } this.state.openTab.splice(index, 1); }, // 设置当前激活的tab set_active_index (state, index) { this.state.activeIndex = index; }, // 保存右键点击tab的id saveCurContextTabId(state, curContextTabId) { this.state.curContextTabId = curContextTabId }, // 关闭所有标签 closeAllTabs(state) { this.state.openTab = []; //想实现标签和页面同步,以下三句代码 store.commit('add_tabs', {route: '/main', name: '首页'}); //添加进入标签页数组 store.commit('set_active_index', '/main'); // 激活/main的标签页 router.push({path: '/main'});// 跳转路由页面 显示页面 }, // 关闭其它标签页 closeOtherTabs(state, par) { let tabs = state.openTab; let length = tabs.length; let curContextTabId = state.curContextTabId; let activeTabItem = state.activeIndex // console.log("当前激活:"+activeTabItem) // console.log("右击选中:"+curContextTabId) let id; // 右键点击时的tab在整个tabs数组中的id let curId // 左键点击时的tab在整个tabs数组中的id tabs.forEach((tab, index) => { if (tab.route == curContextTabId) { id = index } if (tab.route == activeTabItem) { curId = index } }) // console.log("激活:"+activeTabItem+" id:"+id+"|| 右击选中:"+curContextTabId+" curid:"+ curId) store.commit('set_active_index', curContextTabId);//激活显示当前页面 router.push({path: curContextTabId});//激活路由转换页面 if (par == "left") { // console.info("关闭左边! 显示当前标签:"+curContextTabId) this.state.openTab= state.openTab.slice(id, length) //截取当前标签到末尾标签 //使首页一直存在第一位置 if(curContextTabId !="/main") { this.state.openTab.unshift({route: '/main', name: '首页'}); //导入首页在数组头部 } } if (par == "right") { // console.info("关闭右边! 显示当前标签:"+curContextTabId) this.state.openTab = state.openTab.slice(0, id + 1) //截取0到当前选择标签 } if (par == "other") { // console.info("关闭其他! 激活当前标签:"+curContextTabId) this.state.openTab = [state.openTab[id]] //只留当前标签和首页 if(curContextTabId !="/main") { this.state.openTab.unshift({route: '/main', name: '首页'}); } } } }, actions: { } })
新增:标签页和标签页右键菜单功能、用户操作缓存、关闭标签页清除缓存。
使用以下布局结合标签页
<template> <el-container class="el-container"> <!-- 头部布局 --> <el-header> <nav-top></nav-top> </el-header> <!-- 左侧导航栏 --> <el-container> <slider></slider> <el-main> <!-- 内容区 --> <div class="app-wrap"> <!-- 此处放置el-tabs 标签代码 --> <div > <el-tabs v-model="activeIndex" type="border-card" closable v-if="openTab.length" @tab-click='tabClick' @tab-remove='tabRemove' @contextmenu.prevent.native="openContextMenu($event)" > <el-tab-pane :key="item.name" v-for="(item, index) in openTab" :label="item.name" :name="item.route" > </el-tab-pane> <div class="content-wrap"> <!-- 缓存页面不刷新 --> <keep-alive :include="includeList"> <router-view></router-view> </keep-alive> </div> </el-tabs> </div> </div> <ul v-show="contextMenuVisible" :style="{left:left+'px',top:top+'px'}" class="contextmenu" > <li @click="closeAllTabs">关闭所有</li> <li @click="closeOtherTabs('left')">关闭左边</li> <li @click="closeOtherTabs('right')">关闭右边</li> <li @click="closeOtherTabs('other')">关闭其他</li> </ul> </el-main> </el-container> </el-container> </template> <script> // @ is an alias to /src import Slider from '@/components/Slider.vue' import navTop from '@/components/Header.vue' import store from '../store.js' export default { name: 'Home', components: { Slider,navTop,store }, data(){ return{ contextMenuVisible:false, currentContextTabId:'', left:0, top:0, includeList:[] } }, mounted () { //监控标签页点击空白事件 document.body.addEventListener("click", this.closeContextMenu); window.addEventListener("keydown", this.getKeyDown); // 刷新时以当前路由做为tab加入tabs // 当前路由不是首页时,添加首页以及另一页到store里,并设置激活状态 // 当当前路由是首页时,添加首页到store,并设置激活状态 if (this.$route.path != '/' && this.$route.path != '/main') { // console.log('1'); this.$store.commit('add_tabs', {route: '/main', name: '首页'}); //添加到标签页中 this.$store.commit('set_active_index', '/main');//激活显示页面 this.$store.commit('add_tabs', {route: this.$route.path , name: this.$route.name }); this.$store.commit('set_active_index', this.$route.path); } else { // console.log('2'); this.$store.commit('add_tabs', {route: '/main', name: '首页'}); this.$router.push('/'); } this.updateIncludeList(); }, methods:{ // 标签页右击菜单 openContextMenu(e) { //console.log(e.srcElement); if (e.srcElement.id) { let currentContextTabId = e.srcElement.id.split("-")[1]; this.contextMenuVisible = true; // console.info("标签id:"+currentContextTabId); this.$store.commit("saveCurContextTabId", currentContextTabId); this.currentContextTabId=currentContextTabId; this.left = e.clientX; this.top = e.clientY + 10; } }, // 关闭所有标签页 closeAllTabs() { this.$store.commit("closeAllTabs"); this.contextMenuVisible = false; this.updateIncludeList(); }, // 关闭其它标签页 closeOtherTabs(par) { this.$store.commit("closeOtherTabs", par); this.contextMenuVisible = false; this.updateIncludeList(); }, // 关闭右击菜单 closeContextMenu() { this.contextMenuVisible = false; }, //tab标签点击时,切换相应的路由 tabClick(tab){ // console.log("tab",tab); console.log('active',this.activeIndex); this.$router.push({path: this.activeIndex}); }, //移除tab标签 tabRemove(targetName){ console.log("tabRemove",targetName); this.blackList=targetName; //首页不删 if(targetName == '/'||targetName == '/main'){ return } this.$store.commit('delete_tabs', targetName); if (this.activeIndex === targetName) { // 设置当前激活的路由 // console.info("当前标签:"+this.openTab.length) if (this.openTab && this.openTab.length >= 1) { // console.log('=============',this.openTab[this.openTab.length-1].route) this.$store.commit('set_active_index', this.openTab[this.openTab.length-1].route); this.$router.push({path: this.activeIndex}); } else { this.$router.push({path: '/'}); } } this.updateIncludeList(); }, //更新缓存白名单 updateIncludeList(){ var arr=[]; for (var i = 0; i < this.openTab.length; i++) { var temp=this.openTab[i].route.replace("/",""); console.log("temp:"+temp); arr.push(temp) } this.includeList=arr; } }, computed: { openTab () { return this.$store.state.openTab; }, activeIndex:{ get () { return this.$store.state.activeIndex; }, set (val) { this.$store.commit('set_active_index', val); } } }, watch:{ '$route'(to,from){ //判断路由是否已经打开 //已经打开的 ,将其置为active //未打开的,将其放入队列里 let flag = false; for(let item of this.openTab){ console.log("item.name",item.name) // console.log("t0.name",to.name) if(item.name === to.name){ // console.log('to.path',to.path); this.$store.commit('set_active_index',to.path) flag = true; break; } } if(!flag){ // console.log('to.path',to.path); this.$store.commit('add_tabs', {route: to.path, name: to.name}); this.$store.commit('set_active_index', to.path); } this.updateIncludeList(); } } } </script> <style > /*设置整个容器*/ .el-container{ width: 100%; height: 100%; } /*头部布局*/ .el-header { background-color: #001529; display: flex; justify-content: space-between; padding-left:0 ; color: #FFFFFF; align-items: center; font-size: 20px; } /*侧边栏*/ .el-aside { background-color: #001529; color: #333; /* //去掉边框 */ .el-menu { border-right: none; } /*展开/收起*/ .toggle_box { background-color: chartreuse; font-size: 15px; font-weight: bold; line-height: 24px; color: #FFFFFF; letter-spacing: 0.2em; text-align: center; cursor: pointer; } } /*内容*/ .el-main { background-color: #E9EEF3; } body > .el-container { margin-bottom: 40px; } /*左边logo和标题*/ .left_box{ display: flex; align-items: center; /*logo*/ img{ width: 60px; height: 60px; margin: 0px 0px 10px 15px; } /*标题*/ span{ margin-left: 15px; } } /*右边的登录头像*/ .right_box{ /*全屏按钮、主题换色*/ // el-icon-full-screen // 头像 .el-dropdown > img{ height: 60px; width: 60px; border-radius:50% ; background-color: #FFFFFF; margin:0px 15px 0px 0px ; } } /* // 下拉菜单 */ .el-dropdown-link { cursor: pointer; color: #409EFF; } .el-icon-arrow-down { font-size: 12px; } a { text-decoration: none; color: #000000; } /* 标签页右击菜单样式 */ .contextmenu { width: 100px; margin: 0; border: 1px solid #ccc; background: #fff; z-index: 3000; position: absolute; list-style-type: none; padding: 5px 0; border-radius: 4px; font-size: 14px; color: #333; box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.2); } .contextmenu li { margin: 0; padding: 7px 16px; } .contextmenu li:hover { background: #f2f2f2; cursor: pointer; } </style>
<template> <div class="header"> <!-- logo和项目名 --> <div class="left_box"> <img src="../assets/img/timg.gif" /> <span>XX管理系统</span> </div> <!-- 用户登录展示头像 --> <div class="right_box"> <i class="el-icon-full-screen" @click="toggleFullscreen"></i> <!-- 下拉菜单 --> <el-dropdown> <img src="../assets/img/timg.gif" /> <el-dropdown-menu slot="dropdown"> <el-dropdown-item icon="el-icon-user">个人信息</el-dropdown-item> <el-dropdown-item icon="el-icon-edit-outline">修改密码</el-dropdown-item> <el-dropdown-item icon="el-icon-switch-button" ><span @click='logout'>退出登录</span></el-dropdown-item> </el-dropdown-menu> </el-dropdown> </div> </div> </template> <script> export default { name:'navTop', methods:{ logout(){ console.info("退出登录!清除token") this.$store.state.openTab = []; // this.$store.state.activeIndex = '/main'; //清除token localStorage.removeItem('token'); this.$router.push('/login') }, toggleFullscreen() { let _this = this; let el = document.documentElement; if (document.fullscreenElement === null) { _this.openFullscreen(el); } else { _this.quitFullscreen(); } }, openFullscreen(element) { if (element.requestFullscreen) { element.requestFullscreen(); } else if (element.webkitRequestFullScreen) { element.webkitRequestFullScreen(); } else if (element.mozRequestFullScreen) { element.mozRequestFullScreen(); } else if (element.msRequestFullscreen) { // IE11 element.msRequestFullscreen(); } }, } } </script> <style lang="less" scoped> /*头部布局*/ .header { background-color: #001529; display: flex; justify-content: space-between; padding-left:0 ; color: #FFFFFF; align-items: center; font-size: 20px; width: 100%; } /*左边logo和标题*/ .left_box{ display: flex; align-items: center; /*logo*/ img{ width: 60px; height: 60px; margin: 0px 0px 10px 15px; } /*标题*/ span{ margin-left: 15px; } } /*右边的登录头像*/ .right_box{ /*全屏按钮、主题换色*/ /* // el-icon-full-screen */ /* // 头像 */ .el-dropdown > img{ height: 60px; width: 60px; border-radius:50% ; background-color: #FFFFFF; margin:0px 15px 0px 0px ; } } </style>
<template> <el-aside class="aside" :width="isCollapse?'60px':'200px'" > <!--展开/收起--> <div class="toggle_box" @click="toggleCollapse">|||</div> <el-menu class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose" background-color="#001529" text-color="#fff" active-text-color="#ffd04b" :collapse="isCollapse" :default-active="activePath" :collapse-transition="false" :unique-opened="true" :router="true"> <el-menu-item index="/main" > <i class="el-icon-menu"></i> <span slot="title">首页</span> </el-menu-item> <el-submenu v-for="(item,index) in menuList" :key="index" :index='item.id' v-if="item.show && item.children!=''"> <!-- 主菜单 --> <template slot="title"> <i :class="item.icon"></i> <span>{{item.title}}</span> </template> <!-- 子菜单 --> <el-menu-item-group > <el-menu-item v-for="child in item.children" :key="child.id" :index='child.index' v-if="child.show" > <i :class="child.icon"></i><span>{{child.childtitle}}</span> </el-menu-item> </el-menu-item-group> </el-submenu> </el-menu> </el-aside> </template> <script> export default { name: 'Slider', data(){ return { isCollapse: false, activePath: '', menuList:[ { id:'1', title: '系统管理', icon:'el-icon-s-tools', show:true, children:[ { index:'/user', childtitle:'用户管理', icon:'el-icon-user', show:true,//是否显示子菜单页面 }, { index:'/page2', childtitle:'权限管理', icon:'el-icon-star-off', show:true, }, ] }, { id:'2', title: '日志管理', icon:'el-icon-message-solid', show:true, children:[ { index:'/page3', childtitle:'sql日志', icon:'el-icon-view', show:true, }, { index:'/page4', childtitle:'用户日志', icon:'el-icon-edit', show:true, }, ] } ] } }, methods:{ toggleCollapse () { this.isCollapse = !this.isCollapse }, handleOpen (key, keyPath) { // console.log(key, keyPath) }, handleClose (key, keyPath) { // console.log(key, keyPath) }, quitFullscreen() { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.webkitCancelFullScreen) { document.webkitCancelFullScreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if (document.msExitFullscreen) { document.msExitFullscreen(); } }, getKeyDown() { let _this = this; if (event.keyCode === 122) { event.preventDefault() || (event.returnValue = false); _this.toggleFullscreen(); // 触发全屏的按钮 } else if (event.keyCode === 27) { if (document.fullscreenElement !== null) { _this.quitFullScreen(); } } }, } } </script> <style lang="less" scoped> /*侧边栏*/ .el-aside { background-color: #001529; color: #333; /* //去掉边框 */ .el-menu { border-right: none; } /*展开/收起*/ .toggle_box { background-color: chartreuse; font-size: 15px; font-weight: bold; line-height: 24px; color: #FFFFFF; letter-spacing: 0.2em; text-align: center; cursor: pointer; .i{ font-size: 15px; } } } </style>
新增:限制标签页条数,路由拦截跳转。
import Vue from 'vue' import App from './App.vue' import router from './router' //引入路由 import store from './store' //引入标签 import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; import './assets/css/global.css' //引入全局样式 //验证码插件 import SlideVerify from 'vue-monoplasty-slide-verify'; Vue.use(SlideVerify); Vue.use(ElementUI); Vue.config.productionTip = false import { Message } from 'element-ui'; //引入日期标签格式化插件 import moment from 'moment' Vue.use(require('vue-moment')); Vue.prototype.moment = moment Vue.filter('dateYMDHMSFormat',function(dateStr,pattern='YYYY-MM-DD HH:mm:ss'){ return moment(dateStr).format(pattern); }) import axios from 'axios' //引入axios import qs from 'qs' var base="http://localhost:8090" export const POST = (url, params) => { axios.defaults.transformRequest = [ function (data) { return qs.stringify(data) } ]; console.log(url); return axios.post(`${base}${url}`, params).then(res => { console.log(res); if (res.status == 200) return res.data; else this.$message.error("操作失败,服务端出现异常错误!") }) } Vue.prototype.$ajax = { POST(url, params) { return POST(url, params); }, } // 添加请求拦截器 axios.interceptors.request.use(function (config) { let token=localStorage.getItem('token') // console.info("每次带token") // console.info(token) if(token) config.headers.common['token'] = token; return config; }, function (error) { // Do something with request error Message.error('网络连接异常,请稍后再试!'); console.info("error: "); console.info(error); return Promise.reject(error); }); // 添加一个响应拦截器 axios.interceptors.response.use(function (response) { return response; }, function (error) { // Do something with response error if (error.response.status == 404) { //找不到请求的服务 Message.error('找不到请求的服务!'); } else if (error.response.status == 401) { //没有token需要登录 Message.error('登录凭证失效,请重新登录!'); localStorage.removeItem("token"); window.location.href = "/login"; } else if (error.response.status == 403) { Message.error('token过期,请重新登录!'); localStorage.removeItem("token"); window.location.href = "/login"; } return Promise.reject(error); }) router.beforeEach((to, from, next) => { console.info("当前标签数:"+store.state.openTab.length) //限制最大标签页数 超过不跳转路由~ if(store.state.openTab.length>12){ Message.info('标签页已达到最大12页'); return } //根据字段判断是否进行路由过滤(即是否需要登录才能访问) if (localStorage.getItem('token') != null || to.name == 'login') { next() } else { console.info("token失效跳转登录页面") next({ path: '/login', }); } }); new Vue({ router, store, render: h => h(App) }).$mount('#app')
import Vue from 'vue' import Router from 'vue-router' import Home from './views/Home.vue' import main from './views/main.vue' import user from './views/user.vue' import page2 from './views/page2.vue' import page3 from './views/page3.vue' import page4 from './views/page4.vue' import login from './views/Login' Vue.use(Router) export default new Router({ routes: [ { path:'/login', component:login, name:'login' }, { path: '/', component: Home, redirect: 'main', children: [ { path: 'main', name: '首页', component: main }, { path: 'user', name: '用户管理', component: user }, { path: 'page2', name: 'page2', component: page2 }, { path: 'page3', name: 'page3', component: page3 }, { path: 'page4', name: 'page4', component: page4 } ] } ] }) const originalPush = Router.prototype.push Router.prototype.push = function push(location) { return originalPush.call(this, location).catch(err => err) }
到此vue主体框架完成…
github地址:https://github.com/123ac/tabRouter
– 未完待续
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。