vue2后台管理系统通用模板
依赖包:router/index.js:vuexstore/index.js:axiosapi/axios.js:main.js登录页布局home.vue:404页面exception/NotFound.vue:
·
依赖包:

路由
router/index.js:
import Vue from "vue";
import VueRouter from "vue-router";
import { Message } from "element-ui";
import jwt_decode from "jwt-decode";
Vue.use(VueRouter);
const WHITE_LIST = ["/login"];
const routes = [
{
path: "/",
name: "home",
component: () => import("@/views/home.vue"),
redirect: "/userStatistics",
children: [
{
path: "/official_website_userlist",
name: "official_website_userlist",
component: () => import("@/views/official_website_userlist"),
},
{
path: "/userStatistics",
name: "userStatistics",
component: () => import("@/views/userStatistics"),
},
{
path: "/orderStatistics",
name: "orderStatistics",
component: () => import("@/views/orderStatistics"),
},
],
},
{
path: "/login",
name: "login",
component: () => import("@/views/login"),
},
{
path: "*",
component: () => import("@/views/exception/NotFound"),
},
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
});
// 路由守卫
router.beforeEach((to, from, next) => {
let token = localStorage.getItem("token");
if (WHITE_LIST.includes(to.path)) {
next();
} else if (token) {
if (jwt_decode(token).exp < parseInt(new Date().getTime() / 1000)) {
// 已过期
Message.warning("登录状态已过期, 请重新登录");
localStorage.clear();
next({
path: "/login",
query: to.path === "/" ? {} : { from: to.path },
});
} else {
next();
}
} else {
next({
path: "/login",
query: to.path === "/" ? {} : { from: to.path },
});
}
});
export default router;
vuex
store/index.js:
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
// 当前登录用户信息
user_info: {
username: "",
},
},
mutations: {
// 设置登录用户的信息
setUserInfo(state, user_info) {
state.user_info = {
username: user_info.username,
};
},
},
actions: {},
modules: {},
});
axios
api/axios.js:
/**
* axios实例
*/
import axios from "axios";
import { Message } from "element-ui";
import router from "@/router";
const service = axios.create({
baseURL: `${config_settings.address_node}`,
});
service.defaults.timeout = 1000 * 60;
service.defaults.headers.post["Content-Type"] =
"application/x-www-form-urlencoded;charset=utf-8";
/* 添加请求拦截器 */
service.interceptors.request.use(
(config) => {
// 添加token到header
const token = localStorage.getItem("token");
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
/* 添加响应拦截器 */
service.interceptors.response.use(
(res) => {
/**
* 以下几种情况都会报401:
* 1:无token
* 2:token被篡改
* 3:token过期
*/
if (res.data.code === 401) {
Message.warning("身份验证过期, 请重新登录");
router.push({
path: "/login",
query: { from: window.location.pathname },
});
localStorage.clear();
}
return res;
},
(error) => {
return Promise.resolve(error);
}
);
export default service;
main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
Vue.config.productionTip = false;
import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";
Vue.use(ElementUI);
//全局注册axios
import http from "@/api/axios.js";
Vue.prototype.$axios = http;
import dayjs from "dayjs";
Vue.prototype.$dayjs = dayjs;
import Cookies from "js-cookie";
Vue.prototype.$cookie = Cookies;
import "xe-utils";
import VXETable from "vxe-table";
import "vxe-table/lib/style.css";
Vue.use(VXETable);
Vue.prototype.$VXETable = VXETable;
import "echarts";
import ECharts from "vue-echarts";
Vue.component("VueEcharts", ECharts);
Vue.filter("formatDate", function(value) {
return dayjs(value).format("YYYY-MM-DD HH:mm:ss");
});
new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#app");
登录页

<template>
<div class="login">
<el-form
ref="form"
size="large"
:model="form"
:rules="rules"
class="login-form"
@keyup.enter.native="submit"
>
<h4
style="
color: black;
font-weight: 400;
text-align: center;
margin: 0 0 25px 0;
"
>
用户登录
</h4>
<el-form-item prop="username">
<el-input
clearable
v-model="form.username"
prefix-icon="el-icon-user"
placeholder="用户名"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
show-password
v-model="form.password"
prefix-icon="el-icon-lock"
placeholder="密码"
/>
</el-form-item>
<div class="el-form-item">
<el-checkbox v-model="form.remember"> 记住密码 </el-checkbox>
</div>
<div class="el-form-item">
<el-button
size="large"
type="primary"
:loading="loading"
@click="submit"
style="display: block; width: 100%"
>
{{ loading ? "登录中..." : "登录" }}
</el-button>
</div>
</el-form>
</div>
</template>
<script>
export default {
name: "login",
data() {
return {
loading: false,
form: {
username: "",
password: "",
remember: true,
},
};
},
mounted() {
this.form.username = this.$cookie.get("username");
this.form.password = this.$cookie.get("password");
},
computed: {
// 表单验证规则
rules() {
return {
username: [
{
required: true,
message: "用户名不能为空",
type: "string",
trigger: "blur",
},
],
password: [
{
required: true,
message: "密码不能为空",
type: "string",
trigger: "blur",
},
],
};
},
// 当前语言
},
created() {},
methods: {
/* 提交 */
submit() {
this.$refs.form.validate(async (valid) => {
if (!valid) {
return false;
}
this.loading = true;
if (this.form.remember) {
this.$cookie.set("username", this.form.username, {
expires: 30,
});
this.$cookie.set("password", this.form.password, {
expires: 30,
});
} else {
this.$cookie.remove("username");
this.$cookie.remove("password");
}
try {
this.goHome();
//登录
let { data } = await this.$axios.post(`/login`, {
username: this.form.username,
// password: md5(this.form.password).toLowerCase(),
password: this.form.password,
});
if (data.code == 0) {
localStorage.setItem("token", data.token);
this.$message({
showClose: true,
message: "登录成功!",
type: "success",
});
this.goHome();
} else {
this.$message({
showClose: true,
message: data.message,
type: "error",
});
}
this.loading = false;
} catch (error) {
this.loading = false;
}
});
},
/* 跳转到首页 */
goHome() {
this.$router.push(this.$route?.query?.from ?? "/").catch(() => {});
},
},
};
</script>
<style lang="scss" scoped>
.login {
box-sizing: border-box;
// background-image: url(~@/assets/bg_login.jpg);
background-image: url(https://file.rivermap.cn/lj/RivermapPanDownload/bg-login.jpg);
background-repeat: no-repeat;
background-size: cover;
min-height: 100vh;
.login-form {
width: 360px;
max-width: 100%;
padding: 25px 30px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
box-sizing: border-box;
border-radius: 4px;
z-index: 2;
background-color: white;
}
}
</style>
布局

home.vue:
<template>
<el-container class="homeContainer">
<el-header class="headerContainer">
<div
class="hidden-xs-only"
style="
display: flex;
align-items: center;
margin-left: 20px;
cursor: pointer;
"
>
<img
src="http://file.rivermap.cn/lj/RivermapPanDownload/logo.svg"
style="width: 30px; height: 30px; color: white"
/>
<div style="margin: 0 10px; font-weight: bold; letter-spacing: 2px">
xxx平台
</div>
<li
style="margin-left: 5px;padding:0 20px"
@click="isShowAside = !isShowAside"
>
<i v-if="isShowAside" class="el-icon-s-unfold"></i>
<i v-else class="el-icon-s-fold"></i>
</li>
</div>
<div
style="
margin-left: auto;
padding: 0 10px;
display: flex;
align-items: center;
"
>
<li style="margin-right: 5px" @click="screenfull()">
<i class="vxe-icon-zoom-out fullscreen"></i>
</li>
<el-dropdown style="margin-right: 15px" size="small">
<div class="el-dropdown-link" style="color: white; cursor: pointer">
<li>
<span>{{ this.$store.state.user_info.username }}</span
><i class="el-icon-arrow-down el-icon--right"></i>
</li>
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="loginOut()"
><i class="el-icon-switch-button"></i
><span>退出登录</span></el-dropdown-item
>
</el-dropdown-menu>
</el-dropdown>
</div>
</el-header>
<el-container class="bottomContainer">
<el-aside
class="asideContainer"
style="width: 240px;"
v-show="isShowAside"
>
<el-menu
:default-active="$route.path"
class="el-menu-vertical-demo"
text-color="white"
active-text-color="white"
>
<el-menu-item
index="/official_website_userlist"
@click="$router.push({ path: '/official_website_userlist' })"
>
<i
class="el-icon-tickets"
style="font-size: 32px; margin-right: 10px; color: white"
></i>
<span slot="title">用户列表</span>
</el-menu-item>
<el-menu-item
index="/userStatistics"
@click="$router.push({ path: '/userStatistics' })"
>
<i
class="el-icon-user"
style="font-size: 32px; margin-right: 10px; color: white"
></i>
<span slot="title">用户统计</span> </el-menu-item
><el-menu-item
index="/orderStatistics"
@click="$router.push({ path: '/orderStatistics' })"
>
<i
class="el-icon-date"
style="font-size: 32px; margin-right: 10px; color: white"
></i>
<span slot="title">订单统计</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-main class="mainContainer">
<keep-alive :include="cacheRoute">
<router-view></router-view>
</keep-alive>
</el-main>
</el-container>
</el-container>
</template>
<script>
import screenfull from "screenfull";
export default {
components: {},
data() {
return {
isShowAside: true,
cacheRoute: [
"userStatistics",
"official_website_userlist",
"orderStatistics",
],
};
},
props: {},
created() {},
mounted() {
// 获取并存储用户信息
this.getUserInfo();
},
computed: {},
methods: {
async getUserInfo() {
let data = await this.$axios.get("/getUserInfo");
this.$store.commit("setUserInfo", data.data.data);
},
// 全屏事件
screenfull() {
screenfull.toggle();
},
//退出登录
loginOut() {
this.$confirm("确定要退出登录吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
localStorage.clear();
this.$router.push({ path: "/login" });
});
},
},
};
</script>
<style lang="scss" scoped>
.homeContainer {
li {
list-style-type: none;
padding: 0 10px;
cursor: pointer;
&:hover {
background-color: rgba(180, 180, 180, 0.26);
}
}
.headerContainer {
background-color: black;
height: 60px;
line-height: 60px;
display: flex;
}
.bottomContainer {
background-color: rgb(255, 255, 255);
.asideContainer {
background-color: #246cbb;
height: calc(100vh - 60px);
overflow-x: hidden;
overflow-y: auto;
//展开时宽度
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 240px;
}
.el-menu {
border-right: solid 0px #e6e6e6;
height: 100%;
// background: url(~@/assets/bg_aside.png);
background: #246cbb;
background-repeat: no-repeat;
background-size: cover;
.el-menu-item {
&:hover {
background-color: rgba(5, 5, 5, 0.137) !important;
}
}
.el-menu-item.is-active {
border-bottom: 0 !important;
background-color: #6295ce !important ;
}
}
}
.mainContainer {
padding: 10px;
height: calc(100vh - 60px);
overflow-y: auto;
}
}
}
</style>
404页面

exception/NotFound.vue:
<template>
<div class="wscn-http404-container">
<div class="wscn-http404">
<div class="pic-404">
<img
class="pic-404__parent"
src="https://lj-common.oss-cn-chengdu.aliyuncs.com/404.png"
alt="404"
/>
<img
class="pic-404__child left"
src="https://lj-common.oss-cn-chengdu.aliyuncs.com/404_cloud.png"
alt="404"
/>
<img
class="pic-404__child mid"
src="https://lj-common.oss-cn-chengdu.aliyuncs.com/404_cloud.png"
alt="404"
/>
<img
class="pic-404__child right"
src="https://lj-common.oss-cn-chengdu.aliyuncs.com/404_cloud.png"
alt="404"
/>
</div>
<div class="bullshit">
<div class="bullshit__oops">OOPS!</div>
<div class="bullshit__headline">{{ message }}</div>
<div class="bullshit__info">
Please check that the URL you entered is correct, or click the button
below to return to the homepage.
</div>
<a class="bullshit__return-home" @click="$router.push({ path: '/' })"
>Back to home</a
>
</div>
</div>
</div>
</template>
<script>
export default {
name: "NotFound",
computed: {
message() {
return "The webmaster said that you can not enter this page...";
},
},
};
</script>
<style lang="scss" scoped>
.wscn-http404-container {
transform: translate(-50%, -50%);
position: absolute;
top: 40%;
left: 50%;
}
.wscn-http404 {
position: relative;
width: 1200px;
padding: 0 50px;
overflow: hidden;
.pic-404 {
position: relative;
float: left;
width: 600px;
overflow: hidden;
&__parent {
width: 100%;
}
&__child {
position: absolute;
&.left {
width: 80px;
top: 17px;
left: 220px;
opacity: 0;
animation-name: cloudLeft;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
&.mid {
width: 46px;
top: 10px;
left: 420px;
opacity: 0;
animation-name: cloudMid;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1.2s;
}
&.right {
width: 62px;
top: 100px;
left: 500px;
opacity: 0;
animation-name: cloudRight;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
@keyframes cloudLeft {
0% {
top: 17px;
left: 220px;
opacity: 0;
}
20% {
top: 33px;
left: 188px;
opacity: 1;
}
80% {
top: 81px;
left: 92px;
opacity: 1;
}
100% {
top: 97px;
left: 60px;
opacity: 0;
}
}
@keyframes cloudMid {
0% {
top: 10px;
left: 420px;
opacity: 0;
}
20% {
top: 40px;
left: 360px;
opacity: 1;
}
70% {
top: 130px;
left: 180px;
opacity: 1;
}
100% {
top: 160px;
left: 120px;
opacity: 0;
}
}
@keyframes cloudRight {
0% {
top: 100px;
left: 500px;
opacity: 0;
}
20% {
top: 120px;
left: 460px;
opacity: 1;
}
80% {
top: 180px;
left: 340px;
opacity: 1;
}
100% {
top: 200px;
left: 300px;
opacity: 0;
}
}
}
}
.bullshit {
position: relative;
float: left;
width: 300px;
padding: 30px 0;
overflow: hidden;
&__oops {
font-size: 32px;
font-weight: bold;
line-height: 40px;
color: #1482f0;
opacity: 0;
margin-bottom: 20px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
&__headline {
font-size: 20px;
line-height: 24px;
color: #222;
font-weight: bold;
opacity: 0;
margin-bottom: 10px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.1s;
animation-fill-mode: forwards;
}
&__info {
font-size: 13px;
line-height: 21px;
color: grey;
opacity: 0;
margin-bottom: 30px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.2s;
animation-fill-mode: forwards;
}
&__return-home {
display: block;
float: left;
width: 110px;
height: 36px;
background: #1482f0;
border-radius: 100px;
text-align: center;
color: #ffffff;
opacity: 0;
font-size: 14px;
line-height: 36px;
cursor: pointer;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.3s;
animation-fill-mode: forwards;
}
@keyframes slideUp {
0% {
transform: translateY(60px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
}
}
</style>
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)