阅读本文你会收获些什么?
- 不玩虚的,真实的企业项目实战技巧,可以直接拿过去用
- 真实的接口调用,实现相关功能
- 优秀的封装技巧(本项目在前腾讯前端架构师指导下构建)
- 帮你踩坑,让你开发更加顺畅
- 提供脱敏化的所有代码,让你不会存在一知半解
项目预览
-
登录页效果
-
登录时过渡效果
-
登录成功,跳转页面
-
左侧导航与右侧表格效果
-
你看到的是一个标准的后台管理系统
-
简洁的页面后面有强大的代码支持,请继续往下看
项目技术栈概览
- 开发工具:vscode(推荐使用的前端开发工具)
- Vue版本:V 2.6.11
- vue-router:V 3.2.0
- element ui版本:V 2.15.1
- 接口调试:axios库 V 0.21.1
- vue-cli(脚手架)版本: V 4.5.0
- node版本:V 13.14.0
- node-sass:V 4.12.0
- sass-loader:V 8.0.2
- babel-eslint:V 10.1.0
1. 路由配置
router文件夹下,index.js 配置
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home,
meta: {
requireAuth: true
},
children: [
// 学生数据
{
path: '/home/studentData',
name: 'studentData',
meta: {
requireAuth: true
},
component: () => import('../views/studentData/studentData.vue')
},
// 老师数据
{
path: '/home/teacherData',
name: 'teacherData',
meta: {
requireAuth: true
},
component: () => import('../views/teacherData/teacherData.vue')
},
]
},
{
path: '/login',
name: 'login',
component: () => import('../views/login/login.vue'),
// 子路由
children: [
]
}
]
// 多次点击同一个导航时会报错,所以添加了这段代码
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location, onResolve, onReject) {
if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
return originalPush.call(this, location).catch(err => err)
}
const router = new VueRouter({
routes
})
// 别的地方会用到 router,所以将它导出去
export default router
- 说明:因为此类项目只有中间表格部分会切换,所以路由配置采用的是:父子路由模式
- 一个父路由:home.vue,包含:1. 左侧导航,2. 头部信息栏,3.表格部分的容器
- view文件目录配置如下:(如果你不太熟练,先跟着我的配置走)
2. 登录页配置
login.vue(当前是纯静态页面,稍后添加接口与方法)
<template>
<div class="login-box">
<div class="form-con">
<h2>Vue+element ui 管理系统实战</h2>
<div class="label">
<el-input placeholder="请输入账号" v-model="userName">
<template slot="prepend"><i class="el-icon-user"></i></template>
</el-input>
</div>
<div class="label">
<el-input placeholder="请输入密码" v-model="password" show-password @keyup.enter.native="loginFn()">
<template slot="prepend"><i class="el-icon-lock"></i></template>
</el-input>
</div>
<div class="label">
<el-button class="login-btn" type="primary" @click="loginFn()"
>登 录</el-button
>
</div>
</div>
</div>
</template>
<script>
export default {
name: "login",
data() {
return {
userName: "",
password: "",
verify: "",
};
},
};
</script>
<style lang="scss" scope>
.login-box {
width: 100%;
height: 100%;
display: flex;
text-align: center;
background: url("~@/assets/4.jpg") no-repeat;
background-size: 100% 100%;
.form-con {
h2 {
color: #ffffff;
// color: $colorRed;
}
width: 360px;
height: 420px;
margin: 150px auto auto auto;
.label {
margin: 40px 0;
.login-btn {
width: 100%;
}
}
}
}
</style>
3. API层配置(接口配置)
-
接口采用分层设计,为了方便维护,会有以下四个基础文件(这些是推荐必须要有的,其余有需求可以自己加)
-
其中前三个文件写完之后,基本不用去动,一劳永逸,爽的不行
-
思路:深度解耦与高度复用
3.1 service.js 配置
- 此文件负责和后台打交道,统一处理所有接口(各种拦截处理、状态处理等)
import axios from 'axios'
import vue from '../main.js'
// 从本地获取token
function getTokenByLocal() {
let token = sessionStorage.getItem('token');
return token;
}
const service = axios.create({
baseURL: '/sys',
// withCredentials: true,
timeout: 5000,
})
// 请求拦截
service.interceptors.request.use(
config => {
if (getTokenByLocal()) {
// 在此可以设置所有接口headers头部
config.headers['token'] = getTokenByLocal();
}else{
// window.location.href="/login";
}
return config
},
error => {
return Promise.reject(error)
}
)
// 响应拦截
service.interceptors.response.use(
response => {
let res = response.data;
// console.log(res);
// 状态码处理
if (res.code == '200') {
// location.href = "home/login";
}
// 如果为 -101 代表用户未登录
if(res.code == '-101'){
vue.$router.push('/login');
}
return Promise.resolve(res);
},
error => {
return Promise.reject(error)
}
)
export default service;
3.2 common.js 配置
- 此文件只做一件事情:统一处理项目中所有接口的传参处理
- 一般来说,不会有太多花里胡哨的传参
// 将service.js引入进来
import service from './service.js'
// post请求 80% 耦合度低 复用性高
export function requestOfPost(url, data){
return service.post(url, data);
}
3.3 api.js 配置
- 此文件为二次封装,加入处理异步的 promise
import {
requestOfPost} from './common.js'
export function postRequest(url, data){
return new Promise((resolve, reject) => {
requestOfPost(url, data).then(res => resolve(res))
.catch(error => reject(error))
})
}
3.4 url.j配置
- 此文件统一管理所有接口路径,不然项目里东一个西一个维护起来麻烦(架构师说的,咱也不敢反驳,仔细寻思了一下,这也是对的)
- 如果项目里的接口超过了一百个,可以拆分成两个
- 切记,加注释啊亲
const url = {
// 登录
login: '/login',
// 学生列表
getClassmates: '/getClassmates'
}
export default url;
4. 跨域处理
-
一般来说,本地开发都需要配置接口跨域,后台一般懒得处理(卑微前端的无力)
-
项目最外层添加文件: vue.config.js(这个文件只能这么命名,别的名字cli 服务不会识别)
-
配置如下
module.exports = {
devServer: {
compress: false,
open: true,
proxy: {
'/sys': {
// 代理地址
target: 'http://api.gebilaowang.com',
// websocket (一般用于即时通讯,游戏,这里不需要,所以不开)
ws: false,
// 是否允许跨域
changeOrigin: true,
// 重写
pathRewite: {
'/sys': '/'
}
}
}
}
}
5. 可以愉快的调接口了
login.vue 代码添加
<script>
// 此为全局定义的过渡方法
import {
loadingShow } from "../../common/js/common.js";
import url from "../../request/url.js";
import {
postRequest } from "../../request/api.js";
export default {
name: "login",
props: {
msg: String,
},
data() {
return {
userName: "",
password: "",
verify: "",
};
},
methods: {
// 登录方法
loginFn() {
if (!this.userName) {
this.msgFn("warning", "请输入账号名");
return;
} else if (!this.password) {
this.msgFn("warning", "请输入密码");
return;
} else {
// 加载动画
loadingShow();
let data = {
userName: this.userName,
passWord: this.password
};
postRequest(url.login, data).then(
(res) => {
// 动画隐藏
loadingHide();
if (res.code == 500) {
this.msgFn("error", res.msg);
return;
}
// token 存入sessionStorage
sessionStorage.setItem("token", res.token);
// 页面跳转
setTimeout(() => {
this.$router.push("/home/studentData");
}, 500);
},
(error) => {
console.log(error);
}
);
}
},
// 弹窗
msgFn(type, text) {
this.$message({
message: text,
type: type,
});
},
},
created() {
},
};
</script>
6. main.js中 你需要的配置
- 引入element ui中的元素并注册
- 一些全局的动画配置
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import {
Button,
Container,
Header,
Aside,
Main,
Footer,
Input,
Loading,
Message,
Menu,
Submenu,
MenuItem,
MenuItemGroup,
Dropdown,
DropdownMenu,
Table,
TableColumn,
DropdownItem,
Form,
FormItem,
Select,
Option,
OptionGroup,
DatePicker,
Pagination,
MessageBox,
Popover,
Tag,
Switch,
Dialog
} from 'element-ui';
Vue.use(global)
Vue.use(Button)
Vue.use(Container)
Vue.use(Header)
Vue.use(Aside)
Vue.use(Main)
Vue.use(Footer)
Vue.use(Input)
Vue.use(Menu)
Vue.use(Submenu)
Vue.use(MenuItem)
Vue.use(MenuItemGroup)
Vue.use(Dropdown)
Vue.use(Table)
Vue.use(TableColumn)
Vue.use(DropdownMenu)
Vue.use(DropdownItem)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Select)
Vue.use(Option)
Vue.use(OptionGroup)
Vue.use(DatePicker)
Vue.use(Pagination)
Vue.use(Popover)
Vue.use(Dialog);
Vue.use(Tag)
Vue.use(Switch)
Vue.use(Loading.directive);
// 添加全局方法
Vue.prototype.$loading = Loading.service;
Vue.prototype.$message = Message;
Vue.prototype.$confirm = MessageBox.confirm;
Vue.prototype.$msgbox = MessageBox;
Vue.config.productionTip = false;
let vue = new Vue({
router,
render: h => h(App)
}).$mount('#app')
export default vue;
7. 配置路由拦截
- 如果内容不多,可以放在main.js中
- 记得要引入 router
router.beforeEach((to, from, next) => {
// 以token为例
let token = sessionStorage.getItem('token');
// 需要验证之后才能进入 requireAuth
if (to.meta.requireAuth) {
if (token) {
next();
// 顺利进入
} else {
// 跳入到指定页面
next({
path: '/login'
})
}
} else {
// 顺利进入
next();
}
})
8. 全局方法配置
- 存放目录,如下(架构师说的,我又思考了一下,放这里是有道理的)
- 给大家一个全局方法做参考,后续的按照这个来就是(依旧很贴心)
import vue from '../../main.js'
// 遮罩层控制
export function loadingShow(close){
const loadingFade = vue.$loading({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
if(close){
loadingFade.close();
}
}
9. 父页面配置(home.vue)
- 我感觉大家可能会需要,所以代码还是贴出来(有一种需要是作者感觉你会需要)
<template>
<div class="container-big">
<el-container class="container">
<el-aside width="220px">
<el-menu class="menu" :default-active="index">
<el-submenu index="1">
<template slot="title"><i class="el-icon-s-home"></i>首页</template>
<el-menu-item index="1-1" @click="toRoute('/home/studentData', '学生数据')"
>学生数据</el-menu-item
>
</el-submenu>
<el-submenu index="2">
<template slot="title"
><i class="el-icon-s-cooperation"></i>教务处 / 管理</template
>
<el-submenu index="2-2">
<template slot="title">角色管理</template>
<el-menu-item
index="2-2-1"
@click="toRoute('/home/teacherData', '老师管理')"
>
老师管理</el-menu-item
>
<el-menu-item
index="2-2-2"
@click="toRoute()"
>
老师审核</el-menu-item
>
</el-submenu>
<el-submenu index="2-3">
<template slot="title">其它管理</template>
<el-menu-item
index="2-3-1"
@click="
toRoute()
"
>
教务管理</el-menu-item
>
<el-menu-item
index="2-3-3"
@click="
toRoute(
'/home/shopList',
' / 运营管理 / 店铺管理 / 店铺列表',
'店铺列表',
'2-3-3'
)
"
>
扫厕所管理</el-menu-item
>
</el-submenu>
</el-submenu>
</el-menu>
</el-aside>
<el-container>
<el-header style="text-align: right; font-size: 14px">
<div class="opration">
<el-dropdown>
<i class="el-icon-setting" style="margin-right: 15px"> 操作 </i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="logOut()"
>退出登录</el-dropdown-item
>
</el-dropdown-menu>
</el-dropdown>
<span>{
{
userName }}</span>
</div>
<div class="router-con">
<div>
<span>xxx公司运营管理系统</span>
</div>
<h2>{
{
pageName}}</h2>
</div>
</el-header>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script>
export default {
name: "home",
data() {
return {
userName: "",
index: "1-1",
pageName: ''
};
},
created() {
// 可以在此取权限相关数据
},
methods: {
// 跳转各个页面
toRoute(url, name) {
this.$router.push(url);
this.pageName = name;
},
// 退出登录
logOut() {
// 跳转至登录页面
this.$router.push("/login");
// 一般需要在此清除用户的一些缓存数据
},
},
};
</script>
<style lang="scss" scope>
.logo {
overflow: hidden;
padding: 10px 0px 10px 20px;
cursor: pointer;
img {
float: left;
margin-top: 10px;
}
p {
float: left;
margin: 12px 0 0 12px;
color: #fff;
font-weight: 600;
font-size: 20px;
vertical-align: middle;
animation: fade-in;
animation-duration: 0.3s;
}
}
.el-submenu .el-menu-item {
text-align: left;
height: 40px;
line-height: 40px;
// margin-left: 10px;
}
.menu {
.el-submenu {
.el-submenu__title {
padding-left: 30px;
}
.el-menu {
.el-submenu__title {
padding-left: 50px !important;
}
}
}
}
.el-header {
color: #333;
border-bottom: 1px solid #d3d3d3;
background: #fff;
padding: 0;
box-shadow: darkgrey 5px 0 5px 1px; //边框阴影
.opration {
height: 60px;
line-height: 60px;
padding: 0 20px;
cursor: pointer;
}
.router-con {
height: 60px;
// width: 100%;
padding: 20px;
border-bottom: 1px solid #d3d3d3;
text-align: left;
div {
margin-bottom: 10px;
}
}
}
.container-big {
width: 100%;
height: 100%;
}
.container {
width: 100%;
height: 100%;
.el-main {
margin-top: 100px;
background: #f2f2f2;
}
}
.el-aside {
color: #333;
height: 100%;
background: #001529;
overflow-y: auto;
overflow-x: hidden;
.el-menu {
border: none;
background: #001529;
li {
.el-submenu__title {
color: #f5f5f5;
text-align: left;
}
.el-submenu__title:hover {
color: #333;
}
ul {
li {
color: #dcdcdc;
.el-menu-item-group__title {
// color: #dcdcdc;
}
}
li.is-active {
color: #409eff;
}
li:hover {
color: #333;
}
}
}
}
}
</style>
- 代码中写了详细的注释,在这里不多做解释
- 可以全部复制丢你的文件里即可看到效果(很贴心吧)
10. FAQ:你可能会碰到的问题及解决方案
- 某些东西安装出错(国外的网络可能不稳定,可以配置淘宝镜像)
- 某些东西未生效(请注意版本问题)
- vue.config.js文件不生效(此文件做出了任何改变,一定要重启服务,重新编译)
- 某些组件样式不对(查看 main.js 中的引入与注册是否做好了)
- 报某些模块找不到(查看文件存放路径与引入路径是否正确)
- 其它问题(可以直接私信我,每天我会查看几次,看到就回复大家)
- 为了力求大家能看懂,不会出问题。此文花了很多精力完成(抽着红塔山,熬着夜)希望能帮到大家
- 别忘了三连走一波啊~,点赞、评论、关注
- 一下子看不完,可以收藏一下~
- 再次感谢大家的支持,一定会持续输出优质好文~
转载:https://blog.csdn.net/qq_35942348/article/details/117307350
查看评论