Vue后台搭建及登录模块

发布于:2022-08-07 ⋅ 阅读:(276) ⋅ 点赞:(0)

一.后台搭建完成后,安装依赖

yarn add element-ui axios moment

二.在根目录新建 .env.development 文件 

VUE_APP_BASE_API=在线接口地址

三.封装 axios 新增 src/utils/request.js 文件

import axios from "axios";
import { MessageBox, Message } from "element-ui";
import store from "@/store"; // 1、导入 store

// 创建axios实例
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API,
  //  process.env.VUE_APP_BASE_API
  timeout: 5000,
});

// request拦截器
service.interceptors.request.use(
  (config) => {
    const token = store.getters.token; // 2、从 getters 里面获取 token
    if (token) {
      // 让每个请求携带token
      config.headers["token"] = token;
    }
    return config;
  },
  (error) => {
    console.log(error);
    return Promise.reject(error);
  }
);

// response拦截器
service.interceptors.response.use(
  (response) => {
    const res = response.data;

    if (res.code !== 20000) {
      Message({
        message: res.message || "Error",
        type: "error",
        duration: 5 * 1000,
      });

      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        // 重新登录
        MessageBox.confirm(
          "你已被登出,可以取消继续留在该页面,或者重新登录",
          "确定登出",
          {
            confirmButtonText: "重新登录",
            cancelButtonText: "取消",
            type: "warning",
          }
        ).then(() => {
          // 3、通过 store 的 dispatch 方法请求 auth/resetToken
          store.dispatch("auth/resetToken").then(() => {
            location.reload();
          });
        });
      }
      return Promise.reject(new Error(res.message || "Error"));
    } else {
      return res;
    }
  },
  (error) => {
    console.log("err" + error);
    Message({
      message: error.message,
      type: "error",
      duration: 5 * 1000,
    });
    return Promise.reject(error);
  }
);

export default service;

四.配置全局过滤器  新建 src/filters/index.js 文件

import moment from "moment";

/**
 * "Fri Dec 10 2021 01:01:49 GMT+0800 (China Standard Time)" => "2021年12月10日凌晨1点01分"
 * @param {number} num
 */
export function dateFormat(value) {
  moment.locale("zh-cn");
  return moment(value).format("LLL");
}

/**
 * 10000 => "10,000"
 * @param {number} num
 */
export function thousandFormat(num) {
  return (+num || 0)
    .toString()
    .replace(/^-?\d+/g, (m) => m.replace(/(?=(?!\b)(\d{3})+$)/g, ","));
}

五.CSS文件  新建 src/styles/common.scss 文件

a {
  color: #1f99b0;
  text-decoration: none;
}

.clear:after {
  display: block;
  content: "clear";
  height: 0;
  clear: both;
  overflow: hidden;
  visibility: hidden;
}

.thumb {
  max-height: 65px;
  max-width: 120px;
}

.gray {
  background: #e6e8ec;
}

新建 src/styles/element-ui.scss 文件

.el-menu-item.is-active {
  color: #1f99b0 !important;
}

.el-switch.is-checked .el-switch__core {
  border-color: #1f99b0 !important;
  background-color: #1f99b0 !important;
}

.el-button--primary {
  color: #fff !important;
  background-color: #1f99b0 !important;
  border-color: #1f99b0 !important;
}

.el-menu-item:focus, .el-menu-item:hover {
  background-color: #e1f0f0 !important;
}

.el-submenu__title:hover {
  background-color: #e1f0f0 !important;
}

.el-tag {
  background-color: #e1f0f0;
  border-color: #e1f0f0;
  color: #1f99b0;
}

.el-button.el-button--default:focus, .el-button.el-button--default:hover {
  color: #1f99b0;
  border-color: #dcdef6;
  background-color: #e1f0f0;
}

.el-radio__input.is-checked .el-radio__inner {
  border-color: #1f99b0;
  background: #1f99b0;
}

.el-radio__input.is-checked + .el-radio__label {
  color: #1f99b0;
}


.el-checkbox__input.is-checked .el-checkbox__inner, .el-checkbox__input.is-indeterminate .el-checkbox__inner {
  background-color: #1f99b0;
  border-color: #1f99b0;
}

.el-button--text{
  color: #1f99b0;
}

.el-button--text:focus, .el-button--text:hover {
  color: #1f99b0;
  text-decoration: underline;
}

.el-input.is-active .el-input__inner, .el-input__inner:focus {
  border-color: #1f99b0;
  outline: 0;
}

.el-cascader-node.in-active-path, .el-cascader-node.is-active, .el-cascader-node.is-selectable.in-checked-path {
  color: #1f99b0;
}

.el-radio__inner:hover {
  border-color: #1f99b0;
}

新建 src/styles/layouts.scss 文件

html, body {
  height: 100%;
  margin: 0;
}

#app {
  height: 100%;
}

.el-container, .el-aside, .el-aside .el-col, .el-menu {
  height: 100%;
}

.el-header, .el-footer {
  background-color: #e1f0f0;
  color: #333;
  height: 60px;
  line-height: 60px;
}

.el-header {
  position: fixed;
  top: 0;
  width: 100%;
  z-index: 2;

  .logo {
    height: 40px;
    margin-top: 10px;
    float: left;
  }

  h1 {
    margin: 0 0 0 20px;
    font-size: 18px;
    font-weight: normal;
    float: left;
    line-height: 60px;;
  }
}

.el-footer {
  position: fixed;
  bottom: 0;
  width: 100%;
  z-index: 2;
  text-align: center;
}

.el-aside {
  position: fixed;
  top: 60px;
  width: 100%;
  z-index: 2;
  background-color: #e1f0f0;
  color: #333;
  line-height: 200px;
}

.el-main {
  color: #333;
  overflow: visible;
  margin: 60px 0 0 200px;
  padding-bottom: 80px;
}

.user-info {
  float: right;
}

新建 src/styles/table.scss 文件

.sort-input input{
  width: 50px;
  text-align: center;
}

.el-table .cell .delete{
  margin-left: 10px;
}

新建 src/styles/tree.scss 文件

.custom-tree-node {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 14px;
  padding-right: 8px;
}

新建 src/styles/uploads.scss 文件

.image-uploader .el-upload {
  border: 1px dashed #d9d9d9;
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
}

.image-uploader .el-upload:hover {
  border-color: #409EFF;
}

.image-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 178px;
  height: 178px;
  line-height: 178px !important;
  text-align: center;
}

新建 src/styles/index.scss 文件

@import './layouts.scss';
@import './common.scss';
@import './element-ui.scss';
@import './table.scss';
@import './tree.scss';
@import './uploads.scss';

五.布局模板

新建 src/views/layouts/components/NavBarView.vue 组件 

<template>
  <el-header>
    <img
      src="https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png"
      alt="百度Logo"
      class="logo"
    />
    <h1>后台管理</h1>

    <div class="user-info">
      <el-dropdown @command="handleCommand">
        <i class="el-icon-setting" style="margin-right: 15px"></i>
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item command="logout">安全退出</el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
      <span>{{ username }}</span>
    </div>
  </el-header>
</template>
<script>
// 1、引入 mapGetters 辅助函数
import { mapGetters } from "vuex";

export default {
  // 2、把 username 这个 Getters 映射过来,最后在模板中渲染
  computed: mapGetters(["username"]),
  methods: {
    // 绑定 command 事件对应的方法 handleCommand
    async handleCommand(command) {
      // 若 command 的值为 logout,请求退出接口,跳转到登录页
      if (command === "logout") {
        console.log(123);
        await this.$store.dispatch("auth/resetToken");
        await this.$router.push("/sign_in");
      }
    },
  },
};
</script>

 新建 src/views/layouts/components/SideBar.vue 组件

<template>
  <el-aside width="200px">
    <el-col>
      <el-menu :router="true" default-active="/" class="el-menu-vertical-demo">
        <el-menu-item index="/">
          <i class="el-icon-pie-chart"></i>
          <span slot="title">仪表盘</span>
        </el-menu-item>

        <el-submenu index="article_manage">
          <template slot="title">
            <i class="el-icon-document"></i>
            <span>内容管理</span>
          </template>
          <el-menu-item index="/list">栏目管理</el-menu-item>
          <el-menu-item index="/articles">文章管理</el-menu-item>
        </el-submenu>

        <el-submenu index="advert_manage">
          <template slot="title">
            <i class="el-icon-picture-outline-round"></i>
            <span>广告管理</span>
          </template>
          <el-menu-item index="/ad_categories">广告分类</el-menu-item>
          <el-menu-item index="/ads">广告管理</el-menu-item>
        </el-submenu>

        <el-submenu index="system_manage">
          <template slot="title">
            <i class="el-icon-setting"></i>
            <span>系统管理</span>
          </template>
          <el-menu-item index="/permissions">菜单与权限</el-menu-item>
          <el-menu-item index="/roles">用户组</el-menu-item>
          <el-menu-item index="/users">用户</el-menu-item>
        </el-submenu>
      </el-menu>
    </el-col>
  </el-aside>
</template>

新建 src/views/layouts/IndexView.vue 组件

 

<template>
  <el-container>
    <Navbar />
    <el-container>
      <Sidebar />
      <el-container>
        <el-main>
          <router-view />
        </el-main>
        <el-footer> 123132213231222222222 </el-footer>
      </el-container>
    </el-container>
  </el-container>
</template>

<script>
import Navbar from "./components/NavBarView";
import Sidebar from "./components/SideBarView";

export default {
  components: {
    Navbar,
    Sidebar,
  },
};
</script>

 修改 src/App.vue 文件

<template>
  <div id="app">
    <router-view />
  </div>
</template>

 修改 src/views/HomeView.vue 文件

<template>
  <div class="home">
    <el-breadcrumb separator-class="el-icon-arrow-right">
      <el-breadcrumb-item :to="{ path: '/' }">首页
      <el-breadcrumb-item>仪表盘</el-breadcrumb-item>
    </el-breadcrumb>

    <el-divider></el-divider>

    <el-card class="box-card"
      ><div><echartsView /></div
    ></el-card>
  </div>
</template>

<script>
export default {
  name: "HomeView",
};
</script>

<style scoped lang="scss">
.info {
  font-size: 14px;
}
</style>

修改 src/main.js 文件

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 "@/styles/index.scss";
// 路由守卫
import "./permission";

// 全局过滤器
import { dateFormat, thousandFormat } from "./filters";

// 注册全局过滤器,一定要放在new Vue之前
Vue.filter("dateFormat", dateFormat);
Vue.filter("thousandFormat", thousandFormat);

Vue.use(ElementUI);
Vue.config.productionTip = false;

new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount("#app");

 修改 src/router/index.js 文件

import Vue from "vue";
import VueRouter from "vue-router";
import Layout from "@/views/layouts/IndexView";

Vue.use(VueRouter);

const routes = [
  {
    path: "/sign_in",
    component: () => import("@/views/auth/SignInView"),
    meta: { title: "登录" },
  },
  {
    path: "/",
    component: Layout,
    children: [
      {
        path: "",
        component: () => import("@/views/HomeView"),
        meta: { title: "首页" },
      },
      {
        path: "/list",
        name: "list",
        component: () => import("@/views/list/ListView"),
        meta: { title: "列表" },
      },
      {
        path: "/list/:id",
        name: "index",
        component: () => import("@/views/list/IndexView"),
        meta: { title: "列表ID" },
      },
    ],
  },
  // {
  //   path: "/about",
  //   name: "about",
  //   // route level code-splitting
  //   // this generates a separate chunk (about.[hash].js) for this route
  //   // which is lazy-loaded when the route is visited.
  //   component: () =>
  //     import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
  // },
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes,
});

export default router;

 封装登录接口 创建api文件夹 下auth.js

import request from "@/utils/request";

// 登录
export function login(data) {
  return request({
    url: "/admin/auth/login",
    method: "post",
    data,
  });
}

 创建登录组件及功能实现  新建 src/views/auth/SignIn.vue

<template>
  <div class="sign-in">
    <el-row>
      <el-col :span="12" :offset="6">
        <h1>后台管理系统</h1>
        <el-form
          :model="loginForm"
          :rules="loginRules"
          ref="ruleForm"
          label-width="100px"
          class="demo-ruleForm"
        >
          <el-form-item label="用户名" prop="username">
            <el-input v-model="loginForm.username"></el-input>
          </el-form-item>
          <el-form-item label="密码" prop="password">
            <el-input v-model="loginForm.password" type="password"></el-input>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="handleLogin('ruleForm')">
              立即登录
            </el-button>
            <el-button @click="resetForm('ruleForm')">重置</el-button>
          </el-form-item>
        </el-form>
      </el-col>
    </el-row>
  </div>
</template>

<script>
// import { login } from "@/api/auth";

export default {
  data() {
    return {
      loginForm: {
        username: "",
        password: "",
      },
      loginRules: {
        name: [{ required: true, message: "请输入用户名", trigger: "blur" }],
        password: [{ required: true, message: "请输入密码", trigger: "blur" }],
      },
    };
  },
  methods: {
    handleLogin(formName) {
      this.$refs[formName].validate(async (valid) => {
        if (valid) {
          // const res = await login(this.loginForm);
          await this.$store.dispatch("auth/login", this.loginForm);
          await this.$router.push({ path: "/" });
        }
      });
    },
    resetForm(formName) {
      this.$refs[formName].resetFields();
    },
  },
};
</script>

<style scoped lang="scss">
.sign-in {
  padding-top: 100px;
}

h1 {
  text-align: center;
}
</style>

1.封装 cookie 文件 

yarn add js-cookie

2、新建 utils/auth.js 

import Cookies from "js-cookie";

// cookie中key的命名
const TokenKey = "Admin-Token";

/**
 * 获取cookie中的token
 * @returns {*}
 */
export function getToken() {
  return Cookies.get(TokenKey);
}

/**
 * 设置token到cookie中
 * @param token
 * @param remember
 * @returns {*}
 */
export function setToken(token, remember = false) {
  // 记住我
  if (remember) {
    return Cookies.set(TokenKey, token, { expires: 7 });
  }

  return Cookies.set(TokenKey, token);
}

/**
 * 删除cookie中的token
 * @returns {*}
 */
export function removeToken() {
  return Cookies.remove(TokenKey);
}

创建 store 登录状态管理器   1、新建 src/store/modules/auth.js

import { getToken, setToken, removeToken } from "@/utils/auth";
import { login } from "@/api/auth";
import { getMe } from "@/api/users";

const state = () => ({
  token: getToken(),
  username: "",
  role: {},
  routers: {},
  menus: {},
});

const mutations = {
  SET_TOKEN: (state, token) => {
    state.token = token;
  },
  SET_USERNAME: (state, username) => {
    state.username = username;
  },
  SET_ROLE: (state, role) => {
    state.role = role;
  },
  SET_ROUTERS: (state, routers) => {
    state.routers = routers;
  },
  SET_MENUS: (state, menus) => {
    state.menus = menus;
  },
};

const actions = {
  async login({ commit }, payload) {
    const { username, password } = payload;
    const res = await login({
      username: username.trim(),
      password: password.trim(),
    });
    const { data } = res;
    setToken(data.token);
    commit("SET_TOKEN", data.token);
  },
  // 1、getInfo函数用于获取用户信息,用户组
  async getInfo({ commit }) {
    // 2、请求 getMe 接口
    const res = await getMe();
    console.log(res); // 这个接口给我们返回了user,role,routers,menus这些数据
    const { data } = res;
    const { user, role, routers, menus } = data;
    // 4、把所有数据全部提交给 mutations
    commit("SET_USERNAME", user.username);
    commit("SET_ROLE", role);
    commit("SET_ROUTERS", routers);
    commit("SET_MENUS", menus);
    return data;
  },
  resetToken({ commit }) {
    commit("SET_TOKEN", "");
    commit("SET_USERNAME", "");
    commit("SET_ROLE", "");
    commit("SET_ROUTERS", "");
    commit("SET_MENUS", "");
    removeToken();
  },
};

export default {
  namespaced: true,
  state,
  actions,
  mutations,
};

2、新建 store/getters.js

const getters = {
  token: (state) => state.auth.token,
  username: (state) => state.auth.username,
  role: (state) => state.auth.role,
  routers: (state) => state.auth.routers,
  menus: (state) => state.auth.menus,
};

export default getters;

 3、在 src/store/index.js 中

import Vue from "vue";
import Vuex from "vuex";
import auth from "./modules/auth"; // 1、引入 auth
import getters from "@/store/getters"; // 2、引入 getters

Vue.use(Vuex);

export default new Vuex.Store({
  getters, // 3、注册 getters,由于getters是全局属性,所以我们注册到 Store 实例中
  modules: {
    auth, // 4、注册 auth
  },
});

 登录实现  

1、修改 src/store/modules/auth.js 代码,按步骤理解  前面已经修改

2、修改 views/auth/SignIn.vue 代码   前面已修改

验证 token

1、修改request.js 前面第三部已经修改

2、新建 src/permission.js

import router from "@/router";
import { getToken } from "@/utils/auth";
import store from "./store";
import { Message } from "element-ui";

router.beforeEach(async (to, from, next) => {
  document.title = `${to.meta.title} - 后台`;

  const hasToken = getToken();
  if (hasToken) {
    if (to.path === "/sign_in") {
      next({ path: "/" });
    } else {
      try {
        // 3、调用 auth/getInfo 函数获取用户信息
        await store.dispatch("auth/getInfo");
        //  如果用户信息出错了。表示token可能过期了,直接清空,跳转到登录页
      } catch (error) {
        // 删除token,跳转到登录页
        await store.dispatch("auth/resetToken");
        Message.error(error || "Has Error");
        next("/sign_in");
      }
    }
    next();
  } else {
    if (to.path === "/sign_in") {
      next();
    } else {
      next("/sign_in");
    }
  }
});

3、在 main.js 中  前面已经修改

获取用户信息

1、修改 src/store/modules/auth.js 中的代码  前面已经修改

2、在 src/permission.js 中调用  前面已经修改

3、显示用户名,在 views/layout/components/NavBar.vue 中  前面已经修改

 

 

 

 

 

 

 

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到