irpas技术客

腾讯架构师带我写代码 —— vue真实企业实战分享_欧阳的技术_vue开发实战

未知 3538

Vue.js+element ui构造后台管理系统 阅读本文你会收获些什么?项目预览项目技术栈概览1. 路由配置2. 登录页配置3. API层配置(接口配置)4. 跨域处理5. 可以愉快的调接口了6. main.js中 你需要的配置7. 配置路由拦截8. 全局方法配置9. 父页面配置(home.vue)10. FAQ:你可能会碰到的问题及解决方案

阅读本文你会收获些什么? 不玩虚的,真实的企业项目实战技巧,可以直接拿过去用真实的接口调用,实现相关功能优秀的封装技巧(本项目在前腾讯前端架构师指导下构建)帮你踩坑,让你开发更加顺畅提供脱敏化的所有代码,让你不会存在一知半解 项目预览

登录页效果

登录时过渡效果

登录成功,跳转页面

左侧导航与右侧表格效果

你看到的是一个标准的后台管理系统

简洁的页面后面有强大的代码支持,请继续往下看

项目技术栈概览 开发工具:vscode(推荐使用的前端开发工具)Vue版本:V 2.6.11vue-router:V 3.2.0element ui版本:V 2.15.1接口调试:axios库 V 0.21.1vue-cli(脚手架)版本: V 4.5.0node版本:V 13.14.0node-sass:V 4.12.0sass-loader:V 8.0.2babel-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 中的引入与注册是否做好了)报某些模块找不到(查看文件存放路径与引入路径是否正确)其它问题(可以直接私信我,每天我会查看几次,看到就回复大家) 为了力求大家能看懂,不会出问题。此文花了很多精力完成(抽着红塔山,熬着夜)希望能帮到大家别忘了三连走一波啊~,点赞、评论、关注一下子看不完,可以收藏一下~再次感谢大家的支持,一定会持续输出优质好文~


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #vue开发实战 #Vuejselement #路由配置2 #登录页配置3 #API层配置接口配置4 #跨域处理5 #可以愉快的调接口了6