腾讯架构师带我写代码 —— vue真实企业实战分享

阅读本文你会收获些什么?

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

项目预览

  • 登录页效果
    在这里插入图片描述

  • 登录时过渡效果
    在这里插入图片描述

  • 登录成功,跳转页面

  • 左侧导航与右侧表格效果
    在这里插入图片描述

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

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

项目技术栈概览

  • 开发工具: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:你可能会碰到的问题及解决方案

  1. 某些东西安装出错(国外的网络可能不稳定,可以配置淘宝镜像)
  2. 某些东西未生效(请注意版本问题)
  3. vue.config.js文件不生效(此文件做出了任何改变,一定要重启服务,重新编译)
  4. 某些组件样式不对(查看 main.js 中的引入与注册是否做好了)
  5. 报某些模块找不到(查看文件存放路径与引入路径是否正确)
  6. 其它问题(可以直接私信我,每天我会查看几次,看到就回复大家)
  • 为了力求大家能看懂,不会出问题。此文花了很多精力完成(抽着红塔山,熬着夜)希望能帮到大家
  • 别忘了三连走一波啊~,点赞、评论、关注
  • 一下子看不完,可以收藏一下~
  • 再次感谢大家的支持,一定会持续输出优质好文~
    在这里插入图片描述