❤️springboot+vue2+springsecurity权限管理系统

小明的学习圈子2024-10-24项目项目

一、项目的介绍

1、更多内容在线阅读的文档open in new window

2、关注下面的公众号 小明的学习圈子 回复 20231014权限管理,获取本项目的源码和sql

3、项目演示地址open in new window

环境:

win10、idea22023.2、vscode1.7、maven3.5、jdk8、Redis

技术:

  • springboot2.5.9
  • springsecurity
  • Redis
  • mybatis plus
  • mysql5.7.38
  • swagger
  • jwt
  • vue2
  • element ui
  • vuex
  • vue-router
  • axios
  • mockjs
  • echarts
  • less

模块:

  1. 登录页面

  2. 首页

  3. 权限管理

    1.用户管理

    2.角色管理

    3.菜单管理

功能要点:

  1. 从0到1实现项目前后端搭建
  2. vuex实现面包屑和Tag功能
  3. vuex+localStorage动态路由和菜单(重点)
  4. 封装一个ECharts组件
  5. 封装Table表格
  6. 树形表格和CheckBox的使用
  7. 封装统一返回类
  8. 两种方式实现代码生成,解放双手
  9. jwt、swagger、Redis封装
  10. 权限管理(重点)

二、脚手架的搭建

1、vue 环境搭建

参照博客文章:vue安装node以及nrm、nrm配置,路由安装open in new windowVue CLI2初始化项目vue2open in new window

2、注意版本号

3、这里选择vue2

4、安装完成

5、启动

6、游览器访问

三、引入element-ui

1、npm安装

npm i element-ui -S
  • 网页直接使用

2、脚手架中使用

全部引入 官网:[https://element.eleme.cn/#/zh-CN/component/quickstart]open in new window 在 main.js 中写入以下内容:

import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui';//引入element-ui
import 'element-ui/lib/theme-chalk/index.css';//引入element-ui的样式
Vue.config.productionTip = false
Vue.use(ElementUI);//全局注册
new Vue({
  render: h => h(App),
}).$mount('#app')

App.vue加入element-ui的按钮

<template>
  <div id="app">
    <el-row>
        <el-button>默认按钮</el-button>
        <el-button type="primary">主要按钮</el-button>
        <el-button type="success">成功按钮</el-button>
        <el-button type="info">信息按钮</el-button>
        <el-button type="warning">警告按钮</el-button>
        <el-button type="danger">危险按钮</el-button>
  </el-row>
  </div>
</template>

<script>
export default {
  name: 'App',
}
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

游览器效果

四、路由的使用

1、路由官网:https://v3.router.vuejs.org/zh/installation.htmlopen in new window

这里使用v3.x的版本 npm.jscom查看vue-router3的最新版本https://www.npmjs.com/package/vue-router?activeTab=versionsopen in new window

2、npm安装

安装最新版(install 可以简写为i)

npm install vue-router

安装指定版本

npm i vue-router@3.6.5

3、在src目录下新建router文件夹和index.js文件

4、如果在一个模块化工程中使用它,必须要通过 Vue.use() 明确地安装路由功能:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

如果使用全局的 script 标签,则无须如此 (手动安装)。 官网:https://v3.router.vuejs.org/zh/guide/#javascriptopen in new window

5、index.js配置

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/views/Home.vue'
import User from '@/views/User.vue'
Vue.use(VueRouter)

// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
// 1. 定义 (路由) 组件。
// 可以从其他文件 import 进来
// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
  { path: '/home', component: Home },
  { path: '/user', component: User }
]

// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
  routes // (缩写) 相当于 routes: routes
})

// 4. 创建和挂载根实例。挂载到main.js的根节点
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
// const app = new Vue({
//   router
// }).$mount('#app')

export default router

6、main.js挂载路由

import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import router from './router';//如果router文件夹下有index.js就不需要'./router/index.js'

Vue.config.productionTip = false
Vue.use(ElementUI);
new Vue({
  router,//挂载路由
  render: h => h(App),
}).$mount('#app')

7、路由出口

路由匹配到的组件将渲染在这里

  <router-view></router-view>

8、App.vue配置路由出口

<template>
  <div id="app">
    <el-row>
        <el-button>默认按钮</el-button>
        <el-button type="primary">主要按钮</el-button>
        <el-button type="success">成功按钮</el-button>
        <el-button type="info">信息按钮</el-button>
        <el-button type="warning">警告按钮</el-button>
        <el-button type="danger">危险按钮</el-button>
 		<!-- 路由出口 -->
  		<!-- 路由匹配到的组件将渲染在这里 -->
        <router-view></router-view>
  </el-row>
  </div>
</template>

<script>
export default {
  name: 'App',
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

9、相关文件目录

10、启动服务,如果报错如下

解决办法,在vue.config.js文件里添加lintOnSave:false关闭eslint的校验。

11、访问

12、嵌套路由

官网:https://v3.router.vuejs.org/zh/guide/essentials/nested-routes.htmlopen in new window index.js和Main.vue配置如下

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/views/Home.vue'
import User from '@/views/User.vue'
import Main from '@/views/Main.vue'
Vue.use(VueRouter)

// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
// 1. 定义 (路由) 组件。
// 可以从其他文件 import 进来
// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
  //主路由
  {
    path: '/',
    component: Main,
    children: [
      //子路由
      { path: 'home', component: Home },
      { path: 'user', component: User }
    ]
  },

]

// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
  routes // (缩写) 相当于 routes: routes
})

// 4. 创建和挂载根实例。挂载到main.js的根节点
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
// const app = new Vue({
//   router
// }).$mount('#app')

export default router

1、Main.vue需要配置路由出口

<template>
  <div>
    <h1>main</h1>
    <!-- 子路由出口 -->
    <!-- 子路由匹配到的组件将渲染在这里 -->
    <router-view></router-view>
  </div>
</template>
 <script>
export default {
  //name:"Home",
  data() {
    return {};
  },
};
</script>

2、目录 3、访问效果

五、首页架子的搭建

1、container的容器open in new window

<el-container>
  <el-aside width="200px">Aside</el-aside>
  <el-container>
    <el-header>Header</el-header>
    <el-main>Main</el-main>
  </el-container>
</el-container>

2、Main.vue

<template>
  <div>
    <el-container>
      <el-aside width="auto"><common-aside/></el-aside>
      <el-container>
        <el-header><common-header /></el-header>
        <el-main>
          main
          <!-- 子路由出口 -->
          <!-- 子路由匹配到的组件将渲染在这里 -->
          <router-view></router-view>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>
 <script>
import CommonAside from "@/components/CommonAside.vue";
import CommonHeader from "@/components/CommonHeader.vue";
export default {
  name: "Main",
  components: {
    CommonAside,
    CommonHeader,
  },
  data() {
    return {};
  },
};
</script>
 <style scoped>
.el-header {
  padding: 0;
}
</style>

3、侧边栏的实现

CommonAsideopen in new window

<template>
  <div>
    <el-menu
      default-active="1-4-1"
      class="el-menu-vertical-demo"
      @open="handleOpen"
      @close="handleClose"
      :collapse="isCollapse"
      background-color="#545c64"
      text-color="#fff"
      active-text-color="#ffd04b"
    >
      <h3>通用后台管理系统</h3>
      <el-menu-item
        v-for="item in noChildren"
        :key="item.name"
        :index="item.path"
        @click="clickMenu(item)"
      >
        <i :class="`el-icon-${item.icon}`"></i>
        <span slot="title">{{ item.label }}</span>
      </el-menu-item>
      <el-submenu
        v-for="item in hasChildren"
        :key="item.name"
        :index="item.name"
      >
        <template slot="title">
          <i :class="`el-icon-${item.icon}`"></i>
          <span slot="title">{{ item.label }}</span>
        </template>
        <el-menu-item-group
          v-for="subItem in item.children"
          :key="subItem.path"
        >
          <el-menu-item :index="subItem.path" @click="clickMenu(subItem)">{{
            subItem.label
          }}</el-menu-item>
        </el-menu-item-group>
      </el-submenu>
    </el-menu>
  </div>
</template>
<style lang="less" scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
  width: 200px;
  min-height: 400px;
}
.el-menu {
  //height: calc(100vh - 0px);
  border-right:0px;
  height: 100vh;
  h3 {
    color: #fff;
    text-align: center;
    line-height: 48px;
    font-size: 16px;
    font-weight: 400px;
  }
}
</style>
<script>
export default {
  data() {
    return {
      isCollapse: false,
      menuData: [
        {
          path: "/",
          name: "home",
          label: "首页",
          icon: "s-home",
          url: "Home/Home",
        },
        {
          label: "权限管理",
          name: "system",
          icon: "location",
          children: [
            {
              path: "/user",
              name: "user",
              label: "用户管理",
              icon: "setting",
              url: "Other/PageOne",
            },
            {
              path: "/role",
              name: "role",
              label: "角色管理",
              icon: "setting",
              url: "Other/PageTwo",
            },
            {
              path: "/menu",
              name: "menu",
              label: "菜单管理",
              icon: "setting",
              url: "Other/PageTwo",
            },
          ],
        },
      ],
    };
  },
  methods: {
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    },
    clickMenu(item) {
      //当页面的路由和跳转的路由不一致才允许跳转
       //this.$route.path 上一个路径,item.path下一个路径
      if (this.$route.path !== item.path && !(this.$route.path ==='/home' && (item.path ==='/'))) {
        this.$router.push(item.path);
      }


    },
  },
  computed: {
    //没有子菜单
    noChildren() {
      return this.menuData.filter((item) => !item.children);
    },
    //有子菜单
    hasChildren() {
      return this.menuData.filter((item) => item.children);
    },
  },
};
</script>

4、less安装

官网open in new window

npm i less@4.1.2
npm i less-loader@6.0.0

5、Main.vue

<template>
  <div>
    <el-container>
      <el-aside width="auto"><CommonAside></CommonAside></el-aside>
      <el-container>
        <el-header>Header</el-header>
        <el-main>
          main
          <!-- 子路由出口 -->
          <!-- 子路由匹配到的组件将渲染在这里 -->
          <router-view></router-view>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>
 <script>
 import CommonAside from '@/components/CommonAside.vue';
export default {
  //name:"Main",
  components:{
    CommonAside
  },
  data() {
    return {};
  },
};
</script>

6、CommonAside.vue

<template>
  <div>
    <el-menu
      default-active="1-4-1"
      class="el-menu-vertical-demo"
      @open="handleOpen"
      @close="handleClose"
      :collapse="isCollapse"
      background-color="#545c64"
      text-color="#fff"
      active-text-color="#ffd04b"
    >
      <h3>通用后台管理系统</h3>
      <el-menu-item
        v-for="item in noChildren"
        :key="item.name"
        :index="item.name"
        @click="clickMenu(item)"
      >
        <i :class="`el-icon-${item.icon}`"></i>
        <span slot="title">{{ item.label }}</span>
      </el-menu-item>
      <el-submenu
        v-for="item in hasChildren"
        :key="item.name"
        :index="item.name"
      >
        <template slot="title">
          <i :class="`el-icon-${item.icon}`"></i>
          <span slot="title">{{ item.label }}</span>
        </template>
        <el-menu-item-group
          v-for="subItem in item.children"
          :key="subItem.path"
        >
          <el-menu-item :index="subItem.path" @click="clickMenu(subItem)">{{ subItem.label }}</el-menu-item>
        </el-menu-item-group>
      </el-submenu>
    </el-menu>
  </div>
</template>
<style lang="less" scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
  width: 200px;
  min-height: 400px;
}
.el-menu {
  //height: calc(100vh - 0px);
  height: 100vh;
  h3 {
    color: #fff;
    text-align: center;
    line-height: 48px;
    font-size: 16px;
    font-weight: 400px;
  }
}
</style>
<script>
export default {
  data() {
    return {
      isCollapse: false,
      menuData: [
        {
          path: "/",
          name: "home",
          label: "首页",
          icon: "s-home",
          url: "Home/Home",
        },
        {
          label: "权限管理",
          icon: "location",
          children: [
            {
              path: "/user",
              name: "user",
              label: "用户管理",
              icon: "setting",
              url: "Other/PageOne",
            },
            {
              path: "/role",
              name: "role",
              label: "角色管理",
              icon: "setting",
              url: "Other/PageTwo",
            },
            {
              path: "/menu",
              name: "menu",
              label: "菜单管理",
              icon: "setting",
              url: "Other/PageTwo",
            },
          ],
        },
      ],
    };
  },
  methods: {
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    },
    clickMenu(item){
      this.$router.push(item.path)
      console.log(item)
    },

  },
  computed: {
    //没有子菜单
    noChildren() {
      return this.menuData.filter((item) => !item.children);
    },
    //有子菜单
    hasChildren() {
      return this.menuData.filter((item) => item.children);
    },
  },
};
</script>

7、App.vue清除默认样式

<template>
  <div id="app">
    <el-row>
      <!-- 路由出口 -->
      <!-- 路由匹配到的组件将渲染在这里 -->
      <router-view></router-view>
    </el-row>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>
<style lang="less">
//清除默认样式
html,
body,h3 {
  margin: 0;
  padding: 0;
}
</style>>

效果如下

8、出现的小问题

连续点击同一个菜单多次,出现这个错误,虽然不影响路由跳转,但是为了完美,解决方法如下

9、解决办法

1、方法判断

  methods: {
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    },
    clickMenu(item) {
      //当页面的路由和跳转的路由不一致才允许跳转
       //this.$route.path 上一个路径,item.path下一个路径
      if (this.$route.path !== item.path 
         && !(this.$route.path ==='/home' && (item.path ==='/'))) {
        this.$router.push(item.path);
      }

    },
  },

2、路由的index文件添加如下配置

// 防止连续点击多次路由报错
// 获取原型对象push函数
const originalPush = VueRouter.prototype.push
// 获取原型对象replace函数
const originalReplace = VueRouter.prototype.replace
// 修改原型对象中的push函数
VueRouter.prototype.push = function push(location) {
  return originalPush.call(this, location).catch(err => err)
}
// 修改原型对象中的replace函数
VueRouter.prototype.replace = function replace(location) {
  return originalReplace.call(this, location).catch(err => err)
}

10、CommonHeader组件的实现

新增CommonHeader.vue组件

<template>
  <div class="header-main">
    <div class="l-content">
      <el-button icon="el-icon-menu" size="mini"></el-button>
      <!-- 面包屑 -->
      <span class="text">首页</span>
    </div>
    <div class="r-content">
      <el-dropdown>
     <img src="@/assets/img/user.jpg" class="user">
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item>个人中心</el-dropdown-item>
          <el-dropdown-item>退出</el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
  </div>
</template>


<script>
export default {
  data() {
    return {};
  },
  methods: {
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
  },
};
</script>
<style lang="less" scoped>
.header-main {
  padding: 0 20px;
  background-color: #333;
  height: 60px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  .text {
    color: #ffff;
    font-size: 14px;
    margin-left: 10px;
  }
  .r-content{
    .user{
      width:40px;
      height: 40px;
      border-radius: 50%;
    }
  }
}
</style>

main.vue

<template>
  <div>
    <el-container>
      <el-aside width="auto"><common-aside/></el-aside>
      <el-container>
        <el-header><common-header /></el-header>
        <el-main>
          main
          <!-- 子路由出口 -->
          <!-- 子路由匹配到的组件将渲染在这里 -->
          <router-view></router-view>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>
 <script>
import CommonAside from "@/components/CommonAside.vue";
import CommonHeader from "@/components/CommonHeader.vue";
export default {
  name: "Main",
  components: {
    CommonAside,
    CommonHeader,
  },
  data() {
    return {};
  },
};
</script>
 <style scoped>
.el-header {
  padding: 0;
}
</style>

11、Vuex实现左侧折叠

官网:https://v3.vuex.vuejs.org/zh/open in new window 1、下载依赖

npm i vuex@3.6.2

2、新建store文件夹并在此文件夹下新建index.js和menu.js文件 index

import Vue from 'vue'
import Vuex from 'vuex'
import menu from './menu'


Vue.use(Vuex)


//创建vuex的实例
export default new Vuex.Store({
    modules: {
        menu,


    }
})

3、menu

export default {
    state: {
        isCollapse: false //用于控制菜单的展开收起
    },
    mutations: {
        //用于控制菜单的展开收起
        handleCollapse(state) {
            state.isCollapse = !state.isCollapse
        }
    }
}

4、CommonHeader新增handleIsCollapse方法

<template>
  <div class="header-main">
    <div class="l-content">
      <el-button
        icon="el-icon-menu"
        @click="handleIsCollapse"
        size="mini"
      ></el-button>
      <!-- 面包屑 -->
      <span class="text">首页</span>
    </div>
    <div class="r-content">
      <el-dropdown>
        <img src="@/assets/img/user.jpg" class="user" />
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item>个人中心</el-dropdown-item>
          <el-dropdown-item>退出</el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
  </div>
</template>


<script>
export default {
  data() {
    return {};
  },
  methods: {
    handleIsCollapse() {
      this.$store.commit("handleCollapse");
    },
  },
};
</script>
<style lang="less" scoped>
.header-main {
  padding: 0 20px;
  background-color: #333;
  height: 60px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  .text {
    color: #ffff;
    font-size: 14px;
    margin-left: 10px;
  }
  .r-content {
    .user {
      width: 40px;
      height: 40px;
      border-radius: 50%;
    }
  }
}
</style>

5、CommonAside新增isCollapse计算属性,注意计算属性和data里不能同事存在isCollapse。

<template>
  <div>
    <el-menu
      default-active="1-4-1"
      class="el-menu-vertical-demo"
      @open="handleOpen"
      @close="handleClose"
      :collapse="isCollapse"
      background-color="#545c64"
      text-color="#fff"
      active-text-color="#ffd04b"
    >
      <h3>{{ isCollapse ? "后台" : "通用后台管理系统" }}</h3>
      <el-menu-item
        v-for="item in noChildren"
        :key="item.name"
        :index="item.path"
        @click="clickMenu(item)"
      >
        <i :class="`el-icon-${item.icon}`"></i>
        <span slot="title">{{ item.label }}</span>
      </el-menu-item>
      <el-submenu
        v-for="item in hasChildren"
        :key="item.name"
        :index="item.name"
      >
        <template slot="title">
          <i :class="`el-icon-${item.icon}`"></i>
          <span slot="title">{{ item.label }}</span>
        </template>
        <el-menu-item-group
          v-for="subItem in item.children"
          :key="subItem.path"
        >
          <el-menu-item :index="subItem.path" @click="clickMenu(subItem)">{{
            subItem.label
          }}</el-menu-item>
        </el-menu-item-group>
      </el-submenu>
    </el-menu>
  </div>
</template>
<style lang="less" scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
  width: 200px;
  min-height: 400px;
}
.el-menu {
  //height: calc(100vh - 0px);
  border-right: 0px;
  height: 100vh;
  h3 {
    color: #fff;
    text-align: center;
    line-height: 48px;
    font-size: 16px;
    font-weight: 400px;
  }
}
</style>
<script>
export default {
  data() {
    return {
      //isCollapse: false,
      menuData: [
        {
          path: "/",
          name: "home",
          label: "首页",
          icon: "s-home",
          url: "Home/Home",
        },
        {
          label: "权限管理",
          name: "system",
          icon: "location",
          children: [
            {
              path: "/user",
              name: "user",
              label: "用户管理",
              icon: "setting",
              url: "Other/PageOne",
            },
            {
              path: "/role",
              name: "role",
              label: "角色管理",
              icon: "setting",
              url: "Other/PageTwo",
            },
            {
              path: "/menu",
              name: "menu",
              label: "菜单管理",
              icon: "setting",
              url: "Other/PageTwo",
            },
          ],
        },
      ],
    };
  },
  methods: {
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    },
    clickMenu(item) {
      //当页面的路由和跳转的路由不一致才允许跳转
      //this.$route.path 上一个路径,item.path下一个路径
      if (
        this.$route.path !== item.path &&
        !(this.$route.path === "/home" && item.path === "/")
      ) {
        this.$router.push(item.path);
      }
    },
  },
  computed: {
    //没有子菜单
    noChildren() {
      return this.menuData.filter((item) => !item.children);
    },
    //有子菜单
    hasChildren() {
      return this.menuData.filter((item) => item.children);
    },
    //vuex取得菜单展开收起的值,但是data里不能重复定义
    isCollapse() {
      return this.$store.state.menu.isCollapse;
    },
  },
};
</script>

6、main.js挂载vuex

import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import router from './router';//如果router文件夹下有index.js就不需要'./router/index.js'
import store from './store'


Vue.config.productionTip = false
Vue.use(ElementUI);
new Vue({
  router,//挂载路由
  store,
  render: h => h(App),
}).$mount('#app')

效果如下 image.png 收起 image.png

12、Home布局

1、用户信息

<template>
  <el-row>
    <el-col :span="8"
      ><div class="grid-content bg-purple">
        <el-card class="box-card">
          <div class="user-content">
            <img src="@/assets/img/user.jpg" class="user-img" />
            <div class="user-info">
              <p class="user-name">Admin</p>
              <p class="user-access">超级管理员</p>
            </div>
          </div>
          <div class="login-content">
            <p>上次登录的时间:<span>2023-02-25</span></p>
            <p>上次登录的地点:<span>西安</span></p>
          </div>
        </el-card>
      </div></el-col
    >
    <el-col :span="16"><div class="grid-content bg-purple-light"></div></el-col>
  </el-row>
</template>
<script>
export default {
  name: "Home",
  data() {
    return {};
  },
};
</script>
<style lang="less" scoped>
.user-content {
  padding-bottom: 20px;
  margin-bottom: 20px;
  border-bottom: 1px solid#ccc;
  display: flex;
  align-items: center; //垂直居中
  .user-img {
    width: 150px;
    height: 150px;
    border-radius: 50%;
    margin-right: 40px;
  }
  .user-info {
    .user-name {
      font-size: 32px;
      margin-bottom: 10px;
    }
    .user-access {
      color: #999999;
    }
  }
}
.login-content {
  p {
    line-height: 28px;
    font-size: 14px;
    color: #999999;
    span {
      color: #666666;
      margin-left: 60px;
    }
  }
}
</style>

效果如下 image.png 2、列表,这里封装了动态的表格列

<template>
  <div class="main">
    <el-row>
      <el-col :span="8"
        ><div class="grid-content bg-purple">
          <el-card class="box-card">
            <div class="user-content">
              <img src="@/assets/img/user.jpg" class="user-img" />
              <div class="user-info">
                <p class="user-name">Admin</p>
                <p class="user-access">超级管理员</p>
              </div>
            </div>
            <div class="login-content">
              <p>上次登录的时间:<span>2023-02-25</span></p>
              <p>上次登录的地点:<span>西安</span></p>
            </div>
          </el-card>
          <el-card class="box-card" style="margin-top: 20px">
            <el-table :data="tableData" style="width: 100%">
              <el-table-column
                v-for="(val, key) in tableLabel"
                :prop="key"
                :label="val"
                :key="key"
               
              />


              <!-- <el-table-column prop="date" label="日期" width="180">
              </el-table-column>
              <el-table-column prop="name" label="姓名" width="180">
              </el-table-column>
              <el-table-column prop="address" label="地址">
              </el-table-column>  -->
            </el-table></el-card
          >
        </div></el-col
      >
      <el-col :span="16"
        ><div class="grid-content bg-purple-light"></div
      ></el-col>
    </el-row>
  </div>
</template>
<script>
export default {
  name: "Home",
  data() {
    return {
      tableLabel: {
        name: "姓名",
        date: "日期",
        address: "地址",
      },
      tableData: [
        {
          date: "2016-05-02",
          name: "王小虎",
          address: " 1518 弄",
        },
        {
          date: "2016-05-04",
          name: "王小虎",
          address: " 1517 弄",
        },
        {
          date: "2016-05-01",
          name: "王小虎",
          address: " 1519 弄",
        },
        {
          date: "2016-05-03",
          name: "王小虎",
          address: " 1516 弄",
        },
      ],
    };
  },
};
</script>
<style lang="less" scoped>
.main {
  .user-content {
    padding-bottom: 20px;
    margin-bottom: 20px;
    border-bottom: 1px solid#ccc;
    display: flex;
    align-items: center; //垂直居中
    .user-img {
      width: 150px;
      height: 150px;
      border-radius: 50%;
      margin-right: 40px;
    }
    .user-info {
      .user-name {
        font-size: 32px;
        margin-bottom: 10px;
      }
      .user-access {
        color: #999999;
      }
    }
  }
  .login-content {
    p {
      line-height: 28px;
      font-size: 14px;
      color: #999999;
      span {
        color: #666666;
        margin-left: 60px;
      }
    }
  }
}
</style>

3、效果如下 image.png 4、首页右半部分

<template>
  <div class="main">
    <el-row>
      <el-col :span="8"
        ><div class="grid-content bg-purple">
          <el-card class="box-card">
            <div class="user-content">
              <img src="@/assets/img/user.jpg" class="user-img" />
              <div class="user-info">
                <p class="user-name">Admin</p>
                <p class="user-access">超级管理员</p>
              </div>
            </div>
            <div class="login-content">
              <p>上次登录的时间:<span>2023-02-25</span></p>
              <p>上次登录的地点:<span>西安</span></p>
            </div>
          </el-card>
          <el-card class="box-card" style="margin-top: 20px">
            <el-table :data="tableData" style="width: 100%">
              <el-table-column
                v-for="(val, key) in tableLabel"
                :prop="key"
                :label="val"
                :key="key"
              />


              <!-- <el-table-column prop="date" label="日期" width="180">
              </el-table-column>
              <el-table-column prop="name" label="姓名" width="180">
              </el-table-column>
              <el-table-column prop="address" label="地址">
              </el-table-column>  -->
            </el-table></el-card
          >
        </div></el-col
      >
      <el-col :span="16">
        <div class="home-right-info">
          <el-card
            v-for="item in countData"
            :key="item.name"
            :body-style="{ display: 'flex' }"
          >
            <i
              class="icon"
              :class="`el-icon-${item.icon}`"
              :style="{ background: item.color }"
            ></i>
            <div class="detail-info">
              <p class="price">{{ item.value }}</p>
              <p class="desc">{{ item.name }}</p>
            </div>
          </el-card>
        </div>
      </el-col>
    </el-row>
  </div>
</template>
<script>
export default {
  name: "Home",
  data() {
    return {
      countData: [
        {
          name: "样式1",
          value: "样式1",
          icon: "success",
          color: "#2ec7c9",
        },
        {
          name: "样式2",
          value: "样式2",
          icon: "star-on",
          color: "#ffb980",
        },
        {
          name: "样式3",
          value: "样式3",
          icon: "s-goods",
          color: "#5ab1ef",
        },
        {
          name: "样式4",
          value: "样式4",
          icon: "user",
          color: "#2ec7c9",
        },
        {
          name: "样式5",
          value: "样式5",
          icon: "s-home",
          color: "#ffb980",
        },
        {
          name: "样式6",
          value: "样式6",
          icon: "folder",
          color: "#5ab1ef",
        },
      ],
      tableLabel: {
        name: "姓名",
        date: "日期",
        address: "地址",
      },
      tableData: [
        {
          date: "2016-05-02",
          name: "王小虎",
          address: " 1518 弄",
        },
        {
          date: "2016-05-04",
          name: "王小虎",
          address: " 1517 弄",
        },
        {
          date: "2016-05-01",
          name: "王小虎",
          address: " 1519 弄",
        },
        {
          date: "2016-05-03",
          name: "王小虎",
          address: " 1516 弄",
        },
      ],
    };
  },
};
</script>
<style lang="less" scoped>
.main {
  .user-content {
    padding-bottom: 20px;
    margin-bottom: 20px;
    border-bottom: 1px solid#ccc;
    display: flex;
    align-items: center; //垂直居中
    .user-img {
      width: 150px;
      height: 150px;
      border-radius: 50%;
      margin-right: 40px;
    }
    .user-info {
      .user-name {
        font-size: 32px;
        margin-bottom: 10px;
      }
      .user-access {
        color: #999999;
      }
    }
  }
  .login-content {
    p {
      line-height: 28px;
      font-size: 14px;
      color: #999999;
      span {
        color: #666666;
        margin-left: 60px;
      }
    }
  }
  .home-right-info {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    .icon {
      width: 80px;
      height: 80px;
      font-size: 30px;
      text-align: center;
      line-height: 80px;
      color: #fff;
    }
    .detail-info {
      margin-left: 15px;
      display: flex;
      flex-direction: column;
      justify-content: center;


      .price {
        font-size: 30px;
        margin-bottom: 10px;
        line-height: 30px;
        height: 30px;
        text-align: left;
      }
      .desc {
        font-size: 14px;
        color: #999;
        text-align: left;
      }
    }
    .el-card {
      width: 32%;
      margin-bottom: 20px;
    }
  }
}
</style>

5、效果如下 image.png

13、Axios的使用

官网:https://www.axios-http.cn/docs/introopen in new window 1、npm安装 npm install axios

2、src目录下新建utils文件夹并在utils下新建request.js文件

import axios from "axios";
const http = axios.create({
    baseURL: '/api',//通用请求的地址
    timeout: 10000,//超时时间,10000毫秒,10秒


});
// 添加请求拦截器
http.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
}, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});


// 添加响应拦截器
http.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response;
}, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
});
export default http

3、src下新建api文件夹并新建index.js文件

import http from "@/utils/request";


//请求首页的数据
export const getData = () => {
    return http.get('/home/getData');
}

4、在Home.vue首页使用

<template>
  <div class="main">
    <el-row>
      <el-col :span="8"
        ><div class="grid-content bg-purple">
          <el-card class="box-card">
            <div class="user-content">
              <img src="@/assets/img/user.jpg" class="user-img" />
              <div class="user-info">
                <p class="user-name">Admin</p>
                <p class="user-access">超级管理员</p>
              </div>
            </div>
            <div class="login-content">
              <p>上次登录的时间:<span>2023-02-25</span></p>
              <p>上次登录的地点:<span>西安</span></p>
            </div>
          </el-card>
          <el-card class="box-card" style="margin-top: 20px">
            <el-table :data="tableData" style="width: 100%">
              <el-table-column
                v-for="(val, key) in tableLabel"
                :prop="key"
                :label="val"
                :key="key"
              />


              <!-- <el-table-column prop="date" label="日期" width="180">
              </el-table-column>
              <el-table-column prop="name" label="姓名" width="180">
              </el-table-column>
              <el-table-column prop="address" label="地址">
              </el-table-column>  -->
            </el-table></el-card
          >
        </div></el-col
      >
      <el-col :span="16">
        <div class="home-right-info">
          <el-card
            v-for="item in countData"
            :key="item.name"
            :body-style="{ display: 'flex' }"
          >
            <i
              class="icon"
              :class="`el-icon-${item.icon}`"
              :style="{ background: item.color }"
            ></i>
            <div class="detail-info">
              <p class="price">{{ item.value }}</p>
              <p class="desc">{{ item.name }}</p>
            </div>
          </el-card>
        </div>
      </el-col>
    </el-row>
  </div>
</template>
<script>
import { getData } from "@/api";
export default {
  name: "Home",
  data() {
    return {
      countData: [
        {
          name: "样式1",
          value: "样式1",
          icon: "success",
          color: "#2ec7c9",
        },
        {
          name: "样式2",
          value: "样式2",
          icon: "star-on",
          color: "#ffb980",
        },
        {
          name: "样式3",
          value: "样式3",
          icon: "s-goods",
          color: "#5ab1ef",
        },
        {
          name: "样式4",
          value: "样式4",
          icon: "user",
          color: "#2ec7c9",
        },
        {
          name: "样式5",
          value: "样式5",
          icon: "s-home",
          color: "#ffb980",
        },
        {
          name: "样式6",
          value: "样式6",
          icon: "folder",
          color: "#5ab1ef",
        },
      ],
      tableLabel: {
        name: "姓名",
        date: "日期",
        address: "地址",
      },
      tableData: [
        {
          date: "2016-05-02",
          name: "王小虎",
          address: " 1518 弄",
        },
        {
          date: "2016-05-04",
          name: "王小虎",
          address: " 1517 弄",
        },
        {
          date: "2016-05-01",
          name: "王小虎",
          address: " 1519 弄",
        },
        {
          date: "2016-05-03",
          name: "王小虎",
          address: " 1516 弄",
        },
      ],
    };
  },
  mounted() {
    getData().then((res) => {
      console.log(res);
    });
  },
};
</script>
<style lang="less" scoped>
.main {
  .user-content {
    padding-bottom: 20px;
    margin-bottom: 20px;
    border-bottom: 1px solid#ccc;
    display: flex;
    align-items: center; //垂直居中
    .user-img {
      width: 150px;
      height: 150px;
      border-radius: 50%;
      margin-right: 40px;
    }
    .user-info {
      .user-name {
        font-size: 32px;
        margin-bottom: 10px;
      }
      .user-access {
        color: #999999;
      }
    }
  }
  .login-content {
    p {
      line-height: 28px;
      font-size: 14px;
      color: #999999;
      span {
        color: #666666;
        margin-left: 60px;
      }
    }
  }
  .home-right-info {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    .icon {
      width: 80px;
      height: 80px;
      font-size: 30px;
      text-align: center;
      line-height: 80px;
      color: #fff;
    }
    .detail-info {
      margin-left: 15px;
      display: flex;
      flex-direction: column;
      justify-content: center;


      .price {
        font-size: 30px;
        margin-bottom: 10px;
        line-height: 30px;
        height: 30px;
        text-align: left;
      }
      .desc {
        font-size: 14px;
        color: #999;
        text-align: left;
      }
    }
    .el-card {
      width: 32%;
      margin-bottom: 20px;
    }
  }
}
</style>

5、游览器效果 因为没有后端,也没有用mock,所以这个是正常的 image.png

14、Mock.js模拟数据

官网:http://mockjs.com/open in new window 1、npm安装 npm install mockjs 2、在src下的api目录下新建mock.js文件

import Mock from 'mockjs'
import homeApi from './mockServerData/home';


// Mock.mock('/api/home/getData',function(){
//     //拦截到請求後的處理邏輯
//     console.log('攔截到了')
// })


Mock.mock('/api/home/getData', homeApi.getResultData)

3、在src下的api下新建mockServerData文件夹并新建home.js

// mock数据模拟
import Mock from 'mockjs'


// 图表数据
let List = []
export default {
    getResultData: () => {
        //Mock.Random.float 产生随机数100到8000之间 保留小数 最小0位 最大0位
        for (let i = 0; i < 7; i++) {
            List.push(
                Mock.mock({
                    test1: Mock.Random.float(100, 8000, 0, 0),
                    test2: Mock.Random.float(100, 8000, 0, 0),
                    test3: Mock.Random.float(100, 8000, 0, 0),
                    test4: Mock.Random.float(100, 8000, 0, 0),
                    test5: Mock.Random.float(100, 8000, 0, 0),
                    test6: Mock.Random.float(100, 8000, 0, 0)
                })
            )
        }
        return {
            code: 20000,
            data: {
                // 饼图
                videoData: [
                    {
                        name: '小米',
                        value: 999
                    },
                    {
                        name: '苹果',
                        value: 1999
                    },
                    {
                        name: 'vivo',
                        value: 1800
                    },
                    {
                        name: 'oppo',
                        value: 5999
                    },
                    {
                        name: '魅族',
                        value: 4500
                    },
                    {
                        name: '三星',
                        value: 5500
                    }
                ],
                // 柱状图
                userData: [
                    {
                        date: '周一',
                        new: 5,
                        active: 200
                    },
                    {
                        date: '周二',
                        new: 10,
                        active: 500
                    },
                    {
                        date: '周三',
                        new: 12,
                        active: 550
                    },
                    {
                        date: '周四',
                        new: 60,
                        active: 800
                    },
                    {
                        date: '周五',
                        new: 65,
                        active: 550
                    },
                    {
                        date: '周六',
                        new: 53,
                        active: 770
                    },
                    {
                        date: '周日',
                        new: 33,
                        active: 170
                    }
                ],
                // 折线图
                orderData: {
                    date: ['20191001', '20191002', '20191003', '20191004', '20191005', '20191006', '20191007'],
                    data: List
                },
                tableData: [
                    {
                        name: '王小虎1',
                        address: '北京1',
                        date: '2016-05-03',
                    },
                    {
                        name: '王小虎2',
                        address: '北京2',
                        date: '2016-05-03',
                    },
                    {
                        name: '王小虎3',
                        address: '北京3',
                        date: '2016-05-03',
                    },
                    {
                        name: '王小虎4',
                        address: '北京4',
                        date: '2016-05-03',
                    },
                    {
                        name: '王小虎5',
                        address: '北京5',
                        date: '2016-05-03',


                    },
                    {
                        name: '王小虎6',
                        address: '北京6',
                        date: '2016-05-03',
                    }
                ]
            }
        }
    }
}

4、在main.js里导入

import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import router from './router';//如果router文件夹下有index.js就不需要'./router/index.js'
import store from './store'
import '@/api/mock'


Vue.config.productionTip = false
Vue.use(ElementUI);
new Vue({
  router,//挂载路由
  store,
  render: h => h(App),
}).$mount('#app')

5、效果如下 image.png

15、可视化图表

1、页面布局 Home.vue

<template>
  <div class="main">
    <el-row>
      <el-col :span="8" style="padding-right: 10px"
        ><div class="grid-content bg-purple">
          <el-card class="box-card">
            <div class="user-content">
              <img src="@/assets/img/user.jpg" class="user-img" />
              <div class="user-info">
                <p class="user-name">Admin</p>
                <p class="user-access">超级管理员</p>
              </div>
            </div>
            <div class="login-content">
              <p>上次登录的时间:<span>2023-02-25</span></p>
              <p>上次登录的地点:<span>西安</span></p>
            </div>
          </el-card>
          <el-card class="box-card" style="margin-top: 20px">
            <el-table :data="tableData" style="width: 100%">
              <el-table-column
                v-for="(val, key) in tableLabel"
                :prop="key"
                :label="val"
                :key="key"
              />


              <!-- <el-table-column prop="date" label="日期" width="180">
              </el-table-column>
              <el-table-column prop="name" label="姓名" width="180">
              </el-table-column>
              <el-table-column prop="address" label="地址">
              </el-table-column>  -->
            </el-table></el-card
          >
        </div></el-col
      >
      <el-col :span="16" style="padding-left: 10px">
        <div class="home-right-info">
          <el-card
            v-for="item in countData"
            :key="item.name"
            :body-style="{ display: 'flex' }"
          >
            <i
              class="icon"
              :class="`el-icon-${item.icon}`"
              :style="{ background: item.color }"
            ></i>
            <div class="detail-info">
              <p class="price">{{ item.value }}</p>
              <p class="desc">{{ item.name }}</p>
            </div>
          </el-card>
        </div>
        <el-card style="height: 280px">
          <!-- 折线图 -->
        </el-card>
        <div class="home-right-bottom">
          <el-card></el-card>
          <el-card></el-card>
        </div>
      </el-col>
    </el-row>
  </div>
</template>
<script>
import { getData } from "@/api";
export default {
  name: "Home",
  data() {
    return {
      countData: [
        {
          name: "样式1",
          value: "样式1",
          icon: "success",
          color: "#2ec7c9",
        },
        {
          name: "样式2",
          value: "样式2",
          icon: "star-on",
          color: "#ffb980",
        },
        {
          name: "样式3",
          value: "样式3",
          icon: "s-goods",
          color: "#5ab1ef",
        },
        {
          name: "样式4",
          value: "样式4",
          icon: "user",
          color: "#2ec7c9",
        },
        {
          name: "样式5",
          value: "样式5",
          icon: "s-home",
          color: "#ffb980",
        },
        {
          name: "样式6",
          value: "样式6",
          icon: "folder",
          color: "#5ab1ef",
        },
      ],
      tableLabel: {
        name: "姓名",
        date: "日期",
        address: "地址",
      },
      tableData: [
        {
          date: "2016-05-02",
          name: "王小虎",
          address: " 1518 弄",
        },
        {
          date: "2016-05-04",
          name: "王小虎",
          address: " 1517 弄",
        },
        {
          date: "2016-05-01",
          name: "王小虎",
          address: " 1519 弄",
        },
        {
          date: "2016-05-03",
          name: "王小虎",
          address: " 1516 弄",
        },
      ],
    };
  },
  mounted() {
    getData().then((res) => {
      this.tableData = res.data.data.tableData;
      console.log(res);
    });
  },
};
</script>
<style lang="less" scoped>
.main {
  .user-content {
    padding-bottom: 20px;
    margin-bottom: 20px;
    border-bottom: 1px solid#ccc;
    display: flex;
    align-items: center; //垂直居中
    .user-img {
      width: 150px;
      height: 150px;
      border-radius: 50%;
      margin-right: 40px;
    }
    .user-info {
      .user-name {
        font-size: 32px;
        margin-bottom: 10px;
      }
      .user-access {
        color: #999999;
      }
    }
  }
  .login-content {
    p {
      line-height: 28px;
      font-size: 14px;
      color: #999999;
      span {
        color: #666666;
        margin-left: 60px;
      }
    }
  }
  .home-right-info {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    .icon {
      width: 80px;
      height: 80px;
      font-size: 30px;
      text-align: center;
      line-height: 80px;
      color: #fff;
    }
    .detail-info {
      margin-left: 15px;
      display: flex;
      flex-direction: column;
      justify-content: center;


      .price {
        font-size: 30px;
        margin-bottom: 10px;
        line-height: 30px;
        height: 30px;
        text-align: left;
      }
      .desc {
        font-size: 14px;
        color: #999;
        text-align: left;
      }
    }
    .el-card {
      width: 32%;
      margin-bottom: 20px;
    }
  }
  .home-right-bottom {
    display: flex;
    justify-content: space-between;
    .el-card {
      width: 48%;
      height: 260px;
      margin-top: 20px;
    }
  }
}
</style>

2、main.vue样式调整 添加如下样式(添加这个主要是电脑的分辨率不同页面的样式可能会错乱,就是左边菜单和首页的高度不在一个水平线)

.el-main {
  height: calc(100vh - 70px);
}

完整文件

<template>
  <div>
    <el-container>
      <el-aside width="auto"><common-aside /></el-aside>
      <el-container>
        <el-header><common-header /></el-header>
        <el-main>
          <!-- 子路由出口 -->
          <!-- 子路由匹配到的组件将渲染在这里 -->
          <router-view></router-view>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>
 <script>
import CommonAside from "@/components/CommonAside.vue";
import CommonHeader from "@/components/CommonHeader.vue";
export default {
  name: "Main",
  components: {
    CommonAside,
    CommonHeader,
  },
  data() {
    return {};
  },
};
</script>
 <style scoped>
.el-header {
  padding: 0;
}
.el-main {
  height: calc(100vh - 70px);
}
</style>

3、效果如下 image.png

16、Echarts

1、官网:https://echarts.apache.org/zh/index.htmlopen in new window 快速上手:https://echarts.apache.org/handbook/zh/get-startedopen in new window 2、npm安装 注意指定版本

npm install echarts@5.1.2

3、home.vue页面引入 import * as  echarts from 'echarts' 4、编写代码

<template>
  <div class="main">
    <el-row>
      <el-col :span="8" style="padding-right: 10px"
        ><div class="grid-content bg-purple">
          <el-card class="box-card">
            <div class="user-content">
              <img src="@/assets/img/user.jpg" class="user-img" />
              <div class="user-info">
                <p class="user-name">Admin</p>
                <p class="user-access">超级管理员</p>
              </div>
            </div>
            <div class="login-content">
              <p>上次登录的时间:<span>2023-02-25</span></p>
              <p>上次登录的地点:<span>西安</span></p>
            </div>
          </el-card>
          <el-card class="box-card" style="margin-top: 20px">
            <el-table :data="tableData" style="width: 100%">
              <el-table-column
                v-for="(val, key) in tableLabel"
                :prop="key"
                :label="val"
                :key="key"
              />


              <!-- <el-table-column prop="date" label="日期" width="180">
              </el-table-column>
              <el-table-column prop="name" label="姓名" width="180">
              </el-table-column>
              <el-table-column prop="address" label="地址">
              </el-table-column>  -->
            </el-table></el-card
          >
        </div></el-col
      >
      <el-col :span="16" style="padding-left: 10px">
        <div class="home-right-info">
          <el-card
            v-for="item in countData"
            :key="item.name"
            :body-style="{ display: 'flex' }"
          >
            <i
              class="icon"
              :class="`el-icon-${item.icon}`"
              :style="{ background: item.color }"
            ></i>
            <div class="detail-info">
              <p class="price">{{ item.value }}</p>
              <p class="desc">{{ item.name }}</p>
            </div>
          </el-card>
        </div>
        <el-card style="height: 280px">
          <!-- 折线图 -->
          <div ref="echartsLine" style="height:280px;"></div>
        </el-card>
        <div class="home-right-bottom">
          <el-card></el-card>
          <el-card></el-card>
        </div>
      </el-col>
    </el-row>
  </div>
</template>
<script>
import { getData } from "@/api";
import * as echarts from "echarts";
export default {
  name: "Home",
  data() {
    return {
      countData: [
        {
          name: "样式1",
          value: "样式1",
          icon: "success",
          color: "#2ec7c9",
        },
        {
          name: "样式2",
          value: "样式2",
          icon: "star-on",
          color: "#ffb980",
        },
        {
          name: "样式3",
          value: "样式3",
          icon: "s-goods",
          color: "#5ab1ef",
        },
        {
          name: "样式4",
          value: "样式4",
          icon: "user",
          color: "#2ec7c9",
        },
        {
          name: "样式5",
          value: "样式5",
          icon: "s-home",
          color: "#ffb980",
        },
        {
          name: "样式6",
          value: "样式6",
          icon: "folder",
          color: "#5ab1ef",
        },
      ],
      tableLabel: {
        name: "姓名",
        date: "日期",
        address: "地址",
      },
      tableData: [
        {
          date: "2016-05-02",
          name: "王小虎",
          address: " 1518 弄",
        },
        {
          date: "2016-05-04",
          name: "王小虎",
          address: " 1517 弄",
        },
        {
          date: "2016-05-01",
          name: "王小虎",
          address: " 1519 弄",
        },
        {
          date: "2016-05-03",
          name: "王小虎",
          address: " 1516 弄",
        },
      ],
    };
  },
  mounted() {
    getData().then((res) => {
      this.tableData = res.data.data.tableData;

      //需要定义一个<div ref="echartsLine" style="height:280px;"></div>
      // 1、基于准备好的dom,初始化echarts实例
      const myChartLine = echarts.init(this.$refs.echartsLine);
      //2、 指定图表的配置项和数据
      var optionLine = {};
      //3、获取数据 xAxis
      const { orderData } = res.data.data;
      const xAxisLine = Object.keys(orderData.data[0]);
      const xAxisData = { data: xAxisLine };
      optionLine.xAxis = xAxisData;
      optionLine.yAxis = {};
      optionLine.legend = xAxisData;

      optionLine.series = [];
    	//封装series
      xAxisLine.forEach((key) => {
        optionLine.series.push({
          name: key,
          data: orderData.data.map((item) => item[key]),
          type: "line",
        });
      });
      console.log(optionLine);
      //4、使用刚指定的配置项和数据显示图表。
      myChartLine.setOption(optionLine);
    });
  },
};
</script>
<style lang="less" scoped>
.main {
  .user-content {
    padding-bottom: 20px;
    margin-bottom: 20px;
    border-bottom: 1px solid#ccc;
    display: flex;
    align-items: center; //垂直居中
    .user-img {
      width: 150px;
      height: 150px;
      border-radius: 50%;
      margin-right: 40px;
    }
    .user-info {
      .user-name {
        font-size: 32px;
        margin-bottom: 10px;
      }
      .user-access {
        color: #999999;
      }
    }
  }
  .login-content {
    p {
      line-height: 28px;
      font-size: 14px;
      color: #999999;
      span {
        color: #666666;
        margin-left: 60px;
      }
    }
  }
  .home-right-info {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    .icon {
      width: 80px;
      height: 80px;
      font-size: 30px;
      text-align: center;
      line-height: 80px;
      color: #fff;
    }
    .detail-info {
      margin-left: 15px;
      display: flex;
      flex-direction: column;
      justify-content: center;


      .price {
        font-size: 30px;
        margin-bottom: 10px;
        line-height: 30px;
        height: 30px;
        text-align: left;
      }
      .desc {
        font-size: 14px;
        color: #999;
        text-align: left;
      }
    }
    .el-card {
      width: 32%;
      margin-bottom: 20px;
    }
  }
  .home-right-bottom {
    display: flex;
    justify-content: space-between;
    .el-card {
      width: 48%;
      height: 260px;
      margin-top: 20px;
    }
  }
}
</style>

5、如果报错如下,需要给div定义一个高度

Can't get DOM width or height. Please check dom.clientWidth and dom.clientHeight. They should not be 0.For example, you may need to call this in the callback of window.onload.

6、数据结构 image.png 7、官网例子代码 注意官网的这个是柱状图,通过type=bar指定,这个项目上边用的是折线图,type:=line, 在绘图前我们需要为 ECharts 准备一个定义了高宽的 DOM 容器。在刚才的例子 head 之后,添加:

<body>
  <!-- 为 ECharts 准备一个定义了宽高的 DOM -->
  <div id="main" style="width: 600px;height:400px;"></div>
</body>

然后就可以通过 echarts.initopen in new window 方法初始化一个 echarts 实例并通过 setOptionopen in new window 方法生成一个简单的柱状图,下面是完整代码。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>ECharts</title>
    <!-- 引入刚刚下载的 ECharts 文件 -->
    <script src="echarts.js"></script>
  </head>
  <body>
    <!-- 为 ECharts 准备一个定义了宽高的 DOM -->
    <div id="main" style="width: 600px;height:400px;"></div>
    <script type="text/javascript">
      // 基于准备好的dom,初始化echarts实例
      var myChart = echarts.init(document.getElementById('main'));

      // 指定图表的配置项和数据
      var option = {
        title: {
          text: 'ECharts 入门示例'
        },
        tooltip: {},
        legend: {
          data: ['销量']
        },
        xAxis: {
          data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
        },
        yAxis: {},
        series: [
          {
            name: '销量',
            type: 'bar',
            data: [5, 20, 36, 10, 10, 20]
          }
        ]
      };

      // 使用刚指定的配置项和数据显示图表。
      myChart.setOption(option);
    </script>
  </body>
</html>

这样你的第一个图表就诞生了! image.png

8、本项目的效果 image.png 9、柱状图和饼状图

<template>
  <div class="main">
    <el-row>
      <el-col :span="8" style="padding-right: 10px"
        ><div class="grid-content bg-purple">
          <el-card class="box-card">
            <div class="user-content">
              <img src="@/assets/img/user.jpg" class="user-img" />
              <div class="user-info">
                <p class="user-name">Admin</p>
                <p class="user-access">超级管理员</p>
              </div>
            </div>
            <div class="login-content">
              <p>上次登录的时间:<span>2023-02-25</span></p>
              <p>上次登录的地点:<span>西安</span></p>
            </div>
          </el-card>
          <el-card class="box-card" style="margin-top: 20px">
            <el-table :data="tableData" style="width: 100%">
              <el-table-column
                v-for="(val, key) in tableLabel"
                :prop="key"
                :label="val"
                :key="key"
              />


              <!-- <el-table-column prop="date" label="日期" width="180">
              </el-table-column>
              <el-table-column prop="name" label="姓名" width="180">
              </el-table-column>
              <el-table-column prop="address" label="地址">
              </el-table-column>  -->
            </el-table></el-card
          >
        </div></el-col
      >
      <el-col :span="16" style="padding-left: 10px">
        <div class="home-right-info">
          <el-card
            v-for="item in countData"
            :key="item.name"
            :body-style="{ display: 'flex' }"
          >
            <i
              class="icon"
              :class="`el-icon-${item.icon}`"
              :style="{ background: item.color }"
            ></i>
            <div class="detail-info">
              <p class="price">{{ item.value }}</p>
              <p class="desc">{{ item.name }}</p>
            </div>
          </el-card>
        </div>
        <el-card style="height: 280px">
          <!-- 折线图 -->
          <div ref="echartsLine" style="height: 280px"></div>
        </el-card>


        <div class="home-right-bottom">
          <el-card style="height: 260px">
            <!-- 柱状图 -->
            <div ref="echartsBar1" style="height: 260px"></div>
          </el-card>
          <el-card style="height: 260px">
            <!-- 柱状图 -->
            <div ref="echartsPie" style="height: 240px"></div>
          </el-card>
        </div>
      </el-col>
    </el-row>
  </div>
</template>
<script>
import { getData } from "@/api";
import * as echarts from "echarts";
export default {
  name: "Home",
  data() {
    return {
      countData: [
        {
          name: "样式1",
          value: "样式1",
          icon: "success",
          color: "#2ec7c9",
        },
        {
          name: "样式2",
          value: "样式2",
          icon: "star-on",
          color: "#ffb980",
        },
        {
          name: "样式3",
          value: "样式3",
          icon: "s-goods",
          color: "#5ab1ef",
        },
        {
          name: "样式4",
          value: "样式4",
          icon: "user",
          color: "#2ec7c9",
        },
        {
          name: "样式5",
          value: "样式5",
          icon: "s-home",
          color: "#ffb980",
        },
        {
          name: "样式6",
          value: "样式6",
          icon: "folder",
          color: "#5ab1ef",
        },
      ],
      tableLabel: {
        name: "姓名",
        date: "日期",
        address: "地址",
      },
      tableData: [
        {
          date: "2016-05-02",
          name: "王小虎",
          address: " 1518 弄",
        },
        {
          date: "2016-05-04",
          name: "王小虎",
          address: " 1517 弄",
        },
        {
          date: "2016-05-01",
          name: "王小虎",
          address: " 1519 弄",
        },
        {
          date: "2016-05-03",
          name: "王小虎",
          address: " 1516 弄",
        },
      ],
    };
  },
  mounted() {
    getData().then((res) => {
      this.tableData = res.data.data.tableData;
      //折线图start
      // 1、基于准备好的dom,初始化echarts实例
      const myChartLine = echarts.init(this.$refs.echartsLine);
      //2、 指定图表的配置项和数据
      var optionLine = {};
      //3、获取数据 xAxis
      const { orderData, userData, videoData } = res.data.data;
      const xAxisLine = Object.keys(orderData.data[0]);
      const xAxisData = { data: xAxisLine };
      optionLine.xAxis = xAxisData;
      optionLine.yAxis = {};
      optionLine.legend = xAxisData;


      optionLine.series = [];


      xAxisLine.forEach((key) => {
        optionLine.series.push({
          name: key,
          data: orderData.data.map((item) => item[key]),
          type: "line",
        });
      });
      console.log(optionLine);
      //4、使用刚指定的配置项和数据显示图表。
      myChartLine.setOption(optionLine);
      //折线图end


      //柱状图1start
      // 1、基于准备好的dom,初始化echarts实例
      const myChartBar1 = echarts.init(this.$refs.echartsBar1);
      //2、 指定图表的配置项和数据
      var optionBar1 = {
        legend: {
          // 图例文字颜色
          textStyle: {
            color: "#333",
          },
        },
        grid: {
          left: "20%",
        },
        // 提示框
        tooltip: {
          trigger: "axis",
        },
        xAxis: {
          type: "category", // 类目轴
          data: [],
          axisLine: {
            lineStyle: {
              color: "#17b3a3",
            },
          },
          axisLabel: {
            interval: 0,
            color: "#333",
          },
        },
        yAxis: [
          {
            type: "value",
            axisLine: {
              lineStyle: {
                color: "#17b3a3",
              },
            },
          },
        ],
        color: ["#2ec7c9", "#b6a2de"],
        series: [
          {
            name: "新增用户",
            data: userData.map((item) => {
              return item.new;
            }),
            type: "bar",
          },
          {
            name: "活跃用户",
            data: userData.map((item) => {
              return item.active;
            }),


            type: "bar",
          },
          // {
          //   name: "新增用户",
          //   data: userData.map((item) => item.new),
          //   type: "bar",
          // },
          // {
          //   name: "活跃用户",
          //   data: userData.map((item) => item.active),
          //   type: "bar",
          // },
        ],
      };
      //4、使用刚指定的配置项和数据显示图表。
      myChartBar1.setOption(optionBar1);
      //柱状图1end


      //饼状图1start
      // 1、基于准备好的dom,初始化echarts实例
      const myChartPie = echarts.init(this.$refs.echartsPie);
      //2、 指定图表的配置项和数据
      //饼状图1end
      const optionPie = {
        tooltip: {
          trigger: "item",
        },
        color: [
          "#0f78f4",
          "#dd536b",
          "#9462e5",
          "#a6a6a6",
          "#e1bb22",
          "#39c362",
          "#3ed1cf",
        ],
        series: [{ data: videoData, type: "pie" }],
      };
      myChartPie.setOption(optionPie);
    });
  },
};
</script>
<style lang="less" scoped>
.main {
  .user-content {
    padding-bottom: 20px;
    margin-bottom: 20px;
    border-bottom: 1px solid#ccc;
    display: flex;
    align-items: center; //垂直居中
    .user-img {
      width: 150px;
      height: 150px;
      border-radius: 50%;
      margin-right: 40px;
    }
    .user-info {
      .user-name {
        font-size: 32px;
        margin-bottom: 10px;
      }
      .user-access {
        color: #999999;
      }
    }
  }
  .login-content {
    p {
      line-height: 28px;
      font-size: 14px;
      color: #999999;
      span {
        color: #666666;
        margin-left: 60px;
      }
    }
  }
  .home-right-info {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    .icon {
      width: 80px;
      height: 80px;
      font-size: 30px;
      text-align: center;
      line-height: 80px;
      color: #fff;
    }
    .detail-info {
      margin-left: 15px;
      display: flex;
      flex-direction: column;
      justify-content: center;


      .price {
        font-size: 30px;
        margin-bottom: 10px;
        line-height: 30px;
        height: 30px;
        text-align: left;
      }
      .desc {
        font-size: 14px;
        color: #999;
        text-align: left;
      }
    }
    .el-card {
      width: 32%;
      margin-bottom: 20px;
    }
  }
  .home-right-bottom {
    display: flex;
    justify-content: space-between;
    .el-card {
      width: 48%;
      height: 260px;
      margin-top: 20px;
    }
  }
}
</style>

10、效果

image.png

17、面包屑

1、分析 1.1、默认有一个首页 1.2、点击菜单添加一个面包屑 1.3、选择已有数据,不会重复添加 1.4、使用vuex管理面包屑 2、实现 官网:https://element.eleme.cn/#/zh-CN/component/breadcrumbopen in new window 2.1、CommonAside.vue点击左侧菜单的事件里触发vuex的menu.js的breadcrumbChange的mutations。

<template>
  <div>
    <el-menu
      default-active="1-4-1"
      class="el-menu-vertical-demo"
      @open="handleOpen"
      @close="handleClose"
      :collapse="isCollapse"
      background-color="#545c64"
      text-color="#fff"
      active-text-color="#ffd04b"
    >
      <h3>{{ isCollapse ? "后台" : "通用后台管理系统" }}</h3>
      <el-menu-item
        v-for="item in noChildren"
        :key="item.name"
        :index="item.path"
        @click="clickMenu(item)"
      >
        <i :class="`el-icon-${item.icon}`"></i>
        <span slot="title">{{ item.label }}</span>
      </el-menu-item>
      <el-submenu
        v-for="item in hasChildren"
        :key="item.name"
        :index="item.name"
      >
        <template slot="title">
          <i :class="`el-icon-${item.icon}`"></i>
          <span slot="title">{{ item.label }}</span>
        </template>
        <el-menu-item-group
          v-for="subItem in item.children"
          :key="subItem.path"
        >
          <el-menu-item :index="subItem.path" @click="clickMenu(subItem)">{{
            subItem.label
          }}</el-menu-item>
        </el-menu-item-group>
      </el-submenu>
    </el-menu>
  </div>
</template>
<style lang="less" scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
  width: 200px;
  min-height: 400px;
}
.el-menu {
  //height: calc(100vh - 0px);
  border-right: 0px;
  height: 100vh;
  h3 {
    color: #fff;
    text-align: center;
    line-height: 48px;
    font-size: 16px;
    font-weight: 400px;
  }
}
</style>
<script>
export default {
  data() {
    return {
      //isCollapse: false,
      menuData: [
        {
          path: "/",
          name: "home",
          label: "首页",
          icon: "s-home",
          url: "Home/Home",
        },
        {
          label: "权限管理",
          name: "system",
          icon: "location",
          children: [
            {
              path: "/user",
              name: "user",
              label: "用户管理",
              icon: "setting",
              url: "Other/PageOne",
            },
            {
              path: "/role",
              name: "role",
              label: "角色管理",
              icon: "setting",
              url: "Other/PageTwo",
            },
            {
              path: "/menu",
              name: "menu",
              label: "菜单管理",
              icon: "setting",
              url: "Other/PageTwo",
            },
          ],
        },
      ],
    };
  },
  methods: {
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    },
    clickMenu(item) {
      //当页面的路由和跳转的路由不一致才允许跳转
      //this.$route.path 上一个路径,item.path下一个路径
      if (
        this.$route.path !== item.path &&
        !(this.$route.path === "/home" && item.path === "/")
      ) {
        this.$router.push(item.path);
      }
      this.$store.commit("breadcrumbChange", item);
    },
  },
  computed: {
    //没有子菜单
    noChildren() {
      return this.menuData.filter((item) => !item.children);
    },
    //有子菜单
    hasChildren() {
      return this.menuData.filter((item) => item.children);
    },
    //vuex取得菜单展开收起的值,但是data里不能重复定义
    isCollapse() {
      return this.$store.state.menu.isCollapse;
    },
  },
};
</script>

2.2、在menu.js里新增breadcrumbList的state和breadcrumbChange的mutations。

export default {
    state: {
        isCollapse: false, //用于控制菜单的展开收起
        breadcrumbList: [
            //默认的数据
            {
                path: "/",
                name: "home",
                label: "首页",
                icon: "s-home",
                url: "Home/Home",
            }
        ]//面包屑的数据
    },
    mutations: {
        //用于控制菜单的展开收起
        handleCollapse(state) {
            state.isCollapse = !state.isCollapse
        },
        //更新面包屑数据
        breadcrumbChange(state, val) {
            //判断添加的数据是否是首页
            if (val.name !== 'name') {
                const index = state.breadcrumbList.findIndex(item => item.name === val.name)
                //如果不存在就添加道面包屑数组里
                if (index === -1) {
                    state.breadcrumbList.push(val)
                }
            }


        }
    }
}

2.3、CommonHeader.vue添加面包屑和计算属性breadcrumbList,从vuex获取breadcrumbList

<template>
  <div class="header-main">
    <div class="l-content">
      <el-button
        icon="el-icon-menu"
        @click="handleIsCollapse"
        size="mini"
      ></el-button>
      <!-- 面包屑 -->
      <el-breadcrumb separator="/">
        <el-breadcrumb-item
          v-for="item in breadcrumbList"
          :key="item.path"
          :to="{ path: item.path }"
          >{{ item.label }}</el-breadcrumb-item
        >
      </el-breadcrumb>
    </div>
    <div class="r-content">
      <el-dropdown>
        <img src="@/assets/img/user.jpg" class="user" />
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item>个人中心</el-dropdown-item>
          <el-dropdown-item>退出</el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
  </div>
</template>


<script>
import { mapState } from "vuex";
export default {
  data() {
    return {};
  },
  methods: {
    handleIsCollapse() {
      this.$store.commit("handleCollapse");
    },
  },
  computed: {
    ...mapState({
      breadcrumbList: (state) => state.menu.breadcrumbList,
    }),
  },
};
</script>
<style lang="less" scoped>
.header-main {
  padding: 0 20px;
  background-color: #333;
  height: 60px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  .text {
    color: #ffff;
    font-size: 14px;
    margin-left: 10px;
  }
  .r-content {
    .user {
      width: 40px;
      height: 40px;
      border-radius: 50%;
    }
  }
  .l-content {
    display: flex;
    align-items: center;
    ///deep/样式穿刺
    /deep/.el-breadcrumb__item {
      .el-breadcrumb__inner {
        font-weight: normal;
        //设置面包屑字体为灰色,最后一个除外
        //<span role="link" class="el-breadcrumb__inner is-link">菜单管理</span>
        &.is-link {
          color: #666;
        }
      }
      //设置最后一个面包屑字体为白色
      //<span role="link" class="el-breadcrumb__inner is-link">用户管理</span>
      &:last-child {
        .el-breadcrumb__inner {
          color: #fff;
        }
      }
    }
  }
}
</style>

2.4、页面样式

image.png 2.6、效果 image.png

18、Tag 标签

1、官网:https://element.eleme.cn/#/zh-CN/component/tagopen in new window 2、新建组件CommonTag.vue

<template>
  <div class="tag-content">
    <el-tag
      v-for="item in tagList"
      :key="item.label"
      :closable="item.name !== 'home'"
      :effect="item.name === $route.name ? 'dark' : 'plain'"
    >
      {{ item.label }}
    </el-tag>
  </div>
</template>
<script>
import { mapState } from "vuex";
export default {
  name: "CommonTag",
  data() {
    return {};
  },
  computed: {
    ...mapState({
      tagList: (state) => state.menu.breadcrumbList,//这里实际和面包屑是一个数据
    }),
  },
};
</script>

3、main.vue引入tag组件

<template>
  <div>
    <el-container>
      <el-aside width="auto"><common-aside /></el-aside>
      <el-container>
        <el-header><common-header /></el-header>
        <common-tag/>
        <el-main>
          <!-- 子路由出口 -->
          <!-- 子路由匹配到的组件将渲染在这里 -->
          <router-view></router-view>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>
 <script>
import CommonAside from "@/components/CommonAside.vue";
import CommonHeader from "@/components/CommonHeader.vue";
import CommonTag from "@/components/CommonTag.vue"


export default {
  name: "Main",
  components: {
    CommonAside,
    CommonHeader,
    CommonTag,
  },
  data() {
    return {};
  },
};
</script>
 <style scoped>
.el-header {
  padding: 0;
}
.el-main {
  height: calc(100vh - 70px);
}
</style>

4、route下的index.js文件的路由加上name属性,否则tag页面的判断无效

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/views/Home.vue'
import User from '@/views/User.vue'
import Main from '@/views/Main.vue'
Vue.use(VueRouter)


// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
// 1. 定义 (路由) 组件。
// 可以从其他文件 import 进来
// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
  //主路由
  {
    path: '/',
    component: Main,
    redirect: '/home',
    children: [
      //子路由
      { path: 'home', name: "home", component: Home },//首页
      { path: 'user', name: "user", component: User },//用户管理
      { path: 'role', name: "role", component: () => import("@/views/Role.vue") },//角色管理
      { path: 'menu', name: "menu", component: () => import("@/views/Menu.vue") },//菜单管理管理
    ]
  },


]


// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
  routes // (缩写) 相当于 routes: routes
})


// 4. 创建和挂载根实例。挂载到main.js的根节点
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
// const app = new Vue({
//   router
// }).$mount('#app')
// 获取原型对象push函数
const originalPush = VueRouter.prototype.push


// 获取原型对象replace函数
const originalReplace = VueRouter.prototype.replace


// 修改原型对象中的push函数
VueRouter.prototype.push = function push(location) {
  return originalPush.call(this, location).catch(err => err)
}


// 修改原型对象中的replace函数
VueRouter.prototype.replace = function replace(location) {
  return originalReplace.call(this, location).catch(err => err)
}


export default router

加入以下内容是为了解决多次点击面包屑跳转控制台报错的问题

// 获取原型对象push函数
const originalPush = VueRouter.prototype.push


// 获取原型对象replace函数
const originalReplace = VueRouter.prototype.replace


// 修改原型对象中的push函数
VueRouter.prototype.push = function push(location) {
  return originalPush.call(this, location).catch(err => err)
}


// 修改原型对象中的replace函数
VueRouter.prototype.replace = function replace(location) {
  return originalReplace.call(this, location).catch(err => err)
}

5、效果

image.png

6、tag点击跳转、删除、样式 6.1、menu.js定义删除指定的tag的mutations的closeTag方法

export default {
    state: {
        isCollapse: false, //用于控制菜单的展开收起
        breadcrumbList: [
            //默认的数据
            {
                path: "/",
                name: "home",
                label: "首页",
                icon: "s-home",
                url: "Home/Home",
            }
        ]//面包屑的数据
    },
    mutations: {
        //删除指定的tag
        closeTag(state, item) {
            const index = state.breadcrumbList.findIndex(val => { return val.name === item.name })
            state.breadcrumbList.splice(index, 1)
        },
        //用于控制菜单的展开收起
        handleCollapse(state) {
            state.isCollapse = !state.isCollapse
        },
        //更新面包屑数据
        breadcrumbChange(state, val) {
            //判断添加的数据是否是首页
            if (val.name !== 'name') {
                const index = state.breadcrumbList.findIndex(item => item.name === val.name)
                //如果不存在就添加道面包屑数组里
                if (index === -1) {
                    state.breadcrumbList.push(val)
                }
            }


        }
    }
}

6.2、tag页面定义changeTag的click事件和close的handleClose事件 6.3、使用mapMutations辅助函数 6.4、删除逻辑

//点击删除标签
    handleClose(item, index) {
      //
      //const length = this.tagList.length -1
      //this.$store.commit('')


      //调用store中的mutations
      this.closeTag(item);
      const length = this.tagList.length;
      //删除之后的跳转功能
      //1、如果要删除的tag不是当前高亮选中的路由,不做处理
      if (item.name !== this.$route.name) {
        return;
      }
      //2、如果要删除的tag和length相等,表示删除的是最后一项,自动向左移动,跳转到前一级
      if (index === length) {
        this.$router.push({ name: this.tagList[index - 1].name });
      }
      //3、如果要删除的tag是中间的,自动向后移动,跳转到后一级
      else {
        this.$router.push({ name: this.tagList[index].name });
      }
    }

6.5、完整代码

<template>
  <div class="tag-content">
    <el-tag
    size="small"
      v-for="(item, index) in tagList"
      :key="item.label"
      :closable="item.name !== 'home'"
      :effect="item.name === $route.name ? 'dark' : 'plain'"
      @click="changeTag(item)"
      @close="handleClose(item, index)"
    >
      {{ item.label }}
    </el-tag>
  </div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
  name: "CommonTag",
  data() {
    return {};
  },
  methods: {
    ...mapMutations(["closeTag"]),


    //点击删除标签
    handleClose(item, index) {
      //
      //const length = this.tagList.length -1
      //this.$store.commit('')


      //调用store中的mutations
      this.closeTag(item);
      const length = this.tagList.length;
      //删除之后的跳转功能
      //1、如果要删除的tag不是当前高亮选中的路由,不做处理
      if (item.name !== this.$route.name) {
        return;
      }
      //2、如果要删除的tag和length相等,表示删除的是最后一项,自动向左移动,跳转到前一级
      if (index === length) {
        this.$router.push({ name: this.tagList[index - 1].name });
      }
      //3、如果要删除的tag是中间的,自动向后移动,跳转到后一级
      else {
        this.$router.push({ name: this.tagList[index].name });
      }
    },


    //点击tag跳转页面
    changeTag(item) {
      //this.$router.push('/home')
      this.$router.push({ name: item.name });
    },
  },
  computed: {
    ...mapState({
      tagList: (state) => state.menu.breadcrumbList, //这里实际和面包屑是一个数据
    }),
  },
};
</script>
<style lang="less" scoped>
.tag-content {
    padding: 20px;
    .el-tag {
        margin-right: 15px;
        cursor: pointer;
    }
}
</style>

7、样式

<style lang="less" scoped>
.tag-content {
    padding: 20px;
    .el-tag {
        margin-right: 15px;
        cursor: pointer;
    }
}
</style>

8、效果 image.png

六、用户管理

1、用户表单新增页面(这个页面后边会有调整,可以参考下)

<template>
  <div class="user">
    <el-dialog :title="title" :visible.sync="dialogVisible" width="35%">
      <!-- 用户表单信息 -->
      <el-form
        ref="model"
        :model="model"
        :inline="true"
        label-width="80px"
        :rules="rules"
      >
        <el-form-item label="用户名称" prop="userName">
          <el-input v-model="model.userName"></el-input>
        </el-form-item>
        <el-form-item label="用户昵称" prop="nickName">
          <el-input v-model="model.nickName"></el-input>
        </el-form-item>
        <el-form-item label="密码" prop="password">
          <el-input type="password" v-model="model.password"></el-input>
        </el-form-item>
        <el-form-item label="性别" prop="sex">
          <el-select v-model="model.sex" placeholder="性别">
            <el-option label="" value="1"></el-option>
            <el-option label="" value="0"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="生日" prop="birthday">
          <el-date-picker
            v-model="model.birthday"
            type="date"
            placeholder="选择日期"
          >
          </el-date-picker>
        </el-form-item>
      </el-form>


      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="submit">确 定</el-button>
      </span>
    </el-dialog>
    <div class="user-header">
      <el-button type="primary" @click="dialogVisible = true">+新增</el-button>
    </div>
  </div>
</template>
 <script>
export default {
  name: "user",
  data() {
    return {
      title: "新增",
      model: {
        userName: "",
        password: "",
        nickName: "",
        sex: "",
        birthday: "",
      },
      dialogVisible: false,
      rules: {
        userName: [
          { required: true, message: "请输入用户名称", trigger: "blur" },
          { min: 3, max: 5, message: "长度在 3 到 5 个字符", trigger: "blur" },
        ],
        password: [
          { required: true, message: "请输入密码", trigger: "change" },
        ],
        nickName: [
          { required: true, message: "请输入用户昵称", trigger: "blur" },
          { min: 3, max: 5, message: "长度在 3 到 5 个字符", trigger: "blur" },
        ],
        sex: [{ required: true, message: "请选择性别", trigger: "change" }],


        birthday: [
          {
            type: "date",
            required: true,
            message: "请选择日期",
            trigger: "change",
          },
        ],
      },
    };
  },
  methods: {
    submit() {
      this.$refs.model.validate((valid) => {
        if (valid) {
          //验证通过才进行数据请求
           this.model
        }  
      });
    },
  },
};
</script>
<style lang="less" scoped>
.el-el-input {
  width: 80%;
}
</style>

2、页面效果

image.png 点击新增 image.png

七、SpringBoot框架搭建

软件版本

  • jdk 1.8
  • mysql5.7+

1、如图新建springboot项目

image.pngimage.png

2、developer tools选择lombok,简化代码

3、web选择spring web

4、sql选择mybatis和mysql

image.png

5、点击finish

image.png

6、生成后的项目

image.png

7、pom.xml文件的版本号修改为2.5.9,mysql驱动换了,最好替换成下边的,版本一致

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.9</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.stu</groupId>
  <artifactId>MyServer</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>20230407MyServer</name>
  <description>Demo project for Spring Boot</description>
  <properties>
    <java.version>1.8</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>2.3.1</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <excludes>
            <exclude>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
            </exclude>
          </excludes>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <repositories>
    <repository>
      <id>nexus-aliyun</id>
      <name>nexus-aliyun</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>

  <pluginRepositories>
    <pluginRepository>
      <id>public</id>
      <name>aliyun nexus</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>false</enabled>
        </snapshots>
        </pluginRepository>
        </pluginRepositories>
        </project>

8、配置阿里云,让maven下载依赖更快一些,注意位置,这里是配置阿里云的代码,上边是完整的代码。

<pluginRepositories>
  <pluginRepository>
    <id>public</id>
    <name>aliyun nexus</name>
    <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
    <releases>
      <enabled>true</enabled>
    </releases>
    <snapshots>
      <enabled>false</enabled>
    </snapshots>
  </pluginRepository>
</pluginRepositories>

9、重新加载maven

image.png

10、删除多余的文件

image.png 删除后的目录 image.png

11、启动项目

image.png

12、报错如下,这个是因为没有连接数据库,在此之前需要在mysql里新建一个数据库(文末会给sql)

"C:\Program Files\Java\jdk1.8.0\bin\java.exe" -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2021.1.1\lib\idea_rt.jar=10755:D:\Program Files\JetBrains\IntelliJ IDEA 2021.1.1\bin" -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar;E:\20230406WorkSpace\20230407MyServer\target\classes;E:\ZXJYRepository\org\springframework\boot\spring-boot-starter-web\2.5.9\spring-boot-starter-web-2.5.9.jar;E:\ZXJYRepository\org\springframework\boot\spring-boot-starter\2.5.9\spring-boot-starter-2.5.9.jar;E:\ZXJYRepository\org\springframework\boot\spring-boot\2.5.9\spring-boot-2.5.9.jar;E:\ZXJYRepository\org\springframework\boot\spring-boot-autoconfigure\2.5.9\spring-boot-autoconfigure-2.5.9.jar;E:\ZXJYRepository\org\springframework\boot\spring-boot-starter-logging\2.5.9\spring-boot-starter-logging-2.5.9.jar;E:\ZXJYRepository\ch\qos\logback\logback-classic\1.2.10\logback-classic-1.2.10.jar;E:\ZXJYRepository\ch\qos\logback\logback-core\1.2.10\logback-core-1.2.10.jar;E:\ZXJYRepository\org\apache\logging\log4j\log4j-to-slf4j\2.17.1\log4j-to-slf4j-2.17.1.jar;E:\ZXJYRepository\org\apache\logging\log4j\log4j-api\2.17.1\log4j-api-2.17.1.jar;E:\ZXJYRepository\org\slf4j\jul-to-slf4j\1.7.33\jul-to-slf4j-1.7.33.jar;E:\ZXJYRepository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\ZXJYRepository\org\yaml\snakeyaml\1.28\snakeyaml-1.28.jar;E:\ZXJYRepository\org\springframework\boot\spring-boot-starter-json\2.5.9\spring-boot-starter-json-2.5.9.jar;E:\ZXJYRepository\com\fasterxml\jackson\core\jackson-databind\2.12.6\jackson-databind-2.12.6.jar;E:\ZXJYRepository\com\fasterxml\jackson\core\jackson-annotations\2.12.6\jackson-annotations-2.12.6.jar;E:\ZXJYRepository\com\fasterxml\jackson\core\jackson-core\2.12.6\jackson-core-2.12.6.jar;E:\ZXJYRepository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.12.6\jackson-datatype-jdk8-2.12.6.jar;E:\ZXJYRepository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.12.6\jackson-datatype-jsr310-2.12.6.jar;E:\ZXJYRepository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.12.6\jackson-module-parameter-names-2.12.6.jar;E:\ZXJYRepository\org\springframework\boot\spring-boot-starter-tomcat\2.5.9\spring-boot-starter-tomcat-2.5.9.jar;E:\ZXJYRepository\org\apache\tomcat\embed\tomcat-embed-core\9.0.56\tomcat-embed-core-9.0.56.jar;E:\ZXJYRepository\org\apache\tomcat\embed\tomcat-embed-el\9.0.56\tomcat-embed-el-9.0.56.jar;E:\ZXJYRepository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.56\tomcat-embed-websocket-9.0.56.jar;E:\ZXJYRepository\org\springframework\spring-web\5.3.15\spring-web-5.3.15.jar;E:\ZXJYRepository\org\springframework\spring-beans\5.3.15\spring-beans-5.3.15.jar;E:\ZXJYRepository\org\springframework\spring-webmvc\5.3.15\spring-webmvc-5.3.15.jar;E:\ZXJYRepository\org\springframework\spring-aop\5.3.15\spring-aop-5.3.15.jar;E:\ZXJYRepository\org\springframework\spring-context\5.3.15\spring-context-5.3.15.jar;E:\ZXJYRepository\org\springframework\spring-expression\5.3.15\spring-expression-5.3.15.jar;E:\ZXJYRepository\org\mybatis\spring\boot\mybatis-spring-boot-starter\2.3.1\mybatis-spring-boot-starter-2.3.1.jar;E:\ZXJYRepository\org\springframework\boot\spring-boot-starter-jdbc\2.5.9\spring-boot-starter-jdbc-2.5.9.jar;E:\ZXJYRepository\com\zaxxer\HikariCP\4.0.3\HikariCP-4.0.3.jar;E:\ZXJYRepository\org\springframework\spring-jdbc\5.3.15\spring-jdbc-5.3.15.jar;E:\ZXJYRepository\org\springframework\spring-tx\5.3.15\spring-tx-5.3.15.jar;E:\ZXJYRepository\org\mybatis\spring\boot\mybatis-spring-boot-autoconfigure\2.3.1\mybatis-spring-boot-autoconfigure-2.3.1.jar;E:\ZXJYRepository\org\mybatis\mybatis\3.5.13\mybatis-3.5.13.jar;E:\ZXJYRepository\org\mybatis\mybatis-spring\2.1.1\mybatis-spring-2.1.1.jar;E:\ZXJYRepository\mysql\mysql-connector-java\8.0.28\mysql-connector-java-8.0.28.jar;E:\ZXJYRepository\org\projectlombok\lombok\1.18.22\lombok-1.18.22.jar;E:\ZXJYRepository\org\slf4j\slf4j-api\1.7.33\slf4j-api-1.7.33.jar;E:\ZXJYRepository\org\springframework\spring-core\5.3.15\spring-core-5.3.15.jar;E:\ZXJYRepository\org\springframework\spring-jcl\5.3.15\spring-jcl-5.3.15.jar" com.stu.myserver.Application

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.9)

2023-06-13 20:30:16.228  INFO 2524 --- [           main] com.stu.myserver.Application             : Starting Application using Java 1.8.0 on konglx with PID 2524 (E:\20230406WorkSpace\20230407MyServer\target\classes started by 小明的学习圈子 in E:\20230406WorkSpace\20230407MyServer)
2023-06-13 20:30:16.228  INFO 2524 --- [           main] com.stu.myserver.Application             : No active profile set, falling back to default profiles: default
2023-06-13 20:30:18.603  WARN 2524 --- [           main] o.m.s.mapper.ClassPathMapperScanner      : No MyBatis mapper was found in '[com.stu.myserver]' package. Please check your configuration.
2023-06-13 20:30:20.071  INFO 2524 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-06-13 20:30:20.149  INFO 2524 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-06-13 20:30:20.149  INFO 2524 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.56]
2023-06-13 20:30:20.368  INFO 2524 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-06-13 20:30:20.368  INFO 2524 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 3983 ms
2023-06-13 20:30:21.071  WARN 2524 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method 'dataSource' threw exception; nested exception is org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Failed to determine a suitable driver class
2023-06-13 20:30:21.071  INFO 2524 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2023-06-13 20:30:21.149  INFO 2524 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2023-06-13 20:30:21.274 ERROR 2524 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class


Action:

Consider the following:
	If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
	If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).


Process finished with exit code 1

13、解决办法(2种解放方式)

1、可以在启动类添加配置@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)先排除数据库

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
public class ApiGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }
}

2、配置yml或者properties文件

properties

server.port=9090

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/2023Java?serverTimezone=GMT%2b8
spring.datasource.username=root
spring.datasource.password=study

yml

server:
  port: 9090
spring:
  profiles:
    ## 环境设置
    active: dev
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/2023Java?serverTimezone=Asia/Shanghai
    username: root
    password: study

  ## 响应 json 的全局时间格式
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
##mybatis 日志设置
mybatis-plus:
  configuration:
    ## 日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

serverTimezone这个时区要设置好,不然会出现时差, 如果你设置serverTimezone=UTC,连接不报错, 但是我们在用java代码插入到数据库时间的时候却出现了问题。 比如在java代码里面插入的时间为:2021-06-24 17:29:56 但是在数据库里面显示的时间却为:2021-06-24 09:29:56 有了8个小时的时差 UTC代表的是全球标准时间 ,但是我们使用的时间是北京时区也就是东八区,领先UTC八个小时。 //北京时间==东八区时间!=北京当地时间 serverTimezone=GMT%2B8 //或者使用上海时间 serverTimezone=Asia/Shanghai

14、再次启动,启动正常

"C:\Program Files\Java\jdk1.8.0\bin\java.exe" -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2021.1.1\lib\idea_rt.jar=10807:D:\Program Files\JetBrains\IntelliJ IDEA 2021.1.1\bin" -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar;E:\20230406WorkSpace\20230407MyServer\target\classes;E:\ZXJYRepository\org\springframework\boot\spring-boot-starter-web\2.5.9\spring-boot-starter-web-2.5.9.jar;E:\ZXJYRepository\org\springframework\boot\spring-boot-starter\2.5.9\spring-boot-starter-2.5.9.jar;E:\ZXJYRepository\org\springframework\boot\spring-boot\2.5.9\spring-boot-2.5.9.jar;E:\ZXJYRepository\org\springframework\boot\spring-boot-autoconfigure\2.5.9\spring-boot-autoconfigure-2.5.9.jar;E:\ZXJYRepository\org\springframework\boot\spring-boot-starter-logging\2.5.9\spring-boot-starter-logging-2.5.9.jar;E:\ZXJYRepository\ch\qos\logback\logback-classic\1.2.10\logback-classic-1.2.10.jar;E:\ZXJYRepository\ch\qos\logback\logback-core\1.2.10\logback-core-1.2.10.jar;E:\ZXJYRepository\org\apache\logging\log4j\log4j-to-slf4j\2.17.1\log4j-to-slf4j-2.17.1.jar;E:\ZXJYRepository\org\apache\logging\log4j\log4j-api\2.17.1\log4j-api-2.17.1.jar;E:\ZXJYRepository\org\slf4j\jul-to-slf4j\1.7.33\jul-to-slf4j-1.7.33.jar;E:\ZXJYRepository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\ZXJYRepository\org\yaml\snakeyaml\1.28\snakeyaml-1.28.jar;E:\ZXJYRepository\org\springframework\boot\spring-boot-starter-json\2.5.9\spring-boot-starter-json-2.5.9.jar;E:\ZXJYRepository\com\fasterxml\jackson\core\jackson-databind\2.12.6\jackson-databind-2.12.6.jar;E:\ZXJYRepository\com\fasterxml\jackson\core\jackson-annotations\2.12.6\jackson-annotations-2.12.6.jar;E:\ZXJYRepository\com\fasterxml\jackson\core\jackson-core\2.12.6\jackson-core-2.12.6.jar;E:\ZXJYRepository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.12.6\jackson-datatype-jdk8-2.12.6.jar;E:\ZXJYRepository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.12.6\jackson-datatype-jsr310-2.12.6.jar;E:\ZXJYRepository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.12.6\jackson-module-parameter-names-2.12.6.jar;E:\ZXJYRepository\org\springframework\boot\spring-boot-starter-tomcat\2.5.9\spring-boot-starter-tomcat-2.5.9.jar;E:\ZXJYRepository\org\apache\tomcat\embed\tomcat-embed-core\9.0.56\tomcat-embed-core-9.0.56.jar;E:\ZXJYRepository\org\apache\tomcat\embed\tomcat-embed-el\9.0.56\tomcat-embed-el-9.0.56.jar;E:\ZXJYRepository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.56\tomcat-embed-websocket-9.0.56.jar;E:\ZXJYRepository\org\springframework\spring-web\5.3.15\spring-web-5.3.15.jar;E:\ZXJYRepository\org\springframework\spring-beans\5.3.15\spring-beans-5.3.15.jar;E:\ZXJYRepository\org\springframework\spring-webmvc\5.3.15\spring-webmvc-5.3.15.jar;E:\ZXJYRepository\org\springframework\spring-aop\5.3.15\spring-aop-5.3.15.jar;E:\ZXJYRepository\org\springframework\spring-context\5.3.15\spring-context-5.3.15.jar;E:\ZXJYRepository\org\springframework\spring-expression\5.3.15\spring-expression-5.3.15.jar;E:\ZXJYRepository\org\mybatis\spring\boot\mybatis-spring-boot-starter\2.3.1\mybatis-spring-boot-starter-2.3.1.jar;E:\ZXJYRepository\org\springframework\boot\spring-boot-starter-jdbc\2.5.9\spring-boot-starter-jdbc-2.5.9.jar;E:\ZXJYRepository\com\zaxxer\HikariCP\4.0.3\HikariCP-4.0.3.jar;E:\ZXJYRepository\org\springframework\spring-jdbc\5.3.15\spring-jdbc-5.3.15.jar;E:\ZXJYRepository\org\springframework\spring-tx\5.3.15\spring-tx-5.3.15.jar;E:\ZXJYRepository\org\mybatis\spring\boot\mybatis-spring-boot-autoconfigure\2.3.1\mybatis-spring-boot-autoconfigure-2.3.1.jar;E:\ZXJYRepository\org\mybatis\mybatis\3.5.13\mybatis-3.5.13.jar;E:\ZXJYRepository\org\mybatis\mybatis-spring\2.1.1\mybatis-spring-2.1.1.jar;E:\ZXJYRepository\mysql\mysql-connector-java\8.0.28\mysql-connector-java-8.0.28.jar;E:\ZXJYRepository\org\projectlombok\lombok\1.18.22\lombok-1.18.22.jar;E:\ZXJYRepository\org\slf4j\slf4j-api\1.7.33\slf4j-api-1.7.33.jar;E:\ZXJYRepository\org\springframework\spring-core\5.3.15\spring-core-5.3.15.jar;E:\ZXJYRepository\org\springframework\spring-jcl\5.3.15\spring-jcl-5.3.15.jar" com.stu.myserver.Application

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.9)

2023-06-13 20:33:11.392  INFO 14472 --- [           main] com.stu.myserver.Application             : Starting Application using Java 1.8.0 on konglx with PID 14472 (E:\20230406WorkSpace\20230407MyServer\target\classes started by 小明的学习圈子 in E:\20230406WorkSpace\20230407MyServer)
2023-06-13 20:33:11.392  INFO 14472 --- [           main] com.stu.myserver.Application             : The following profiles are active: dev
2023-06-13 20:33:11.814  WARN 14472 --- [           main] o.m.s.mapper.ClassPathMapperScanner      : No MyBatis mapper was found in '[com.stu.myserver]' package. Please check your configuration.
2023-06-13 20:33:12.079  INFO 14472 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 9090 (http)
2023-06-13 20:33:12.095  INFO 14472 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-06-13 20:33:12.095  INFO 14472 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.56]
2023-06-13 20:33:12.142  INFO 14472 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-06-13 20:33:12.142  INFO 14472 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 719 ms
2023-06-13 20:33:12.516  INFO 14472 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 9090 (http) with context path ''
2023-06-13 20:33:12.516  INFO 14472 --- [           main] com.stu.myserver.Application             : Started Application in 1.441 seconds (JVM running for 1.766)

15、代码生成器(本文有两种方案,后边还有一种更详细的,但是pom的依赖只能使用一个,使用后边的需要吧这个pom依赖注释掉)

1、pom.xml加入以下依赖

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-generator</artifactId>
  <version>3.4.0</version>
</dependency>

<!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
<dependency>
  <groupId>org.apache.velocity</groupId>
  <artifactId>velocity-engine-core</artifactId>
  <version>2.0</version>
</dependency>

2、新建CodeGenerator类 image.png 3、代码

package generator;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;

public class CodeGenerator {

    @Test
    public void genCode() {
        // 前缀
        String prefix = "2023";
        String moduleName = "java";

        // 1、创建代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 2、全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("公众号:小明的学习圈子");
        gc.setOpen(false); // 生成后是否打开资源管理器
        gc.setFileOverride(false); // 重新成时文件是否覆盖
        gc.setServiceName("%sService");    // 去掉Service接口的首字母I
        gc.setIdType(IdType.ASSIGN_ID); // 主键策略
        gc.setDateType(DateType.ONLY_DATE);// 定义生成的实体类中日期类型
        gc.setSwagger2(true);// 开启 Swagger2 模式
        mpg.setGlobalConfig(gc);

        // 3、数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/" + prefix + moduleName + "?serverTimezone=UTC");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("study");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);

        // 4、包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.stu");
        pc.setModuleName("myserver"); //模块名
        pc.setController("controller");
        pc.setEntity("entity");
        pc.setService("service");
        pc.setMapper("mapper");
        mpg.setPackageInfo(pc);

        // 5、策略配置
        StrategyConfig strategy = new StrategyConfig();
        // 生成哪些表
        strategy.setInclude("acl_user" );
        // 数据库表映射到实体的命名策略
        strategy.setNaming(NamingStrategy.underline_to_camel);
        //如果是同时生成多个表用逗号隔开          //strategy.setInclude("acl_permission","acl_role","acl_role_permission","acl_user","acl_user_role");
        // 设置表前缀不生成
        strategy.setTablePrefix(moduleName + "_");
        // 数据库表字段映射到实体的命名策略
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        // lombok 模型 @Accessors(chain = true) setter链式操作
        strategy.setEntityLombokModel(true);
        // 逻辑删除字段名
        strategy.setLogicDeleteFieldName("is_deleted");
        // 去掉布尔值的is_前缀
        strategy.setEntityBooleanColumnRemoveIsPrefix(true);

        // 自动填充
        TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
        TableFill gmtModified = new TableFill("gmt_modified", FieldFill.INSERT_UPDATE);
        ArrayList<TableFill> tableFills = new ArrayList<>();
        tableFills.add(gmtCreate);
        tableFills.add(gmtModified);
        strategy.setTableFillList(tableFills);

        // restful api风格控制器
        strategy.setRestControllerStyle(true);
        // url 中驼峰转连字符
        strategy.setControllerMappingHyphenStyle(true);
        mpg.setStrategy(strategy);

        // 6、执行
        mpg.execute();
    }

    }

4、执行genCode方法,生成的代码如下 image.png 5、数据库截图 image-20230919205557452 6、Application

package com.stu.myserver;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
// @ComponentScan(basePackages = "com.stu")
// @MapperScan("com.stu.*.mapper")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

7、yml文件添加mybatisplus的配置

server:
  port: 9090
spring:
  profiles:
    ## 环境设置
    active: dev
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/2023Java?serverTimezone=Asia/Shanghai
    username: root
    password: study

  ## 响应 json 的全局时间格式
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
##mybatis 日志设置
mybatis-plus:
  configuration:
    ## 日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:/mapper/*.xml
  global-config:
    db-config:
      logic-delete-field: isDeleted ## 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 ## 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 ## 逻辑未删除值(默认为 0)

8、userController添加测试接口index

package com.stu.myserver.controller;


import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author 公众号:小明的学习圈子
 * @since 2023-06-14
 */
@RestController
@RequestMapping("/myserver/user")
public class UserController {

    @RequestMapping
    public String index(){
        return "index";
    }
}

9、游览器访问 image.png 10、连接数据库测试

package com.stu.myserver.controller;


import com.stu.myserver.entity.User;
import com.stu.myserver.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author 公众号:小明的学习圈子
 * @since 2023-06-14
 */
@RestController
@RequestMapping("/myserver/user")
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping
    public String index(){
        return "index";
    }

    @GetMapping("getUserList")
    public List<User> getUserList(){
        List<User> list = userService.list();
        return list;
    }
}

11、启动项目报错 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.stu.myserver.mapper.UserMapper.selectList 解决参照:https://www.cnblogs.com/konglxblog/p/14864807.htmlopen in new window 12、pom.xml文件添加mybatis-plus等配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.9</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.stu</groupId>
  <artifactId>MyServer</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>20230407MyServer</name>
  <description>Demo project for Spring Boot</description>
  <properties>
    <java.version>1.8</java.version>
    <mybatis-plus.version>3.4.0</mybatis-plus.version>
    <velocity.version>2.0</velocity.version>
    <swagger.version>2.9.2</swagger.version>
    <mybatis-spring-boot-starter.version>2.3.1</mybatis-spring-boot-starter.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>${mybatis-spring-boot-starter.version}</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <!-- mybatis-plus -->
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.5.1</version>
    </dependency>
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-generator</artifactId>
      <version>${mybatis-plus.version}</version>
    </dependency>

    <!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
    <dependency>
      <groupId>org.apache.velocity</groupId>
      <artifactId>velocity-engine-core</artifactId>
      <version>${velocity.version}</version>
    </dependency>

    <!--swagger-->
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger2</artifactId>
      <version>${swagger.version}</version>
    </dependency>

    <!--swagger ui-->
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger-ui</artifactId>
      <version>${swagger.version}</version>
    </dependency>
    </dependencies>

      <build>
      <plugins>
      <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <configuration>
      <excludes>
      <exclude>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </exclude>
    </excludes>
    </configuration>
    </plugin>
    </plugins>
      <resources>
      <resource>
      <directory>src/main/java</directory>
      <includes>
      <include>**/*.xml</include>
    </includes>
      <filtering>false</filtering>
    </resource>
    </resources>
    </build>
      <repositories>
      <repository>
      <id>nexus-aliyun</id>
      <name>nexus-aliyun</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
      <releases>
      <enabled>true</enabled>
    </releases>
      <snapshots>
      <enabled>false</enabled>
    </snapshots>
    </repository>
    </repositories>

      <pluginRepositories>
      <pluginRepository>
      <id>public</id>
      <name>aliyun nexus</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
      <releases>
      <enabled>true</enabled>
    </releases>
      <snapshots>
      <enabled>false</enabled>
    </snapshots>
    </pluginRepository>