Front-end and back-end separation simple project--ant blog--front-end part

Original URL: Simple project of front-end and back-end separation -- ant blog -- front-end part - IT's blog - CSDN Blog

Introduction

illustrate

        This article introduces a simple project I developed from the front-end and back-end developed from 0--Ant Blog. This blog post covers the front-end part.

        This project is a full-stack project, developed using mainstream and cutting-edge technology stacks. Although the project is small, it has all the internal organs.

        I will make a video later to explain this project in detail. The link will be posted to this post when the video is finished.

Project Introduction

See: Front-end and back-end separation of simple projects--Ant Blog--Introduction

Project source code

gitee address: https://gitee.com/knifeedge/ant_blog

The ant_frontend directory is the code for the frontend part.

technology stack

  1. vue(^2.6.11)
  2. vue-router(^3.2.0)
  3. vuex(^3.4.0)
  4. element-uI(^2.15.0)
  5. github-markdown-css(^4.0.0)
  6. markdown-it(^12.0.4)
  7. mavon-editor(^2.9.1)

Project structure

Overview

  1. Business part (views folder): divided by modules;
  2. Public parts (axio.js, permission.js): global request interception, global response interception, permission processing (whether token is added)
  3. Components section (components folder): sidebar, topbar
  4. Plugin part (router, store folder): vue-router plugin, vuex plugin

Project overview

dependencies and configuration

rely

package.json

{
  "name": "ant-frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build"
  },
  "dependencies": {
    "axios": "^0.21.1",
    "core-js": "^3.6.5",
    "element-ui": "^2.15.0",
    "github-markdown-css": "^4.0.0",
    "markdown-it": "^12.0.4",
    "mavon-editor": "^2.9.1",
    "vue": "^2.6.11",
    "vue-router": "^3.2.0",
    "vuex": "^3.4.0"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^4.5.0",
    "@vue/cli-service": "^4.5.0",
    "vue-template-compiler": "^2.6.11"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}

configure

Development server configuration

vue.config.js

module.exports = {
    devServer: {
        proxy: {
            '/': {
                target: 'http://localhost:9000/',
                // ws: true,
                changeOrigin: true
            }
        }
    }
}

business part

Home

Home.view

<template>
  <div class="home-container">
    <top-header></top-header>
    <div class="main-container">
      <left-aside class="left">
        <div class="blogger">
          博主列表
        </div>
      </left-aside>
      <user-list class="middle"></user-list>
    </div>

  </div>
</template>

<script>
import TopHeader from "@/components/TopHeader";
import LeftAside from "@/components/LeftAside";
import UserList from "@/views/user/UserList";

export default {
  name: "Home",
  components: {TopHeader, LeftAside, UserList},
}
</script>

<style scoped>

.main-container {
  display: flex;
  margin: 0 50px 0 50px;
}

.middle {
  flex: 1;
}

.blogger {
  font-size: 20px;
  padding: 10px 10px;
  background-color: white;
}

</style>

login page

Login.vue

<template>
  <div>

    <el-container>
      <el-header>
        <img class="logo" src="https://www.markerhub.com/dist/images/logo/markerhub-logo.png" alt="">
      </el-header>
      <el-main>
        <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
          <el-form-item label="用户名" prop="userName">
            <el-input v-model="ruleForm.userName"></el-input>
          </el-form-item>
          <el-form-item label="密码" prop="password">
            <el-input type="password" v-model="ruleForm.password"></el-input>
          </el-form-item>

          <el-form-item>
            <el-button type="primary" @click="submitForm('ruleForm')">登录/注册</el-button>
            <el-button @click="resetForm('ruleForm')">重置</el-button>
          </el-form-item>
        </el-form>

      </el-main>
    </el-container>

  </div>
</template>

<script>
import Auth from "@/common/Auth";
export default {
  name: "Login",
  data() {
    return {
      ruleForm: {
        userName: 'knife',
        password: '222333'
      },
      rules: {
        userName: [
          {required: true, message: '请输入用户名', trigger: 'blur'},
          {min: 3, max: 15, message: '长度在 3 到 15 个字符', trigger: 'blur'}
        ],
        password: [
          {required: true, message: '请选择密码', trigger: 'change'}
        ]
      }
    };
  },
  methods: {
    submitForm(formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          const _this = this;
          this.$axios.post('/login', this.ruleForm)
              .then(res => {
                console.log(res.data);
                const jwt = res.headers[Auth.HEADER_NAME];
                const userInfo = res.data.data;
                // 把数据共享出去
                _this.$store.commit("SET_TOKEN", jwt);
                _this.$store.commit("SET_USERINFO", userInfo);
                // 获取
                console.log(_this.$store.getters.getUser);
                // _this.$router.push("/blogs");
                _this.$router.push({name: "UserHome", params: {userName: userInfo.userName}})
              })
        } else {
          console.log('error submit!!');
          return false;
        }
      });
    },
    resetForm(formName) {
      this.$refs[formName].resetFields();
    }
  }
}
</script>

<style scoped>
.el-header, .el-footer {
  background-color: #B3C0D1;
  color: #333;
  text-align: center;
  line-height: 60px;
}

.el-aside {
  background-color: #D3DCE6;
  color: #333;
  text-align: center;
  line-height: 200px;
}

.el-main {
  /*background-color: #E9EEF3;*/
  color: #333;
  text-align: center;
  line-height: 160px;
}

body > .el-container {
  margin-bottom: 40px;
}

.el-container:nth-child(5) .el-aside,
.el-container:nth-child(6) .el-aside {
  line-height: 260px;
}

.el-container:nth-child(7) .el-aside {
  line-height: 320px;
}

.logo {
  height: 60%;
  margin-top: 10px;
}

.demo-ruleForm {
  max-width: 500px;
  margin: 0 auto;
}
</style>

User page

UserHome.vue (User Home)

<template>
  <div class="user-home-container">
    <top-header></top-header>
    <div class="main-container">
      <left-aside class="left">
        <user-profile :userName="userName"></user-profile>
      </left-aside>
      <blog-list class="middle" :userName="userName"></blog-list>
    </div>
  </div>
</template>

<script>
import BlogList from "@/views/blog/BlogList";
import TopHeader from "@/components/TopHeader";
import LeftAside from "@/components/LeftAside";
import UserProfile from "@/views/user/UserProfile";

export default {
  name: "Home",
  components: {TopHeader, LeftAside, BlogList, UserProfile},
  data() {
    return {
      userName: ""
    }
  },
  created() {
    this.userName = this.$route.params.userName;
  }
}
</script>

<style scoped>

.main-container {
  display: flex;
  margin: 0 50px 0 50px;
}

.middle {
  flex: 1;
}
</style>

UserList.vue (user list) 

<template>
  <div class="user-list-container">
    <div class="user-list">
      <div class="user-item-box"
           v-for="user of users"
           @click="toUserHome(user.userName)">
        <el-avatar class="avatar" :size="50" :src="user.avatarUrl"></el-avatar>
        <div class="nick-name">{
   
   { user.nickName }}</div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "UserList",
  data() {
    return {
      users: {},
      current: 1,
      total: 0,
      size: 10
    }
  },
  methods: {
    page(current, size) {
      const _this = this
      this.$axios
          .get("/user/page",
              {
                params: {
                  current: current,
                  size: size
                }
              }
          )
          .then(res => {
            _this.users = res.data.data.records
            _this.current = res.data.data.current
            _this.total = res.data.data.total
            _this.size = res.data.data.size
          })
    },
    toUserHome(userName) {
      this.$router.push({name: "UserHome", params: {userName: userName}})
    }
  },
  created() {
    this.page(1, 10);
  }

}
</script>

<style scoped>

.user-list-container {
  margin: 0 50px 0 10px;
  background-color: white;
}

.user-item-box {
  cursor: pointer;
  border-bottom: #c2c7d2 solid 1px;
  padding: 3px 0 3px 0;
  display: flex;
}

.user-item-box:hover {
  background-color: rgb(245, 245, 250);
}

.avatar {
  margin: 0 20px 5px 5px;
}

.nick-name {
  margin: auto 10px;
}

</style>

UserProfile.vue (user information)

<template>
  <div class="user-profile-container">
    <div class="user-information">
      <el-avatar class="avatar"
                 :size="50"
                 :src="avatarUrl">
      </el-avatar>
      <div class="nick-name">
        {
   
   {nickName}}
      </div>
    </div>

    <div class="data-information">
      <dl>
        <dt>
          {
   
   {blogCount}}
        </dt>
        <dd>博客总数</dd>
      </dl>
    </div>
  </div>
</template>

<script>
export default {
  name: "UserProfile",
  data() {
    return {
      nickName: '',
      avatarUrl: '',
      blogCount: 0
    }
  },
  methods: {
    getProfile(userName) {
      const _this = this;
      this.$axios
          .get("/user/profile?userName=" + userName)
          .then(user => {
                _this.nickName = user.data.data.nickName;
                _this.avatarUrl = user.data.data.avatarUrl;
                _this.blogCount = user.data.data.blogCount
              }
          )
    }
  },
  created() {
    let userName = this.$route.params.userName;
    this.getProfile(userName);
  }
}
</script>

<style scoped>
.user-profile-container {
  background-color: white;
}

.user-information {
  display: flex;
  margin: 0 0 20px 10px;
}

.nick-name {
  flex: 1;
  margin: auto 20px;
}

.data-information {
  display: flex;
}

dl {
  margin: 10px;
  text-align: center;
}

dd {
  margin: 0;
}

</style>

blog page

BlogList.vue (blog list page)

<template>
  <div class="blog-page-container">
    <div class="blog-list">
      <div class="blog-item-box" v-for="blog of blogs">
        <h3 class="title" @click="viewBlog(blog.id)">
          {
   
   { blog.title }}
        </h3>

        <!-- <h4 class="title">
          <router-link :to="{name: 'BlogDetail', params: {blogId: blog.id}}">
            {
   
   { blog.title }}
          </router-link>
        </h4>-->

        <p class="description">{
   
   { blog.description }}</p>
        <span class="date">{
   
   { blog.createTime }}</span>

        <span class="operations" v-show="isOwner">
          <el-button @click="editBlog(blog.id)" size="mini">
            编辑
          </el-button>

          <!-- <el-button size="mini">
            <router-link :to="{name: 'BlogEdit', params: {blogId: blog.id}}">&ndash;&gt;
               编辑
            </router-link>
          </el-button>-->

          <el-button @click="deleteBlog(blog.id)" size="mini">
            删除
          </el-button>
        </span>
      </div>

      <div class="page-block">
        <el-pagination
            class="page-container"
            layout="total, prev, pager, next, jumper"
            :current-page="current"
            :page-size="size"
            :total="total"
            @current-change=page>
        </el-pagination>
      </div>
    </div>

  </div>
</template>

<script>
import TopHeader from "../../components/TopHeader";
import LeftAside from "@/components/LeftAside";

export default {
  name: "BlogList",
  components: {LeftAside, TopHeader},
  data() {
    return {
      blogs: {},
      current: 1,
      total: 0,
      size: 10,
      isOwner: false,
      userName: ''
    }
  },
  methods: {
    page(currentPage) {
      const _this = this
      _this.$axios
          .get("/blog/page",
              {
                params: {
                  current: currentPage,
                  size: this.size,
                  userName: this.userName
                }
              }
          )
          .then(res => {
            console.log(res)
            _this.blogs = res.data.data.records
            _this.current = res.data.data.current
            _this.total = res.data.data.total
            _this.size = res.data.data.size
          })
    },

    viewBlog(blogId) {
      this.$router.push({name: "BlogDetail", params: {blogId: blogId}})
    },

    editBlog(blogId) {
      this.$router.push({name: "BlogEdit", params: {blogId: blogId}})
    },

    deleteBlog(blogId) {
      const _this = this
      this.$axios
          .post("/blog/delete?ids=" + blogId,
              {}, {
                headers: {
                  "token": localStorage.getItem("token")
                }
              })
          .then(res => {
            console.log(res)
            _this.$alert('操作成功', '提示', {
              confirmButtonText: '确定',
              callback: action => {
                _this.$router.push("/blogs")
              }
            });
          })
    }
  },

  created() {
    this.userName = this.$route.params.userName;
    if (this.$store.getters.getUser != null
        && this.userName === this.$store.getters.getUser.userName) {
      this.isOwner = true;
    }
    this.page(1);
  }
}
</script>

<style scoped>

.blog-page-container {
  margin: 0 50px 0 10px;
  background-color: white;
}

.blog-item-box:hover {
  background-color: rgb(245, 245, 250);
}

.blog-list {
  text-align: left;
}

.blog-list .blog-item-box {
  padding: 16px 24px 13px 24px;
  border-bottom: 1px solid rgb(126, 150, 130);
  position: relative;
}

.blog-list .blog-item-box p.description {
  margin: 8px 0 6px 0;
}

.title {
  cursor: pointer;
}

.description {
  font-size: 14px;
  /*只显示两行*/
  display: -webkit-box;
  overflow: hidden;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.date {
  font-size: 14px;
  color: #5f6471;
}

.operations {
  position: absolute;
  right: 24px;
  margin: 2px;
}

.page-block {
  padding: 20px 0;
}

.page-container {
  width: 100px;
  margin: auto;
}

</style>

BlogDetail.vue (blog detail page)

<template>
  <div>
    <Header></Header>

    <div class="mblog">
      <h2> {
   
   { blog.title }}</h2>
      <el-link icon="el-icon-edit" v-if="ownBlog">
        <router-link :to="{name: 'BlogEdit', params: {blogId: blog.id}}">
          编辑
        </router-link>
      </el-link>
      <el-divider></el-divider>
      <div class="markdown-body" v-html="blog.content"></div>

    </div>

  </div>
</template>

<script>
import 'github-markdown-css'
import Header from "../../components/TopHeader";

export default {
  name: "BlogDetail.vue",
  components: {Header},
  data() {
    return {
      blog: {
        id: "",
        title: "",
        content: "",
        userId: ""
      },
      ownBlog: false
    }
  },
  created() {
    const blogId = this.$route.params.blogId
    // console.log(blogId)
    const _this = this
    this.$axios
        .get('/blog/getThis?id=' + blogId)
        .then(res => {
          const blog = res.data.data
          _this.blog.id = blog.id
          _this.blog.title = blog.title
          let MarkDownIt = require("markdown-it")
          let md = new MarkDownIt()
          let result = md.render(blog.content)
          _this.blog.content = result
          _this.ownBlog = (blog.userId === _this.$store.getters.getUser.id)
        })
  }
}
</script>

<style scoped>
.mblog {
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  width: 100%;
  min-height: 700px;
  padding: 20px 15px;
}
</style>

BlogEdit.vue (blog edit page)

<template>
  <div>
    <Header></Header>

    <div class="m-content">

      <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
        <el-form-item label="标题" prop="title">
          <el-input v-model="ruleForm.title"></el-input>
        </el-form-item>

        <el-form-item label="摘要" prop="description">
          <el-input type="textarea" v-model="ruleForm.description"></el-input>
        </el-form-item>

        <el-form-item label="内容" prop="content">
          <mavon-editor v-model="ruleForm.content"></mavon-editor>
        </el-form-item>

        <el-form-item>
          <el-button type="primary" @click="submitForm('ruleForm')">发布</el-button>
          <el-button @click="resetForm('ruleForm')">重置</el-button>
        </el-form-item>
      </el-form>

    </div>

  </div>
</template>

<script>
import Header from "../../components/TopHeader";

export default {
  name: "BlogEdit.vue",
  components: {Header},
  data() {
    return {
      ruleForm: {
        id: '',
        title: '',
        description: '',
        content: ''
      },
      rules: {
        title: [
          {required: true, message: '请输入标题', trigger: 'blur'},
          {min: 3, max: 25, message: '长度在 3 到 25 个字符', trigger: 'blur'}
        ],
        description: [
          {required: true, message: '请输入摘要', trigger: 'blur'}
        ],
        content: [
          {required: true, message: '请输入内容', trigger: 'blur'}
        ]
      },
      operation: "add"
    };
  },
  methods: {
    submitForm(formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          const _this = this
          this.$axios
              .post('/blog/' + this.operation, this.ruleForm)
              .then(res => {
                console.log(res)
                _this.$alert('操作成功', '提示', {
                  confirmButtonText: '确定',
                  callback: action => {
                    _this.$router.push("/blogs")
                  }
                });
              })
        } else {
          console.log('error submit!!');
          return false;
        }
      });
    },
    resetForm(formName) {
      this.$refs[formName].resetFields();
    }
  },

  created() {
    const blogId = this.$route.params.blogId
    const _this = this
    if (!blogId) {
      this.operation = "add"
    } else {
      this.operation = "edit";
      this.$axios.get('/blog/getThis?id=' + blogId).then(res => {
        const blog = res.data.data
        _this.ruleForm.id = blog.id
        _this.ruleForm.title = blog.title

        _this.ruleForm.description = blog.description
        _this.ruleForm.content = blog.content
      });
    }
  }
}
</script>

<style scoped>
.m-content {
  text-align: center;
}
</style>

Public section

axio.js (request interception, response interception)

import axios from 'axios'
import Element from 'element-ui'
import router from './router'
import store from './store'
import Auth from "@/common/Auth";

// axios.defaults.baseURL = "http://localhost:8081"

// 请求拦截
axios.interceptors.request.use(config => {
    // 统一设置请求参数
    if (store.state.token) {
        config.headers[Auth.HEADER_NAME] = store.state.token
    }
    return config;
})

// 响应拦截
axios.interceptors.response.use(response => {
        let res = response.data;

        if (res.code === 1000) {
            return response
        } else {
            Element.Message.error('失败:' + response.data.msg);
            return response;
        }
    },
    error => {
        console.log(error)
        Element.Message.error('失败:' + error.response.data.message);
        if (error.response.data) {
            error.message = error.response.data.msg
        }

        if (error.response.status === 401) {
            store.commit("REMOVE_INFO")
            router.push("/login")
        }

        Element.Message.error(error.message)
        return Promise.reject(error)
    }
)

permission.js (control permission (whether to pass the token parameter)) 

import router from "./router";

// 路由判断登录 根据路由配置文件的参数
router.beforeEach((to, from, next) => {
    if (to.matched.some(record => (!record.meta.notRequireAuth))) { // 判断该路由是否需要登录权限
        const token = localStorage.getItem("token")
        console.log("token:" + token)

        if (token) { // 判断当前的token是否存在; 登录存入的token
            if (to.path === '/login') {
            } else {
                next()
            }
        } else {
            next({
                path: '/login'
            })
        }
    } else {
        next()
    }
})

component part

LeftAside.vue (left sidebar)

<template>
  <div class="left-aside-container">
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: "LeftAside"
}
</script>

<style scoped>

.left-aside-container {
  width: 300px;
  margin: 0 10px 0 5px;
}

</style>

TopHeader.vue (top bar)

<template>
  <div class="header-container">
    <div class="left">
      <span class="website-name" @click="toHome()">
        蚂蚁博客
      </span>
    </div>

    <div class="middle">
      <span class="search">
        搜索功能,敬请期待
      </span>
    </div>

    <div class="right">
        <span v-show="hasLogin"
              @click="toUserHome(user.userName)">
          <el-avatar class="avatar"
                     :size="35"
                     :src="user.avatarUrl">
          </el-avatar>
        </span>

        <span v-show="hasLogin">
          <router-link to="/blog/add">写博客</router-link>
          <el-divider direction="vertical"></el-divider>
        </span>

        <router-link v-show="!hasLogin" to="/login">登录/注册</router-link>
        <span v-show="hasLogin">
          <el-link type="danger" @click="logout">退出</el-link>
        </span>
      </div>
  </div>
</template>

<script>
import BlogEdit from "@/views/blog/BlogEdit";

export default {
  name: "Header",
  components: {BlogEdit},
  data() {
    return {
      user: {
        userName: '请先登录',
        avatarUrl: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'
      },
      hasLogin: false
    }
  },
  methods: {
    logout() {
      const _this = this
      _this.$axios
          .post("/logout")
          .then(res => {
            // _this.hasLogin = false
            _this.$store.commit("REMOVE_INFO")
            _this.$router.push({name: "Home"})
          })
    },
    toHome() {
      this.$router.push({name: "Home"});
    },
    toUserHome(userName) {
      this.$router.push({name: "UserHome", params: {userName: userName}})
    }
  },
  created() {
    if (this.$store.getters.getUser !== null
        && this.$store.getters.getUser.userName !== null) {
      this.user.userName = this.$store.getters.getUser.userName
      this.user.avatarUrl = this.$store.getters.getUser.avatarUrl
      this.hasLogin = this.$store.getters.getHasLogin
    }
  }
}
</script>

<style scoped>
.header-container {
  display: flex;
  height: 48px;
  margin: 0 0 20px 0;
  box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .2);
  background-color: white;
}

.website-name {
  cursor: pointer;
}

.left {
  /*width: 400px;*/
  margin: 5px 10px;
  font-size: 24px;
  font-weight: bolder;
}

.middle {
  flex: 1;
  display: flex;
}

.search {
  margin: 5px auto;
}

.right {
  margin: 5px 10px;
  width: 250px;
}

.avatar {
  margin: 4px 20px 0 0;
  cursor: pointer;
}

</style>

plugin section

router/index.js (routing plugin)

effect

        The vue-router plugin, when we enter the url on the browser, it can locate the page specified in the front-end code.

code

import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../views/Login.vue'
import BlogDetail from '../views/blog/BlogDetail.vue'
import BlogEdit from '../views/blog/BlogEdit.vue'
import Home from "@/views/Home";

Vue.use(VueRouter)

const routes = [
/*  {
    path: '/',
    name: 'Index',
    redirect: { name: 'Home' }
  }, */
  {
    path: '/',
    name: 'Home',
    component: Home
  }, {
    path: '/login',
    name: 'Login',
    component: Login,
    meta: {
      notRequireAuth: true
    }
  },
  {
    path: '/:userName',
    name: 'UserHome',
    // 懒加载
    component: () => import('../views/user/UserHome'),
    meta: {
      notRequireAuth: true
    }
  },
  {
    path: '/blog/add', // 注意放在 path: '/blog/:blogId'之前
    name: 'BlogAdd',
    component: BlogEdit,
  },
  {
    path: '/blog/:blogId',
    name: 'BlogDetail',
    component: BlogDetail,
    meta: {
      notRequireAuth: true
    }
  },
  {
    path: '/blog/:blogId/edit',
    name: 'BlogEdit',
    component: BlogEdit
  }
];

const router = new VueRouter({
  // mode: 'history',
  base: process.env.BASE_URL,
  routes
})
export default router

store/index.js (vuex plugin)

illustrate

        vuex can be used to share data across components. See: Vue--Vuex--Use/Tutorial/Example

code

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        token: localStorage.getItem("token"),
        userInfo: JSON.parse(localStorage.getItem("userInfo")),
        // userInfo: JSON.parse(sessionStorage.getItem("userInfo")),
    },
    mutations: {
        // set
        SET_TOKEN: (state, token) => {
            state.token = token
            localStorage.setItem("token", token)
        },
        SET_USERINFO: (state, userInfo) => {
            state.userInfo = userInfo
            localStorage.setItem("userInfo", JSON.stringify(userInfo))
            // sessionStorage.setItem("userInfo", JSON.stringify(userInfo))
        },
        REMOVE_INFO: (state) => {
            state.token = ''
            state.userInfo = {}
            localStorage.setItem("token", '')
            localStorage.setItem("userInfo", JSON.stringify(''))
            // sessionStorage.setItem("userInfo", JSON.stringify(''))
        }
    },
    getters: {
        // get
        getUser: state => {
            console.log("getUser:" + JSON.stringify(state));
            return state.userInfo
        },
        getHasLogin: state => {
            return state.token !== ''
        }
    },
    actions: {},
    modules: {}
})

Guess you like

Origin blog.csdn.net/feiying0canglang/article/details/124088597