vue通用后台管理系统(保姆级)--持续更新中

配合目录使用更加友好哦,文章中分享的项目搭建是完全从0-1搭建,完全适用于小白,可用于vue练手项目,目前还在持续更新中,本篇文章不会断更,因工作原因,只能晚上给大家更新,感觉还行的可以给个关注或者收藏本篇文章进行学习!!

文章目录


一、vue通用后台管理系统分析

  1. 项目搭建+使用element-ui实现首页布局
  2. 顶部导航菜单及与左侧导航联动的面包屑实现
  3. 封装一个ECharts组件
  4. 封装一个Form表单组件和Table表格组件
  5. 企业开发之权限管理思路讲解

技术栈

  • vue-router
  • vuex
  • axios
  • element-ui
  • 二次封装axios
  • mock
  • echarts

项目搭建

  • 项目架构分析
  • 项目模块搭建
  • 脚手架搭建配置
  • 组件初始化
  • 路由初始化
  • vuex初始化

模块分配

  • 登录页
  • 后台首页
  • 用户管理页
  • 分页处理
  • 用户crad
  • 路由守卫
  • 权限管理

二、脚手架搭建项目

可参考这篇文章=> Vue脚手架(cli和vite详解)
vue-cli官网
在这里插入图片描述

1、项目环境搭建

1.1nodejs环境(有的话可跳过)

环境需要 要先使用npm进行管理,而使用npm需要先下载nodejs。
Nodejs下载地址中文官网

1.1.1 下载

根据自己电脑系统及位数选择,我的电脑是Windows系统、64位、想下载稳定版的.msi(LTS为长期稳定版)这里选择windows64位.msi格式安装包。
在这里插入图片描述

1.1.2下载你完成后傻瓜式安装(具体可以网上找教程哦)

在这里插入图片描述

1.1.3安装完成后可进入终端查看一下node是否安装成功
node -v # 查看是否安装成功 node.js
npm -v

在前面设置好nodejs相关的配置之后,我们可以直接使用npm工具拉取vue-cli脚手架## 1、cli脚手架创建项目步骤

2.1安装cli
npm install -g  @vue/cli  #安装

npm uninstall -g vue-cli # 卸载

在这里插入图片描述

2.1.1使用vue - v查看vue的版本,检验是否安装成功。
vue -v  或者 vue -version #查看cli脚手架版本

在这里插入图片描述

2.1.3使用cli创建项目
vue create 项目名  #创建项目

在这里插入图片描述
第三个Manually select features是自定义选择
我们这个项目就不选择自定义了,选择 第二个Default ([Vue 2] babel, eslint)创建vue2项目即可

2.1.4选择好之后开始创建

在这里插入图片描述

2.1.5创建完成后切换到项目文件夹启动项目

在这里插入图片描述
在这里插入图片描述

2.1.6cli脚手架项目创建完成

在这里插入图片描述

三、在vue中使用element-ui

element-ui网址
在这里插入图片描述

element-ui结合脚手架使用之全部引用

在这里插入图片描述

1.安装element-ui

1.1 npm 安装

安装前,记得cd进入到我们创建的项目文件夹中

npm i element-ui -S

在这里插入图片描述

2.1package.json介绍

在这里插入图片描述
element-ui安装之后,会下载到node_modules文件夹,并且开发依赖症会多一个element-ui

2.使用element-ui

在这里插入图片描述

2.1完整引入 Element-ui

在 main.js 中写入以下内容

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

在App.vue使用一下element的组件,我这里使用的是element-ui中的按钮组件

<template>
  <div id="app">
    <!-- 引入element当中的按钮组件 -->
    <el-button type="primary">主要按钮</el-button>
  </div>
</template>

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

}
</script>

<style>

</style>

效果
在这里插入图片描述
由上图可可见,引入并且使用成功

2.2按需引入 Element-ui

借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。
安装 babel-plugin-component:

npm install babel-plugin-component -D

在这里插入图片描述
然后,将babelrc 修改为:
在这里插入图片描述
在我们cli脚手架有一个babel.config.js文件,这个文件其实和babelrc 是一样的作用,所以我们在babel.config.js进行以下配置。

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset',
    ["@babel/preset-env", { "modules": false }]
  ],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

接下来,如果只引入部分组件,比如 Button 和 Select,那么需要在 main.js 中写入以下内容:

import Vue from 'vue'
import App from './App.vue'
import 'element-ui/lib/theme-chalk/index.css';
// 引入element-ui样式文件
Vue.config.productionTip = false
import { Button } from 'element-ui';
Vue.use(Button)
// 按需引入
new Vue({
  el: '#app',
  render: h => h(App),
}).$mount('#app')

在App.vue使用一下element的组件,我这里使用的是element-ui中的按钮组件

<template>
  <div id="app">
    <!-- 引入element当中的按钮组件 -->
    <el-button type="primary">主要按钮</el-button>
  </div>
</template>

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

}
</script>

<style>

</style>

效果
在这里插入图片描述
完整引入和按需引入就是打包有一点区别,节省打包后dist文件夹体积
打包命令

npm run build

新手建议使用完整使用

扩展(element-ui文档)

在每个组件底部都有一个当前组件的属性和方法的介绍,我们也一定要学会查阅文档,在工作当中,也能提高我们的工作效率!
接下来就拿el-button组件做个介绍
根据需求,在我们组件添加相关的属性和方法即可
在这里插入图片描述

四、在vue中引入vue-router

没有基础的朋友可以看一下下面这篇文章对vue的讲解,路由引入配置等等,做个简单了解!也可以查看官方网站进行学习,链接在下方,可以看一看!
Vue从入门到精通(第三方插件使用+Axios封装+Vuex状态管理+Vue3新特性)
vue Router3.x主要配合vue2使用,我们这个项目看下面3.x的就可以
vue Router3.x官网
vue Router4.x官网主要配合vue3使用
vue Router4.x官网
在这里插入图片描述

1.npm安装vue-router

由于是vue2项目,我们需要安装vue Router3.x版本的
另外,如果想安装3.x版本最新,这里给大家推荐一个网站npm,在搜索栏搜索vue-router即可查看,以后的一些插件依赖也可以在这里搜查看最新版本
npm
在这里插入图片描述
在这里插入图片描述
通过查看最新的是3.6.5,我们在安装是后面@版本号即可

npm install vue-router@3.6.5

2.vue-router路由配置(上)

2.1vue-router路由配置代码

我们在src项目目录下新建一个router文件夹,然后新建一个index.js作为我们项目的路由配置文件
在这里插入图片描述

2.1.1.创建路由页面组件

我们在src项目目录下新建一个views文件夹,然后新建我们的路由页面组件
在这里插入图片描述
Home.vue页面组件

<template>
  <div>
    <h1>Home页面</h1>
  </div>
</template>
<script>
export default{
  data() {
    return{
      
    }
  }
}
</script>

User.vue页面组件

<template>
  <div>
    <h1>User页面</h1>
  </div>
</template>
<script>
export default{
  data() {
    return{
      
    }
  }
}
</script>
2.1.2 配置路由文件

写入以下代码:
页面组件在2.1.1创建

// 路由配置文件

// 如果在一个模块化工程中使用它,必须要通过 Vue.use() 明确地安装路由功能,也可以再main.js引入
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
// 1.创建路由组件,也就是我们创建views里面的组件文件
// 引入我们创建的vue组件文件
import Home from '../views/Home.vue'
import User from '../views/User.vue'
// 2. 定义路由
// 将路由与组件进行映射
const routes = [
  { path: '/Home', component: Home },
  { path: '/User', component: User }
]
// 3. 创建 router 实例,然后传routes配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
  routes // (缩写) 相当于 routes: routes
})

// 导出我们的router实例
export default router

在main.js引入路由配置文件,然后挂载router

import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
// 引入element-ui样式文件
import router from './router'
// 引入路由文件
Vue.config.productionTip = false
Vue.use(ElementUI);
// 全局注入element-ui

new Vue({
  router,
  // 挂载router实例
  el: '#app',
  render: h => h(App),
}).$mount('#app')

2.1.3路由出口

路由出口=>路由匹配到的组件将渲染在这里
APP.vue

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

<script>
export default {
  name: 'App',
  data() {
    return{
      
    }
  }
}
</script>

<style>

</style>

扩展 eslint

在这里插入图片描述
eslint是一套规范,如果我们按照它所定义的规范来进行代码的编写,但是有时候会对我们的开发有一定影响的,那么下面解决一下这个问题。
在项目文件vue.config.js中加入一行 lintOnSave:false 关闭eslint校验

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave:false//关闭eslint校验
})

在这里插入图片描述
然后重新启动项目
在这里插入图片描述
这样就可以启动成功了!!!接下来我们就去看看路由的展示,根据我们配置路径然后匹配映射的组件文件中的页面
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.vue-router路由配置(下)

3.1vue-router中的嵌套路由

在这里插入图片描述
嵌套路由官方实例
/user/子路由进行匹配,实现动态路由
要在嵌套的出口中渲染组件,需要在 VueRouter 的参数中使用 children 配置:
在这里插入图片描述

3.1.1 项目子路由配置
主页面组件

Main.vue

<template>
  <div>
    <div> Main下面子路由的路由出口</div>
    <!-- router-view Main下面子路由的路由出口 -->
    <router-view>

    </router-view>
  </div>
</template>
<script>
export default{
  data(){
        return{
      
    }
  }
}
</script>

路由文件更改

// 路由配置文件

// 如果在一个模块化工程中使用它,必须要通过 Vue.use() 明确地安装路由功能,也可以再main.js引入
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
import Main from '../views/Mian.vue'
// 主页面
// 1.创建路由组件,也就是我们创建views里面的组件文件
// 引入我们创建的vue组件文件
import Home from '../views/Home.vue'
import User from '../views/User.vue'
// 2. 定义路由
// 将路由与组件进行映射
const routes = [
  // 主页面
  {
    path:'/',
    //  /默认是主出口主页面组件
    component:Main,
    children:[
      // 子路由
      { path: 'Home', component: Home },
      { path: 'User', component: User }
    ]
  },

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

// 导出我们的router实例
export default router

效果
主页面
在这里插入图片描述
Home子路由匹配
在这里插入图片描述

User子路由匹配
在这里插入图片描述

五、vue通用管理后台(整体UI搭建)

在前面我们已经对element-ui和路由的使用有了一定的了解,那么接下来我们就要正式开始我们通用管理后台的布局和样式的搭建了!

1、通用管理后台(布局)

布局的话我们使用element-ui的Container 布局容器组件
在这里插入图片描述
Container 布局容器
用于布局的容器组件,方便快速搭建页面的基本结构:
:外层容器。当子元素中包含 或 时,全部子元素会垂直上下排列,否则会水平左右排列。
:顶栏容器。
:侧边栏容器。
:主要区域容器。
:底栏容器。
我们要用的布局效果图,
在这里插入图片描述
接下来就可以修改我们的主入口代码,把布局组件引入进去

1.1、使用 Container 布局容器进行布局

相关代码如下
main.vue

<template>
  <div>
    <el-container>
      <el-aside width="200px">Aside</el-aside>
      <el-container>
        <el-header>Header</el-header>
        <el-main>
          main        
          <!-- router-view Main下面子路由的路由出口 -->
          <router-view>
          </router-view>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>
<script>
export default {
  data() {

  }
}
</script>

效果
在这里插入图片描述

2、通用管理后台(完善左侧菜单栏)

布局的话我们使用element-ui的NavMenu导航菜单布局容器进行对el-aside菜单区域的内容修改
在这里插入图片描述
在里面找到根据我们项目的需求差不多的菜单栏
在这里插入图片描述
找到后cv到我们的项目中来

2.1、使用 NavMenu 导航菜单布局容器进行完善左侧菜单栏

因为菜单栏功能属于比较单一的,我们在工作中,对于单一的功能应该拆开成一个单一的组件,然后进行封装,这样便于后期的维护!
我们在components下新建一个CommonAside.vue用来放我们的菜单组件
在这里插入图片描述
相关代码
CommonAside.vue
封装菜单

<template>
<el-menu default-active="1-4-1" class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose" :collapse="isCollapse">
  <el-submenu index="1">
    <template slot="title">
      <i class="el-icon-location"></i>
      <span slot="title">导航一</span>
    </template>
    <el-menu-item-group>
      <span slot="title">分组一</span>
      <el-menu-item index="1-1">选项1</el-menu-item>
      <el-menu-item index="1-2">选项2</el-menu-item>
    </el-menu-item-group>
    <el-menu-item-group title="分组2">
      <el-menu-item index="1-3">选项3</el-menu-item>
    </el-menu-item-group>
    <el-submenu index="1-4">
      <span slot="title">选项4</span>
      <el-menu-item index="1-4-1">选项1</el-menu-item>
    </el-submenu>
  </el-submenu>
  <el-menu-item index="2">
    <i class="el-icon-menu"></i>
    <span slot="title">导航二</span>
  </el-menu-item>
  <el-menu-item index="3" disabled>
    <i class="el-icon-document"></i>
    <span slot="title">导航三</span>
  </el-menu-item>
  <el-menu-item index="4">
    <i class="el-icon-setting"></i>
    <span slot="title">导航四</span>
  </el-menu-item>
</el-menu>
</template>


<style>
  .el-menu-vertical-demo:not(.el-menu--collapse) {
    width: 200px;
    min-height: 400px;
  }
</style>

<script>
  export default {
    data() {
      return {
        isCollapse: true
      };
    },
    methods: {
      handleOpen(key, keyPath) {
        console.log(key, keyPath);
      },
      handleClose(key, keyPath) {
        console.log(key, keyPath);
      }
    }
  }
</script>

Main.vue
引入封装好的菜单组件并且使用

<template>
  <div>
    <el-container>
      <el-aside width="200px">
        <!-- 把引入的CommonAside菜单组件进行使用 -->
        <CommonAside/>
      </el-aside>
      <el-container>
        <el-header>Header</el-header>
        <el-main>
          main
          <!-- router-view Main下面子路由的路由出口 -->
          <router-view>
          </router-view>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>
<script>
import CommonAside from '../components/CommonAside.vue'
// 导入拆分的菜单组件
export default {
  // 在components中引入组件,引入好,就可以在template中直接使用了
  components: {
    CommonAside
  },
  data() {
  }
}
</script>

效果

在这里插入图片描述

3、通用管理后台(完善菜单)

我们菜单已经引入成功,接下来我们进一步的完善菜单当前我们的菜单默认是收起的状态,我们要修改为展开的,我们项目菜单一种是有子菜单(二级菜单),一种是没有子菜单(一级菜单),另外,还需要做的就是,当我们选中当前菜单时,当前菜单高亮显示

3.1 、修改菜单默认收起选项

改变el-menu组件的collapse,把值给false即可
在这里插入图片描述
相关代码
在封装CommonAside.vue组件找到collapse属性

<el-menu default-active="1-4-1" class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose" :collapse="isCollapse">

在CommonAside.vue组件data找到这个属性值设置为false

    data() {
      return {
        isCollapse: false  //false展开,true收起
      };
    },

在这里插入图片描述

3.2、修改菜单的结构

根据需求,把不需要的菜单进行删除,整体结构修改为我们想要的效果
相关代码
CommonAside.vue

<template>
<el-menu default-active="1-4-1" class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose" :collapse="isCollapse">
  <el-menu-item index="2">
    <i class="el-icon-menu"></i>
    <span slot="title">导航一</span>
  </el-menu-item>
  <el-submenu index="1">
    <template slot="title">
      <i class="el-icon-location"></i>
      <span slot="title">导航二</span>
    </template>
    <el-menu-item-group>
      <el-menu-item index="1-1">选项1</el-menu-item>
    </el-menu-item-group>
  </el-submenu>
</el-menu>
</template>
<style>
  .el-menu-vertical-demo:not(.el-menu--collapse) {
    width: 200px;
    min-height: 400px;
  }
</style>

<script>
  export default {
    data() {
      return {
        isCollapse: false  //false展开,true收起
      };
    },
    methods: {
      handleOpen(key, keyPath) {
        console.log(key, keyPath);
      },
      handleClose(key, keyPath) {
        console.log(key, keyPath);
      }
    }
  }
</script>

效果
修改后的菜单
在这里插入图片描述

4、通用管理后台(根据菜单数据匹配相关页面)

根据我们点击的菜单,el-main展示相关的页面

4.1、menu菜单数据定义过滤

menu菜单数据
正常开发中,这些数据可能配合后端接口返回给我们,然后可以做权限判断,目前的话咱们就先写死!menuData就是我们的菜单数据

 data() {
      return {
        isCollapse: false,  //false展开,true收起
        // menuData 菜单数据
        menuData:[
        {
          path: '/',
          name: 'home',
          label: '首页',
          icon: 's-home',
          url: 'Home/Home'
        },
        {
          path: '/mall',
          name: 'mall',
          label: '商品管理',
          icon: 'video-play',
          url: 'MallManage/MallManage'
        },
        {
          path: '/user',
          name: 'user',
          label: '用户管理',
          icon: 'user',
          url: 'UserManage/UserManage'
        },
        {
          label: '其他',
          icon: 'location',
          children: [
            {
              path: '/page1',
              name: 'page1',
              label: '页面1',
              icon: 'setting',
              url: 'Other/PageOne'
            },
            {
              path: '/page2',
              name: 'page2',
              label: '页面2',
              icon: 'setting',
              url: 'Other/PageTwo'
            }
          ]
        }
      ]
      };
    },

上列数据中有children的代表有二级菜单,没有的则只有一级菜单,所以我们要判断菜单数据有没有子菜单,可以根据children进行判断
使用计算属性过滤

    computed:{
      // computed计算属性
      // 对menuData菜单数据进行过滤,分组
      // 1.没有子菜单、
      menuNoChildren(){
        return this.menuData.filter(item=>!item.children)
      },
      // 2.有子菜单
      menuHasChildren(){
        return this.menuData.filter(item=>item.children)
      }
    }

我们在渲染数据之前,应该对数据进行一个分组(有子菜单menuHasChildren和无子菜单menuNoChildren),分组完成之后使用vue中的v-for指令遍历生成菜单

4.2、一级动态菜单生成

遍历我们过滤好的menuNoChildren无菜单数据,生成一级菜单
相关代码
CommonAside.vue

<template>
<el-menu default-active="1-4-1" class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose" :collapse="isCollapse">
  <el-menu-item v-for="item in menuNoChildren"  :key="item.name" :index="item.name">
    <!-- 遍历生成无子菜单数据 -->
    <i :class="`el-icon-${item.icon}`"></i>
    <!-- i标签是渲染的element-ui的图标,使用了es6中的自符模板 -->
    <span slot="title">{{ item.label }}</span>
    <!-- span 标签是菜单名字 -->
  </el-menu-item>

  <el-submenu index="1">
    <template slot="title">
      <i class="el-icon-location"></i>
      <span slot="title">导航二</span>
    </template>
    <el-menu-item-group>
       <!-- 下方是二级菜单 -->
      <el-menu-item index="1-1">选项1</el-menu-item>
    </el-menu-item-group>
  </el-submenu>
</el-menu>
</template>
<style>
  .el-menu-vertical-demo:not(.el-menu--collapse) {
    width: 200px;
    min-height: 400px;
  }
</style>

<script>
  export default {
    data() {
      return {
        isCollapse: false,  //false展开,true收起
        // menuData 菜单数据
        menuData:[
        {
          path: '/',
          name: 'home',
          label: '首页',
          icon: 's-home',
          url: 'Home/Home'
        },
        {
          path: '/mall',
          name: 'mall',
          label: '商品管理',
          icon: 'video-play',
          url: 'MallManage/MallManage'
        },
        {
          path: '/user',
          name: 'user',
          label: '用户管理',
          icon: 'user',
          url: 'UserManage/UserManage'
        },
        {
          label: '其他',
          icon: 'location',
          children: [
            {
              path: '/page1',
              name: 'page1',
              label: '页面1',
              icon: 'setting',
              url: 'Other/PageOne'
            },
            {
              path: '/page2',
              name: 'page2',
              label: '页面2',
              icon: 'setting',
              url: 'Other/PageTwo'
            }
          ]
        }
      ]
      };
    },
    methods: {
      handleOpen(key, keyPath) {
        console.log(key, keyPath);
      },
      handleClose(key, keyPath) {
        console.log(key, keyPath);
      }
    },
    computed:{
      // computed计算属性
      // 对menuData菜单数据进行过滤,分组
      // 1.没有子菜单、
      menuNoChildren(){
        return this.menuData.filter(item=>!item.children)
      },
      // 2.有子菜单
      menuHasChildren(){
        return this.menuData.filter(item=>item.children)
      }
    }
  }
</script>

效果
在这里插入图片描述

4.3、二级动态菜单生成

遍历我们过滤好的menuHasChildren无菜单数据,生成二级菜单
==需要注意有二级菜单的子菜单遍历生成时不能使用和有二级菜单的一级菜单遍历相同名字 ==
相关代码
CommonAside.vue

<template>
  <el-menu default-active="1-4-1" class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose"
    :collapse="isCollapse">
    <el-menu-item v-for="item in menuNoChildren" :key="item.name" :index="item.name">
      <!-- 遍历生成无子菜单数据 -->
      <i :class="`el-icon-${item.icon}`"></i>
      <!-- i标签是渲染的element-ui的图标,使用了es6中的自符模板 -->
      <span slot="title">{{ item.label }}</span>
      <!-- span 标签是菜单名字 -->
    </el-menu-item>
    <!-- 遍历生成有二级菜单的一级菜单菜单 -->
    <el-submenu v-for="item in menuHasChildren" :key="item.label" :index="item.label">
      <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">{{ subItem.label }}</el-menu-item>
      </el-menu-item-group>
    </el-submenu>
  </el-menu>
</template>
<style>
.el-menu-vertical-demo:not(.el-menu--collapse) {
  width: 200px;
  min-height: 400px;
}
</style>

<script>
export default {
  data() {
    return {
      isCollapse: false,  //false展开,true收起
      // menuData 菜单数据
      menuData: [
        {
          path: '/',
          name: 'home',
          label: '首页',
          icon: 's-home',
          url: 'Home/Home'
        },
        {
          path: '/mall',
          name: 'mall',
          label: '商品管理',
          icon: 'video-play',
          url: 'MallManage/MallManage'
        },
        {
          path: '/user',
          name: 'user',
          label: '用户管理',
          icon: 'user',
          url: 'UserManage/UserManage'
        },
        {
          label: '其他',
          icon: 'location',
          children: [
            {
              path: '/page1',
              name: 'page1',
              label: '页面1',
              icon: 'setting',
              url: 'Other/PageOne'
            },
            {
              path: '/page2',
              name: 'page2',
              label: '页面2',
              icon: 'setting',
              url: 'Other/PageTwo'
            }
          ]
        }
      ]
    };
  },
  methods: {
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    }
  },
  computed: {
    // computed计算属性
    // 对menuData菜单数据进行过滤,分组
    // 1.没有子菜单、
    menuNoChildren() {
      return this.menuData.filter(item => !item.children)
    },
    // 2.有子菜单
    menuHasChildren() {
      return this.menuData.filter(item => item.children)
    }
  }
}
</script>

效果
在这里插入图片描述

4.4、菜单样式与less引入

上面我们已经把菜单已经可以正常使用了,接下来我们对其样式进行完善一下,调整一下风格。
el-menu有以下三个属性

  • background-color=“#545c64”=>背景颜色
  • text-color=“#fff”=>字体颜色
  • active-text-color=“#ffd04b”=>选中后字体颜色

在这里插入图片描述
效果
在这里插入图片描述
修改整体高度和宽度
在这里插入图片描述

4.4.1、less使用和引入

less是css语法增强版的,可以使用less在我们这种模块化的项目中能够快速的创建我们的样式
less官方文档
在这里插入图片描述
下载less

npm i less@4.1.2
npm i less-loader@6.0.0  //less的解析器

在这里插入图片描述
使用less改写菜单样式

<template>
  <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>vue前端通用管理后台</h3>
    <el-menu-item
      v-for="item in menuNoChildren"
      :key="item.name"
      :index="item.name"
    >
      <!-- 遍历生成无子菜单数据 -->
      <i :class="`el-icon-${item.icon}`"></i>
      <!-- i标签是渲染的element-ui的图标,使用了es6中的自符模板 -->
      <span slot="title">{{ item.label }}</span>
      <!-- span 标签是菜单名字 -->
    </el-menu-item>
    <!-- 遍历生成有二级菜单的一级菜单菜单 -->
    <el-submenu
      v-for="item in menuHasChildren"
      :key="item.label"
      :index="item.label"
    >
      <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">{{ subItem.label }}</el-menu-item>
      </el-menu-item-group>
    </el-submenu>
  </el-menu>
</template>
<style scoped lang="less">
.el-menu-vertical-demo:not(.el-menu--collapse) {
  width: 200px;
  min-height: 400px;
}
// 菜单高度和标题样式
.el-menu{
  height: 100vh;
  h3{
    margin: 0;
    color: white;
    text-align: center;
    line-height: 48px;
    font-size: 16px;
    font-weight: 400;
  }
}
</style>

<script>
export default {
  data() {
    return {
      isCollapse: false, //false展开,true收起
      // menuData 菜单数据
      menuData: [
        {
          path: "/",
          name: "home",
          label: "首页",
          icon: "s-home",
          url: "Home/Home",
        },
        {
          path: "/mall",
          name: "mall",
          label: "商品管理",
          icon: "video-play",
          url: "MallManage/MallManage",
        },
        {
          path: "/user",
          name: "user",
          label: "用户管理",
          icon: "user",
          url: "UserManage/UserManage",
        },
        {
          label: "其他",
          icon: "location",
          children: [
            {
              path: "/page1",
              name: "page1",
              label: "页面1",
              icon: "setting",
              url: "Other/PageOne",
            },
            {
              path: "/page2",
              name: "page2",
              label: "页面2",
              icon: "setting",
              url: "Other/PageTwo",
            },
          ],
        },
      ],
    };
  },
  methods: {
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    },
  },
  computed: {
    // computed计算属性
    // 对menuData菜单数据进行过滤,分组
    // 1.没有子菜单、
    menuNoChildren() {
      return this.menuData.filter((item) => !item.children);
    },
    // 2.有子菜单
    menuHasChildren() {
      return this.menuData.filter((item) => item.children);
    },
  },
};
</script>

效果
在这里插入图片描述
清除标签默认样式样式
APP.vue文件

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

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

}
</script>

<style lang="less">
// 清除html,body的内网边距
html,body{
  margin: 0;
  padding: 0;
}
</style>

效果
在这里插入图片描述

4.5、菜单点击跳转功能实现

我们上面已经完成了左侧的样式以及基本的布局,现在我们要完善一下当我们点击菜单选项,完成路由跳转展示

4.5.1、根据菜单数据,我们先定义一下我们未定义的路由**

先新建对应路由页面文件
Mall.vue商品管理页面

<template>
  <div>
    <h1>Mall页面</h1>
  </div>
</template>
<script>
export default{
  data(){
    return{
      
    }
  }
}
</script>

qtPageOne.vue其他页面1页面

<template>
  <div>
    <h1>qtPageOne页面</h1>
  </div>
</template>
<script>
export default{
  data(){
    return{
      
    }
  }
}
</script>

qtPageTow.vue其他页面2页面

<template>
  <div>
    <h1>qtPageTow页面</h1>
  </div>
</template>
<script>
export default{
  data(){
    return{
      
    }
  }
}
</script>

router文件夹index.js路由·配置文件

// 路由配置文件

// 如果在一个模块化工程中使用它,必须要通过 Vue.use() 明确地安装路由功能,也可以再main.js引入
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
import Main from "../views/Mian.vue";
// 主页面
// 1.创建路由组件,也就是我们创建views里面的组件文件
// 引入我们创建的vue组件文件
import Home from "../views/Home.vue";
import User from "../views/User.vue";
import Mall from "../views/Mall.vue";
import qtPageOne from "../views/qtPageOne.vue";
import qtPageTow from "../views/qtPageTow.vue";
// 2. 定义路由
// 将路由与组件进行映射
const routes = [
  // 主页面
  {
    path: "/",
    //  /默认是主出口主页面组件
    redirect:'home',
    // 重定向到home
    component: Main,
    children: [
      // 子路由
      { path: "home", component: Home },
      // 首页
      { path: "user", component: User },
      // 用户管理
      { path: "mall", component: Mall },
      // 商品管理
      { path: "page1", component: qtPageOne },
      // 其他页面1
      { path: "page2", component: qtPageTow },
      // 其他页面2
    ],
  },
];
// 3. 创建 router 实例,然后传routes配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
  routes, // (缩写) 相当于 routes: routes
});

// 导出我们的router实例
export default router;

文件结构
在这里插入图片描述

4.5.2、点击菜单,根据点击菜单的数据跳转应路由页面**
<template>
  <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>vue前端通用管理后台</h3>
    <!-- 点击菜单选项触发clickMenu事件 -->
    <el-menu-item
      @click="clickMenu(item)"
      v-for="item in menuNoChildren"
      :key="item.name"
      :index="item.name"
    >
      <!-- 遍历生成无子菜单数据 -->
      <i :class="`el-icon-${item.icon}`"></i>
      <!-- i标签是渲染的element-ui的图标,使用了es6中的自符模板 -->
      <span slot="title">{{ item.label }}</span>
      <!-- span 标签是菜单名字 -->
    </el-menu-item>
    <!-- 遍历生成有二级菜单的一级菜单菜单 -->
    <el-submenu

      v-for="item in menuHasChildren"
      :key="item.label"
      :index="item.label"
    >
      <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">
        <!-- 子菜单渲染 -->
        <!-- clickMenu跳转 -->
        <el-menu-item     @click="clickMenu(subItem)" :index="subItem.path">{{ subItem.label }}</el-menu-item>
      </el-menu-item-group>
    </el-submenu>
  </el-menu>
</template>
<style scoped lang="less">
.el-menu-vertical-demo:not(.el-menu--collapse) {
  width: 200px;
  min-height: 400px;
}
// 菜单高度和标题样式
.el-menu {
  height: 100vh;
  border-right: none;
  h3 {
    margin: 0;
    color: white;
    text-align: center;
    line-height: 48px;
    font-size: 16px;
    font-weight: 400;
  }
}
</style>

<script>
export default {
  data() {
    return {
      isCollapse: false, //false展开,true收起
      // menuData 菜单数据
      menuData: [
        {
          path: "/",
          name: "home",
          label: "首页",
          icon: "s-home",
          url: "Home/Home",
        },
        {
          path: "/mall",
          name: "mall",
          label: "商品管理",
          icon: "video-play",
          url: "MallManage/MallManage",
        },
        {
          path: "/user",
          name: "user",
          label: "用户管理",
          icon: "user",
          url: "UserManage/UserManage",
        },
        {
          label: "其他",
          icon: "location",
          children: [
            {
              path: "/page1",
              name: "page1",
              label: "页面1",
              icon: "setting",
              url: "Other/PageOne",
            },
            {
              path: "/page2",
              name: "page2",
              label: "页面2",
              icon: "setting",
              url: "Other/PageTwo",
            },
          ],
        },
      ],
    };
  },
  methods: {
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    },
    // 点击菜单,跳转
    clickMenu(item) {
      // item 触发传递的数据
      console.log(item.path);
      // 添加容错逻辑
      // 当我们的路由与我们跳转的路由不一致时,才允许跳转
      // 当前页面路由
      if(this.$route.path!=item.path &&!(this.$route.path==='/home'&&(item.path==='/'))){
        this.$router.push(item.path);
      }
    },
  },
  computed: {
    // computed计算属性
    // 对menuData菜单数据进行过滤,分组
    // 1.没有子菜单、
    menuNoChildren() {
      return this.menuData.filter((item) => !item.children);
    },
    // 2.有子菜单
    menuHasChildren() {
      return this.menuData.filter((item) => item.children);
    },
  },
};
</script>

效果
在这里插入图片描述

5、通用管理后台(header组件与样式搭建)

5.1、封装header区域组件与样式

菜单区域我们已经完成了,接下来我们完成一下header区域的布局和样式编写,一个是左侧的按钮区域和面包屑区域,另外就是用户头像区域带下来菜单。
封装创建一个CommonHeader.vue

<template>
  <div class="header-content">
    <!-- 头部组件 -->
    <div class="l-content">
      <!-- 左侧 -->
      <!-- 点icon的按钮 -->
      <el-button icon="el-icon-menu" size="mini"></el-button>
      <!-- 面包屑,先用span标签代替 -->
      <span class="text">首页</span>
    </div>
    <div class="r-content">
      <!-- 右侧 -->
      <!-- el-dropdown 下拉菜单-->
      <el-dropdown>
        <img class="user" src="../assets/tx.png" alt="">
        <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 {};
  }
};
</script>
<style lang="less" scoped>
.header-content {
  padding: 0 20px;
  background-color: #333;
  height: 60px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  .text {
    color: white;
    font-size: 14px;
    margin-left: 10px;
  }
  .r-content{
    .user{
      // 头像
      width: 40px;
      height: 40px;
      border-radius: 50%;
    }
  }
}
</style>

在这里插入图片描述
在Main.vue中引入并且使用
Main.vue

<template>
  <div>
    <el-container>
      <el-aside width="200px">
        <!-- 把引入的CommonAside菜单组件进行使用 -->
        <CommonAside/>
      </el-aside>
      <el-container>
        <el-header>
          <CommonHeader/>
              <!-- 把引入的CommonHeader头部组件进行使用 -->
        </el-header>
        <el-main>
       
          <!-- router-view 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 {
  // 在components中引入组件,引入好,就可以在template中直接使用了
  components: {
    CommonAside,
    CommonHeader
  },
  data() {
    return{
      
    }
  }
}
</script>
<style>
.el-header{
  padding: 0;
  border: none;
}
</style>

效果
在这里插入图片描述

5.2、通过vuex实现左侧折叠

前面已经完善了header区域的布局和样式,接下来把header区域按钮的点击操作菜单折叠与展开完善一下!但是按钮在CommonHeader.vue组件中,那么如何去操作CommonAside.vue菜单组件呢?

在这里插入图片描述
因为状态修改牵扯到俩个组件中的变化,接下来我们就用vuex去实现

5.2.1、什么是vuex?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex分为俩个比较大的版本,一个是4.0(主要针对于vue3),一个3.0(主要针对vue2),
Vuex4官网
Vuex3官网
在这里插入图片描述

5.2.2、安装vuex
npm i vuex@3.6.2

在这里插入图片描述

5.2.3、配置vuex

在src文件下创建一个store文件夹,在store新建一个index.js文件配置vuex
因为菜单和用户是俩部分单独的一块信息,对于这种数据,按照模块化的思想,把它拆分成俩个 modules
新建一个tab.js用于管理菜单的数据

// 管理菜单的数据
export default{
  state:{
    isCollapse:false,
    // isCollapse用于控制菜单的展开和收起
  },
  mutations:{
    // 修改菜单展开收起方法
    collapseMenu(state){
      state.isCollapse=!state.isCollapse
    }
  }
}

``
在store下面的index.js配置vuex和注入`
```js
// 引入vue
import Vue from 'vue'
import Vuex from 'vuex'
import tab from './tab'
// 引入管理菜单的数据
Vue.use(Vuex)
// 将Vuex全局注入

// 创建vuex实例
export default new Vuex.Store({
  modules:{
    tab
  }
})

在mian.js将我们的vuex实例挂载到vue上

import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
// 引入element-ui样式文件
import router from './router'
// 引入路由文件
import store from'./store'
Vue.config.productionTip = false
Vue.use(ElementUI);
// 全局注入element-ui

new Vue({
  router,
  // 挂载router实例
  store,
  // 挂载vuex实例
  el: '#app',
  render: h => h(App),
}).$mount('#app')

5.2.4、使用vuex

在我们组件中使用vuex完成我们的点击按钮让菜单展开收起的功能
在CommonHeader.vue中点击按钮调用mutations方法修改vuex中state里isCollapse数据
CommonHeader.vue
HandelMenu方法使我们点击按钮事件方法

<template>
  <div class="header-content">
    <!-- 头部组件 -->
    <div class="l-content">
      <!-- 左侧 -->
      <!-- icon的按钮 ,HandelMenu点击按钮方法 -->
      <el-button @click="HandelMenu" icon="el-icon-menu" size="mini"></el-button>
      <!-- 面包屑,先用span标签代替 -->
      <span class="text">首页</span>
    </div>
    <div class="r-content">
      <!-- 右侧 -->
      <!-- el-dropdown 下拉菜单-->
      <el-dropdown>
        <img class="user" src="../assets/tx.png" alt="">
        <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:{
    HandelMenu(){
      // 按钮点击事件,调用vuex的mutations方法
      this.$store.commit('collapseMenu')
    }
  }
};
</script>
<style lang="less" scoped>
.header-content {
  padding: 0 20px;
  background-color: #333;
  height: 60px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  .text {
    color: white;
    font-size: 14px;
    margin-left: 10px;
  }
  .r-content{
    .user{
      // 头像
      width: 40px;
      height: 40px;
      border-radius: 50%;
    }
  }
}
</style>

CommonAside.vue
获取vuex的state中的isCollapse数据,绑定我们菜单实现菜单展开和收缩
在computed属性中进行使用的

<template>
  <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>vue前端通用管理后台</h3>
    <!-- 点击菜单选项触发clickMenu事件 -->
    <el-menu-item
      @click="clickMenu(item)"
      v-for="item in menuNoChildren"
      :key="item.name"
      :index="item.name"
    >
      <!-- 遍历生成无子菜单数据 -->
      <i :class="`el-icon-${item.icon}`"></i>
      <!-- i标签是渲染的element-ui的图标,使用了es6中的自符模板 -->
      <span slot="title">{{ item.label }}</span>
      <!-- span 标签是菜单名字 -->
    </el-menu-item>
    <!-- 遍历生成有二级菜单的一级菜单菜单 -->
    <el-submenu

      v-for="item in menuHasChildren"
      :key="item.label"
      :index="item.label"
    >
      <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">
        <!-- 子菜单渲染 -->
        <!-- clickMenu跳转 -->
        <el-menu-item     @click="clickMenu(subItem)" :index="subItem.path">{{ subItem.label }}</el-menu-item>
      </el-menu-item-group>
    </el-submenu>
  </el-menu>
</template>
<style scoped lang="less">
.el-menu-vertical-demo:not(.el-menu--collapse) {
  width: 200px;
  min-height: 400px;
}
// 菜单高度和标题样式
.el-menu {
  height: 100vh;
  border-right: none;
  h3 {
    margin: 0;
    color: white;
    text-align: center;
    line-height: 48px;
    font-size: 16px;
    font-weight: 400;
  }
}
</style>

<script>
export default {
  data() {
    return {
    
      // menuData 菜单数据
      menuData: [
        {
          path: "/",
          name: "home",
          label: "首页",
          icon: "s-home",
          url: "Home/Home",
        },
        {
          path: "/mall",
          name: "mall",
          label: "商品管理",
          icon: "video-play",
          url: "MallManage/MallManage",
        },
        {
          path: "/user",
          name: "user",
          label: "用户管理",
          icon: "user",
          url: "UserManage/UserManage",
        },
        {
          label: "其他",
          icon: "location",
          children: [
            {
              path: "/page1",
              name: "page1",
              label: "页面1",
              icon: "setting",
              url: "Other/PageOne",
            },
            {
              path: "/page2",
              name: "page2",
              label: "页面2",
              icon: "setting",
              url: "Other/PageTwo",
            },
          ],
        },
      ],
    };
  },
  methods: {
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    },
    // 点击菜单,跳转
    clickMenu(item) {
      // item 触发传递的数据
      console.log(item.path);
      // 添加容错逻辑
      // 当我们的路由与我们跳转的路由不一致时,才允许跳转
      // 当前页面路由
      if(this.$route.path!=item.path &&!(this.$route.path==='/home'&&(item.path==='/'))){
        this.$router.push(item.path);
      }
    },
  },
  computed: {
    // computed计算属性
    // 对menuData菜单数据进行过滤,分组
    // 1.没有子菜单、
    menuNoChildren() {
      return this.menuData.filter((item) => !item.children);
    },
    // 2.有子菜单
    menuHasChildren() {
      return this.menuData.filter((item) => item.children);
    },
    // 在computed属性下
    isCollapse(){
      return this.$store.state.tab.isCollapse
      // 我们state的isCollapse数据
      //false展开,true收起
    }
  },
};
</script>

效果
展开
在这里插入图片描述
收缩=>可以看到有bug
在这里插入图片描述

5.3、解决菜单收缩展开的bug

利用三元判断修改左侧收起标题问题

    <h3>{{isCollapse?'vue前端通用管理后台':'后台'}}</h3>

宽度问题
Main.vue中el-aside 将之前我们设置的width="200px"值改为auto自适应

      <el-aside width="auto">
        <!-- 把引入的CommonAside菜单组件进行使用 -->
        <CommonAside/>
      </el-aside>

效果
在这里插入图片描述
在这里插入图片描述

5、通用管理后台(home组件与样式搭建)

关于home组件包含左侧用户信息和table数据,右侧是订单数据加上折线图柱状图饼状图,并且自适应布局(使用element-ui中的layout布局),鼠标移入区域有阴影(element-ui中的card组件)

5.1、home组件布局
5.1.1、home组件–左侧用户信息

Home.vue

<template>
  <el-row>
    <!-- span是所占的比例 -->
    <el-col :span="8">
      <el-card class="box-card">
        <div class="user">
          <!-- 用户信息 -->
          <img src="../assets/tx.png" alt="">
          <div class="userDetail">
            <p class="name">前端初见</p>
            <p class="access">管理员</p>
          </div>
        </div>
        <div class="user-info">
          <p>上次登录时间:<span>2023-3-29</span></p>
          <p>上次登录地点:<span>中山</span></p>
        </div>
      </el-card>
    </el-col>
    <el-col :span="16"></el-col>
  </el-row>
</template>
<script>
export default {
  data() {
    return {};
  }
};
</script>
<style lang="less" scoped>
.user{
  display: flex;
  align-items: center;
  border-bottom: 1px solid #ccc;
  padding-bottom: 20px;
  margin-bottom: 20px;
  img{
    margin-right: 40px;
    width: 150px;
    height: 150px;
    border-radius: 50%;
  }
  .userDetail{
    // 用户详情信息
    .name{
      // 用户名
      font-size: 32px;
      margin-bottom: 10px;
    }
    .access{
      // 角色
      color: #999;
    }
  }
}

.user-info{
// 登录时间和地点
p{
  line-height: 28px;
  font-size: 14px;
  color: #999;
  span{
    color: #666;
    margin-left: 60px;
  }
}
}
</style>

效果
在这里插入图片描述

5.1.2、home组件–左侧table表格信息

对于table在后台管理中是一个用的比较多点,基本每个后台都有,像列表页信息的展示等等,我们使用element-ui组件库中的el-table实现
表格数据

      tableData: [
        {
          name: "HTML",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "CSS",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "JavaScript",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "Vue",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "React",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "微信小程序",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        }
      ],

Home.vue表格=>(静态)

<template>
  <el-row>
    <!-- span是所占的比例 -->
    <el-col :span="8">
      <el-card class="box-card">
        <div class="user">
          <!-- 用户信息 -->
          <img src="../assets/tx.png" alt />
          <div class="userDetail">
            <p class="name">前端初见</p>
            <p class="access">管理员</p>
          </div>
        </div>
        <div class="user-info">
          <p>
            上次登录时间:
            <span>2023-3-29</span>
          </p>
          <p>
            上次登录地点:
            <span>中山</span>
          </p>
        </div>
      </el-card>
      <el-card style="margin-top: 20px; height:380px" class="box-card">
        <el-table :data="tableData" style="width: 100%">
          <el-table-column prop="name" label="技术栈"></el-table-column>
          <el-table-column prop="todayBuy" label="今日学习人数" ></el-table-column>
          <el-table-column prop="monthBuy" label="本月学习人数"></el-table-column>
          <el-table-column prop="totalBuy" label="学习总数据"></el-table-column>
        </el-table>
      </el-card>
    </el-col>
    <el-col :span="16"></el-col>
  </el-row>
</template>
<script>
export default {
  data() {
    return {
      // tableData表格数据
      tableData: [
        {
          name: "HTML",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "CSS",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "JavaScript",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "Vue",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "React",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "微信小程序",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        }
      ],
    };
  }
};
</script>
<style lang="less" scoped>
.user {
  display: flex;
  align-items: center;
  border-bottom: 1px solid #ccc;
  padding-bottom: 20px;
  margin-bottom: 20px;
  img {
    margin-right: 40px;
    width: 150px;
    height: 150px;
    border-radius: 50%;
  }
  .userDetail {
    // 用户详情信息
    .name {
      // 用户名
      font-size: 32px;
      margin-bottom: 10px;
    }
    .access {
      // 角色
      color: #999;
    }
  }
}

.user-info {
  // 登录时间和地点
  p {
    line-height: 28px;
    font-size: 14px;
    color: #999;
    span {
      color: #666;
      margin-left: 60px;
    }
  }
}
</style>

Home.vue表格=>(动态)

<template>
  <el-row>
    <!-- span是所占的比例 -->
    <el-col :span="8">
      <el-card class="box-card">
        <div class="user">
          <!-- 用户信息 -->
          <img src="../assets/tx.png" alt />
          <div class="userDetail">
            <p class="name">前端初见</p>
            <p class="access">管理员</p>
          </div>
        </div>
        <div class="user-info">
          <p>
            上次登录时间:
            <span>2023-3-29</span>
          </p>
          <p>
            上次登录地点:
            <span>中山</span>
          </p>
        </div>
      </el-card>
      <el-card style="margin-top: 20px; height:380px" class="box-card">
        <!-- 静态 -->
        <!-- <el-table :data="tableData" style="width: 100%">
          <el-table-column prop="name" label="技术栈"></el-table-column>
          <el-table-column prop="todayBuy" label="今日学习人数" ></el-table-column>
          <el-table-column prop="monthBuy" label="本月学习人数"></el-table-column>
          <el-table-column prop="totalBuy" label="学习总数据"></el-table-column>
        </el-table> -->

        <!-- 遍历生成表格 -->
        <el-table :data="tableData" style="width: 100%">
          <el-table-column v-for="(val, key) in tableLabel" :key="key" :prop="key" :label="val" />
        </el-table>
      </el-card>
    </el-col>
    <el-col :span="16"></el-col>
  </el-row>
</template>
<script>
export default {
  data() {
    return {
      // tableData表格数据
      tableData: [
        {
          name: "HTML",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "CSS",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "JavaScript",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "Vue",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "React",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "微信小程序",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        }
      ],
      // tableLabel根据表格整理表格表格label数据
      tableLabel: {
                name: '技术栈',
                todayBuy: '今日学习人数',
                monthBuy: '本月学习人数',
                totalBuy: '学习总数据'
            },
    };
  }
};
</script>
<style lang="less" scoped>
.user {
  display: flex;
  align-items: center;
  border-bottom: 1px solid #ccc;
  padding-bottom: 20px;
  margin-bottom: 20px;
  img {
    margin-right: 40px;
    width: 150px;
    height: 150px;
    border-radius: 50%;
  }
  .userDetail {
    // 用户详情信息
    .name {
      // 用户名
      font-size: 32px;
      margin-bottom: 10px;
    }
    .access {
      // 角色
      color: #999;
    }
  }
}

.user-info {
  // 登录时间和地点
  p {
    line-height: 28px;
    font-size: 14px;
    color: #999;
    span {
      color: #666;
      margin-left: 60px;
    }
  }
}
</style>

效果
在这里插入图片描述

5.1.3、home组件–右侧学习数据统计实现

前面我们已经完成了左侧布局和table数据展示,图表使用了es6模板字符串拼接类名结合element的图表实现的
Home.vue=>class="num"盒子

<template>
  <el-row>
    <!-- span是所占的比例 -->
    <el-col :span="8">
      <el-card class="box-card">
        <div class="user">
          <!-- 用户信息 -->
          <img src="../assets/tx.png" alt />
          <div class="userDetail">
            <p class="name">前端初见</p>
            <p class="access">管理员</p>
          </div>
        </div>
        <div class="user-info">
          <p>
            上次登录时间:
            <span>2023-3-29</span>
          </p>
          <p>
            上次登录地点:
            <span>中山</span>
          </p>
        </div>
      </el-card>
      <el-card style="margin-top: 20px; height:380px" class="box-card">
        <!-- 静态 -->
        <!-- <el-table :data="tableData" style="width: 100%">
          <el-table-column prop="name" label="技术栈"></el-table-column>
          <el-table-column prop="todayBuy" label="今日学习人数" ></el-table-column>
          <el-table-column prop="monthBuy" label="本月学习人数"></el-table-column>
          <el-table-column prop="totalBuy" label="学习总数据"></el-table-column>
        </el-table>-->

        <!-- 遍历生成表格 -->
        <el-table :data="tableData" style="width: 100%">
          <el-table-column v-for="(val, key) in tableLabel" :key="key" :prop="key" :label="val" />
        </el-table>
      </el-card>
    </el-col>
    <el-col :span="16">
      <!-- 右侧区域 -->
      <div class="num">
        <el-card :body-style="{display:'flex',padding:0}" v-for="item in countData" :key="item.name">
          <i :style="{background:item.color}" class="icon" :class="`el-icon-${item.icon}`"></i>
          <div class="detail">
            <p class="dataPrice">{{ item.value }}</p>
            <p class="dataDesc">{{ item.name }}</p>
          </div>
        </el-card>
      </div>
    </el-col>
  </el-row>
</template>
<script>
export default {
  data() {
    return {
      // tableData表格数据
      tableData: [
        {
          name: "HTML",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "CSS",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "JavaScript",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "Vue",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "React",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        },
        {
          name: "微信小程序",
          todayBuy: 100,
          monthBuy: 300,
          totalBuy: 800
        }
      ],
      // tableLabel根据表格整理表格表格label数据
      tableLabel: {
        name: "技术栈",
        todayBuy: "今日学习人数",
        monthBuy: "本月学习人数",
        totalBuy: "学习总数据"
      },
      // countData 学习数据统计
      countData: [
        {
          name: "今日学习数据",
          value: 1234,
          icon: "s-data",
          color: "deeppink"
        },
        {
          name: "今日收藏文章",
          value: 210,
          icon: "star-on",
          color: "red"
        },
        {
          name: "今日查看数据",
          value: 1234,
          icon: "thumb",
          color: "chartreuse"
        },
        {
          name: "本月学习数据",
          value: 1234,
          icon: "s-data",
          color: "deeppink"
        },
        {
          name: "本月收藏文章",
          value: 210,
          icon: "star-on",
          color: "red"
        },
        {
          name: "本月查看数据",
          value: 1234,
          icon: "thumb",
          color: "chartreuse"
        }
      ]
    };
  }
};
</script>
<style lang="less" scoped>
.user {
  display: flex;
  align-items: center;
  border-bottom: 1px solid #ccc;
  padding-bottom: 20px;
  margin-bottom: 20px;
  img {
    margin-right: 40px;
    width: 150px;
    height: 150px;
    border-radius: 50%;
  }
  .userDetail {
    // 用户详情信息
    .name {
      // 用户名
      font-size: 32px;
      margin-bottom: 10px;
    }
    .access {
      // 角色
      color: #999;
    }
  }
}

.user-info {
  // 登录时间和地点
  p {
    line-height: 28px;
    font-size: 14px;
    color: #999;
    span {
      color: #666;
      margin-left: 60px;
    }
  }
}
// 右侧学习数据
.num {
  display: flex;
  flex-wrap: wrap;  // 强制换行
  justify-content: space-between;

  .icon {
    // 图表
    width: 80px;
    height: 80px;
    font-size: 30px;
    text-align: center;
    line-height: 80px;
    color: white;
  }
  .detail{
    display: flex;
    flex-direction: column;
    justify-content: center;
    margin-left: 15px;
    .dataPrice{
      font-size: 30px;
      margin-bottom: 10px;
      line-height: 30px;
      height: 30px;
    }
    .dataDesc{
      color: #999;
      font-size: 14px;
      text-align: center;
    }
  }
  .el-card{
    // 右侧卡片
    width: 32%;
    margin-bottom: 20px;
  }

}
</style>

效果
在这里插入图片描述

6、通用管理后台(Axios 和Mock.js模拟数据)

那么接下来我们继续完成剩下的图表展示部分,我们除了图表的展示,另外呢就是图表的数据,不过在这个到目前为止,我们的数据还都是写死的,那么在我们实际项目开发中,我们的数据一般是由后端返回给我们的,后端经过逻辑处理然后返回给我们
Axios 文档
在这里插入图片描述

Mock.js文档
在这里插入图片描述

6.1、扩展–前后端联调

在我们开发的页面中,我们的代码都是运行在游览器上的,那我们想获取数据,具体步骤是游览器向服务器发送一个ajax请求,服务器接收到请求之后之后,将数据回传给游览器
发送请求有很多种方式,比如原生js的XMLHttpRequest对象实现ajax请求,当然这种实现方式是比较复杂的,但是在项目开发中,我们更多的是使用工具或者第三方库来完成我们这些功能,接下来就介绍一个非常使用的axios插件

6.2、Axios

Axios 相对于我们原生js的XMLHttpRequest,Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。从写法上可以避免回调地狱的问题,因为我们可以通async和await这俩种写法结合我们promise 将整个异步的过程转换成同步的写法,另外Axios 封装了一些比较好用的api,我们可以直接调用它,实现我们一些一些接口请求的封装、公共的请求头等等

6.2.1、Axios 的基本使用

安装:

npm install axios

在项目文件下进行安装
在这里插入图片描述

6.2.2、Axios 的二次封装

安装完成之后,我们要使用axios做接口请求,在我们项目中接口是非常多的,我们要对这些进行一个二次封装,我们封装的就是 Axios 的实例,axios.create这个方法会返回一个实例,我们拿到这些实例之后,就可以调用这些实例的方法,这些实例的方法就具备我们创建Axios时配置的一些属性,对于这些实例我们还可以配置个拦截器(请求拦截器和响应拦截器) ,请求拦截器在发送请求之前进行的一些处理(像header请求头的内容token等等),响应拦截器是服务器端回传消息给我们的时候,在我们拿到数据之前可以做哪些处理 (像http的状态码,像非200的状态码可以给用户一些提示或者对业务数据进行逻辑判断等等,), 把相对繁琐的的一些请求进行一下处理。
在这里插入图片描述
接下来我们就完成Axios 在项目中的一个封装
在src文件夹下面新建一个utils文件夹用于做数据处理文件的文件夹
utils文件夹=>新建一个requerst.js文件
这里对拦截器没有过多地封装,大家可以根据自己需要网上找一下即可

import axios from 'axios'
// 引入axios
const http=axios.create({
  baseURL:'/api',
  // baseURL通用请求地址(地址前缀),这个地址根据实际项目中更改成自己的即可
  timeout:10000,
  // timeout 请求超时时间 单位是毫秒
  // 其余配置如果需要可参考axios文档进行配置=>http://www.axios-js.com/zh-cn/docs/vue-axios.html
})
// 拦截器
// 添加请求拦截器
http.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 添加响应拦截器
http.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  return response;
}, function (error) {
  // 对响应错误做点什么
  return Promise.reject(error);
});

export default http
6.2.3、api调用接口文件封装

在src文件夹下面新建一个api文件夹用于放封装的接口调用文件
api文件夹=>新建index.js
因为涉及接口比较少,就以index.js做演示,后续大家可以根据项目需要进行一下分类单独封装

import http from "@/utils/request";
// 引入我们二次封装的axios实例


// 接口调用封装
// 请求首页数据
export const getData=()=>{
  // /home/getData接口地址
  // 返回一个promise对象
  return http.get('/home/getData')
}

在页面中使用封装封装好的请求接口方法

<script>
import {getData} from '../api'
// 引入封装好的请求方法
export default {
  data() {
    return {
  },
  mounted(){
    // mounted挂载
    getData().then(res=>{
    //使用getData封装方法
      console.log(res)
    })
  }
};
</script>

结果
可以看到发送了请求,目前只是我们还没有后端服务,所以会报404的错误,不过从这里我们已经完成了Axios的二次封装,并且发送了请求
在这里插入图片描述
因为上面没有后端服务,那么接下来就简单介绍一下mock数据,模拟后端接口,实现交互

6.3、Mock.js

在没有后端提供数据的情况下,前端人员在自己写demo或者练手项目的时候可以使用mock.js来模拟数据
Mock是一个前端模拟后端接口的一个工具,我们可以通过拦截前端发起的请求,可以自己定义一些返回的数据,这种情况下是我们在不依赖后端的情况下,实现数据交互

6.3.1、Mock.js安装
# 安装
npm install mockjs

在项目文件下进行安装
在这里插入图片描述

6.3.2、Mock.mock()方法

Mock.mock()方法参数以及使用
Mock.mock( rurl?, rtype?, template|function( options ) )

6.3.3、Mock使用

api文件夹=>新建mock.js

import Mock from 'mockjs'
// 引入mock


// 定义mock接口请求拦截
/**
 * Mock.mock参数
 * 第一个是拦截接口的地址
 * 第二个是接口请求类型(可省略)
 * 第三个是一个函数=>拦截后的请求逻辑
 */
Mock.mock('/api/home/getData','get',function(){
  // 拦截后的请求逻辑
  console.log('拦截到了')
})


在main.js引入mock处理文件

import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
// 引入element-ui样式文件
import router from './router'
// 引入路由文件
import store from'./store'

// 引入mock处理文件
import './api/mock'
Vue.config.productionTip = false
Vue.use(ElementUI);
// 全局注入element-ui

new Vue({
  router,
  // 挂载router实例
  store,
  // 挂载vuex实例
  el: '#app',
  render: h => h(App),
}).$mount('#app')

效果
在这里插入图片描述

在这里插入图片描述
以上是完成了第一步,对mock进行简单的使用,接下来就正式模拟接口和数据

6.3.4、Mock模拟接口数据

api文件夹=>新建mockServeData文件夹
用于存放对应页面模拟数据文件
mockServeData文件夹=>新建home.js
存放首页mock数据

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

// 图表数据
let List = []
export default {
  getStatisticalData: () => {
    //Mock.Random.float 产生随机数100到8000之间 保留小数 最小0位 最大0位
    for (let i = 0; i < 7; i++) {
      List.push(
        Mock.mock({
          苹果: Mock.Random.float(100, 8000, 0, 0),
          vivo: Mock.Random.float(100, 8000, 0, 0),
          oppo: Mock.Random.float(100, 8000, 0, 0),
          魅族: Mock.Random.float(100, 8000, 0, 0),
          三星: Mock.Random.float(100, 8000, 0, 0),
          小米: Mock.Random.float(100, 8000, 0, 0)
        })
      )
    }
    return {
      code: 20000,
      data: {
        // 饼图
        videoData: [
          {
            name: '小米',
            value: 2999
          },
          {
            name: '苹果',
            value: 5999
          },
          {
            name: 'vivo',
            value: 1500
          },
          {
            name: 'oppo',
            value: 1999
          },
          {
            name: '魅族',
            value: 2200
          },
          {
            name: '三星',
            value: 4500
          }
        ],
        // 柱状图
        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: 'oppo',
            todayBuy: 500,
            monthBuy: 3500,
            totalBuy: 22000
          },
          {
            name: 'vivo',
            todayBuy: 300,
            monthBuy: 2200,
            totalBuy: 24000
          },
          {
            name: '苹果',
            todayBuy: 800,
            monthBuy: 4500,
            totalBuy: 65000
          },
          {
            name: '小米',
            todayBuy: 1200,
            monthBuy: 6500,
            totalBuy: 45000
          },
          {
            name: '三星',
            todayBuy: 300,
            monthBuy: 2000,
            totalBuy: 34000
          },
          {
            name: '魅族',
            todayBuy: 350,
            monthBuy: 3000,
            totalBuy: 22000
          }
        ]
      }
    }
  }
}

将mock里面的第三个参数拦截后的请求逻辑进行一下替换


import Mock from 'mockjs'
// 引入mock
import homeApiData from './mockServeData/home'
//引入我们处理好的数据,作为进行拦截后的请求逻辑的数据
// 定义mock接口请求拦截
/**
 * Mock.mock参数
 * 第一个是拦截接口的地址
 * 第二个是接口请求类型(可省略)
 * 第三个是一个函数=>拦截后的请求逻辑
 */
Mock.mock('/api/home/getData','get',homeApiData.getStatisticalData)

效果
在这里插入图片描述
在这里插入图片描述

7、通用管理后台(首页可视化图表样式调整)

在前面我们已经模拟了后端以及axios封装和接口的封装和调用,调用后也获取到了数据
在这里插入图片描述
接下来我们就把之前的数据全部换成我们的动态数据!

7.1、首页table由静态数据切换成动态数据**

Home.vue
将获取res.data.data.tableData是返回的table数据给tableData重新赋值,这样表格就变成了动态数据

<template>
  <el-row>
    <!-- span是所占的比例 -->
    <el-col :span="8">
      <el-card class="box-card">
        <div class="user">
          <!-- 用户信息 -->
          <img src="../assets/tx.png" alt />
          <div class="userDetail">
            <p class="name">前端初见</p>
            <p class="access">管理员</p>
          </div>
        </div>
        <div class="user-info">
          <p>
            上次登录时间:
            <span>2023-3-29</span>
          </p>
          <p>
            上次登录地点:
            <span>中山</span>
          </p>
        </div>
      </el-card>
      <el-card style="margin-top: 20px; height:380px" class="box-card">
        <!-- 静态 -->
        <!-- <el-table :data="tableData" style="width: 100%">
          <el-table-column prop="name" label="技术栈"></el-table-column>
          <el-table-column prop="todayBuy" label="今日学习人数" ></el-table-column>
          <el-table-column prop="monthBuy" label="本月学习人数"></el-table-column>
          <el-table-column prop="totalBuy" label="学习总数据"></el-table-column>
        </el-table>-->

        <!-- 遍历生成表格 -->
        <el-table :data="tableData" style="width: 100%">
          <el-table-column v-for="(val, key) in tableLabel" :key="key" :prop="key" :label="val" />
        </el-table>
      </el-card>
    </el-col>
    <el-col :span="16">
      <!-- 右侧区域 -->
      <div class="num">
        <el-card :body-style="{display:'flex',padding:0}" v-for="item in countData" :key="item.name">
          <i :style="{background:item.color}" class="icon" :class="`el-icon-${item.icon}`"></i>
          <div class="detail">
            <p class="dataPrice">{{ item.value }}</p>
            <p class="dataDesc">{{ item.name }}</p>
          </div>
        </el-card>
      </div>
    </el-col>
  </el-row>
</template>
<script>
import {getData} from '../api'
// 引入封装好的请求方法
export default {
  data() {
    return {
      // tableData表格数据
      tableData: [
     
      ],
      // tableLabel根据表格整理表格表格label数据
      tableLabel: {
        name: "技术栈",
        todayBuy: "今日学习人数",
        monthBuy: "本月学习人数",
        totalBuy: "学习总数据"
      },
      // countData 学习数据统计
      countData: [
        {
          name: "今日学习数据",
          value: 1234,
          icon: "s-data",
          color: "deeppink"
        },
        {
          name: "今日收藏文章",
          value: 210,
          icon: "star-on",
          color: "red"
        },
        {
          name: "今日查看数据",
          value: 1234,
          icon: "thumb",
          color: "chartreuse"
        },
        {
          name: "本月学习数据",
          value: 1234,
          icon: "s-data",
          color: "deeppink"
        },
        {
          name: "本月收藏文章",
          value: 210,
          icon: "star-on",
          color: "red"
        },
        {
          name: "本月查看数据",
          value: 1234,
          icon: "thumb",
          color: "chartreuse"
        }
      ]
    };
  },
  mounted(){
    // mounted挂载
    getData().then(res=>{
      // 获取res.data.data.tableData是返回的table数据
      this.tableData=res.data.data.tableData
      console.log(this.tableData)
    })
  }
};
</script>
<style lang="less" scoped>
.user {
  display: flex;
  align-items: center;
  border-bottom: 1px solid #ccc;
  padding-bottom: 20px;
  margin-bottom: 20px;
  img {
    margin-right: 40px;
    width: 150px;
    height: 150px;
    border-radius: 50%;
  }
  .userDetail {
    // 用户详情信息
    .name {
      // 用户名
      font-size: 32px;
      margin-bottom: 10px;
    }
    .access {
      // 角色
      color: #999;
    }
  }
}

.user-info {
  // 登录时间和地点
  p {
    line-height: 28px;
    font-size: 14px;
    color: #999;
    span {
      color: #666;
      margin-left: 60px;
    }
  }
}
// 右侧学习数据
.num {
  display: flex;
  flex-wrap: wrap;  // 强制换行
  justify-content: space-between;

  .icon {
    // 图表
    width: 80px;
    height: 80px;
    font-size: 30px;
    text-align: center;
    line-height: 80px;
    color: white;
  }
  .detail{
    display: flex;
    flex-direction: column;
    justify-content: center;
    margin-left: 15px;
    .dataPrice{
      font-size: 30px;
      margin-bottom: 10px;
      line-height: 30px;
      height: 30px;
    }
    .dataDesc{
      color: #999;
      font-size: 14px;
      text-align: center;
    }
  }
  .el-card{
    // 右侧卡片
    width: 32%;
    margin-bottom: 20px;
  }

}
</style>

效果
在这里插入图片描述

7.2、样式完善和图标表区域布局

左侧区域和右侧区域el-col加上一个padding值

 <el-col :span="8" style="padding-right:10px">
 //左侧的col
 <el-col :span="16"  style="padding-left:10px">
 //右侧的col

图表区域布局

<template>
  <el-row>
    <!-- span是所占的比例 -->
    <el-col :span="8" style="padding-right:10px">
      <el-card class="box-card">
        <div class="user">
          <!-- 用户信息 -->
          <img src="../assets/tx.png" alt />
          <div class="userDetail">
            <p class="name">前端初见</p>
            <p class="access">管理员</p>
          </div>
        </div>
        <div class="user-info">
          <p>
            上次登录时间:
            <span>2023-3-29</span>
          </p>
          <p>
            上次登录地点:
            <span>中山</span>
          </p>
        </div>
      </el-card>
      <el-card style="margin-top: 20px; height:380px" class="box-card">
        <!-- 静态 -->
        <!-- <el-table :data="tableData" style="width: 100%">
          <el-table-column prop="name" label="技术栈"></el-table-column>
          <el-table-column prop="todayBuy" label="今日学习人数" ></el-table-column>
          <el-table-column prop="monthBuy" label="本月学习人数"></el-table-column>
          <el-table-column prop="totalBuy" label="学习总数据"></el-table-column>
        </el-table>-->

        <!-- 遍历生成表格 -->
        <el-table :data="tableData" style="width: 100%">
          <el-table-column v-for="(val, key) in tableLabel" :key="key" :prop="key" :label="val" />
        </el-table>
      </el-card>
    </el-col>
    <el-col :span="16"  style="padding-left:10px">
      <!-- 右侧区域 -->
      <div class="num">
        <el-card :body-style="{display:'flex',padding:0}" v-for="item in countData" :key="item.name">
          <i :style="{background:item.color}" class="icon" :class="`el-icon-${item.icon}`"></i>
          <div class="detail">
            <p class="dataPrice">{{ item.value }}</p>
            <p class="dataDesc">{{ item.name }}</p>
          </div>
        </el-card>
      </div>
      <el-card style="height:280px">
        <!-- 折线图 -->
      </el-card>
      <div class="tbStyle">
        <el-card  style="height:260px;width:48%;">
          <!-- 柱状图 -->
        </el-card>
        <el-card  style="height:260px;width:48%;">
          <!-- 饼图 -->
        </el-card>
      </div>
    </el-col>
  </el-row>
</template>
<script>
import {getData} from '../api'
// 引入封装好的请求方法
export default {
  data() {
    return {
      // tableData表格数据
      tableData: [
     
      ],
      // tableLabel根据表格整理表格表格label数据
      tableLabel: {
        name: "技术栈",
        todayBuy: "今日学习人数",
        monthBuy: "本月学习人数",
        totalBuy: "学习总数据"
      },
      // countData 学习数据统计
      countData: [
        {
          name: "今日学习数据",
          value: 1234,
          icon: "s-data",
          color: "deeppink"
        },
        {
          name: "今日收藏文章",
          value: 210,
          icon: "star-on",
          color: "red"
        },
        {
          name: "今日查看数据",
          value: 1234,
          icon: "thumb",
          color: "chartreuse"
        },
        {
          name: "本月学习数据",
          value: 1234,
          icon: "s-data",
          color: "deeppink"
        },
        {
          name: "本月收藏文章",
          value: 210,
          icon: "star-on",
          color: "red"
        },
        {
          name: "本月查看数据",
          value: 1234,
          icon: "thumb",
          color: "chartreuse"
        }
      ]
    };
  },
  mounted(){
    // mounted挂载
    getData().then(res=>{
      // 获取res.data.data.tableData是返回的table数据
      this.tableData=res.data.data.tableData
      console.log(this.tableData)
    })
  }
};
</script>
<style lang="less" scoped>
.user {
  display: flex;
  align-items: center;
  border-bottom: 1px solid #ccc;
  padding-bottom: 20px;
  margin-bottom: 20px;
  img {
    margin-right: 40px;
    width: 150px;
    height: 150px;
    border-radius: 50%;
  }
  .userDetail {
    // 用户详情信息
    .name {
      // 用户名
      font-size: 32px;
      margin-bottom: 10px;
    }
    .access {
      // 角色
      color: #999;
    }
  }
}

.user-info {
  // 登录时间和地点
  p {
    line-height: 28px;
    font-size: 14px;
    color: #999;
    span {
      color: #666;
      margin-left: 60px;
    }
  }
}
// 右侧学习数据
.num {
  display: flex;
  flex-wrap: wrap;  // 强制换行
  justify-content: space-between;

  .icon {
    // 图表
    width: 80px;
    height: 80px;
    font-size: 30px;
    text-align: center;
    line-height: 80px;
    color: white;
  }
  .detail{
    display: flex;
    flex-direction: column;
    justify-content: center;
    margin-left: 15px;
    .dataPrice{
      font-size: 30px;
      margin-bottom: 10px;
      line-height: 30px;
      height: 30px;
    }
    .dataDesc{
      color: #999;
      font-size: 14px;
      text-align: center;
    }
  }
  .el-card{
    // 右侧卡片
    width: 32%;
    margin-bottom: 20px;
  }

}
.tbStyle{
  // 底部图表容器样式
  margin-top: 20px;
  display: flex;
  justify-content: space-between;
}
</style>

图表区域效果
在这里插入图片描述

7.2.1、Echarts

Echarts图表官网
在这里插入图片描述
安装

npm i echarts@5.1.2

在项目文件夹下进行安装
在这里插入图片描述
安装完成后,接下来就在项目中使用把

7.2.1、折线图部分完善

上面已经对表格区域进行了一些简单的布局,接下来就是对空白区域进行一下图表的补充
Home.vue

<template>
  <el-row>
    <!-- span是所占的比例 -->
    <el-col :span="8" style="padding-right:10px">
      <el-card class="box-card">
        <div class="user">
          <!-- 用户信息 -->
          <img src="../assets/tx.png" alt />
          <div class="userDetail">
            <p class="name">前端初见</p>
            <p class="access">管理员</p>
          </div>
        </div>
        <div class="user-info">
          <p>
            上次登录时间:
            <span>2023-3-29</span>
          </p>
          <p>
            上次登录地点:
            <span>中山</span>
          </p>
        </div>
      </el-card>
      <el-card style="margin-top: 20px; height:380px" class="box-card">
        <!-- 静态 -->
        <!-- <el-table :data="tableData" style="width: 100%">
          <el-table-column prop="name" label="技术栈"></el-table-column>
          <el-table-column prop="todayBuy" label="今日学习人数" ></el-table-column>
          <el-table-column prop="monthBuy" label="本月学习人数"></el-table-column>
          <el-table-column prop="totalBuy" label="学习总数据"></el-table-column>
        </el-table>-->

        <!-- 遍历生成表格 -->
        <el-table :data="tableData" style="width: 100%">
          <el-table-column v-for="(val, key) in tableLabel" :key="key" :prop="key" :label="val" />
        </el-table>
      </el-card>
    </el-col>
    <el-col :span="16" style="padding-left:10px">
      <!-- 右侧区域 -->
      <div class="num">
        <el-card
          :body-style="{display:'flex',padding:0}"
          v-for="item in countData"
          :key="item.name"
        >
          <i :style="{background:item.color}" class="icon" :class="`el-icon-${item.icon}`"></i>
          <div class="detail">
            <p class="dataPrice">{{ item.value }}</p>
            <p class="dataDesc">{{ item.name }}</p>
          </div>
        </el-card>
      </div>
      <el-card style="height:280px">
        <!-- 折线图 -->
        <div ref="echartszx" style="height:280px"></div>
      </el-card>
      <div class="tbStyle">
        <el-card style="height:260px;width:48%;">
          <!-- 柱状图 -->
        </el-card>
        <el-card style="height:260px;width:48%;">
          <!-- 饼图 -->
        </el-card>
      </div>
    </el-col>
  </el-row>
</template>
<script>
import { getData } from "../api";
// 引入封装好的请求方法
import * as echarts from "echarts";
// 引入echarts
export default {
  data() {
    return {
      // tableData表格数据
      tableData: [],
      // orderData折线图数据
      orderData: [],
      // tableLabel根据表格整理表格表格label数据
      tableLabel: {
        name: "技术栈",
        todayBuy: "今日学习人数",
        monthBuy: "本月学习人数",
        totalBuy: "学习总数据"
      },
      // countData 学习数据统计
      countData: [
        {
          name: "今日学习数据",
          value: 1234,
          icon: "s-data",
          color: "deeppink"
        },
        {
          name: "今日收藏文章",
          value: 210,
          icon: "star-on",
          color: "red"
        },
        {
          name: "今日查看数据",
          value: 1234,
          icon: "thumb",
          color: "chartreuse"
        },
        {
          name: "本月学习数据",
          value: 1234,
          icon: "s-data",
          color: "deeppink"
        },
        {
          name: "本月收藏文章",
          value: 210,
          icon: "star-on",
          color: "red"
        },
        {
          name: "本月查看数据",
          value: 1234,
          icon: "thumb",
          color: "chartreuse"
        }
      ]
    };
  },
  mounted() {
    // mounted挂载
    getData().then(res => {
      // 获取res.data.data.tableData是返回的table数据
      this.tableData = res.data.data.tableData;
      // 图表数据
      console.log(this.tableData);
      this.orderData = res.data.data.orderData;
      // orderData折线图数据
      // 基于准备好的dom,初始化echarts实例
      // 获取dom元素,然后初始化echarts
      const echartszx = echarts.init(this.$refs.echartszx);
      // 指定图表的配置和数据
      let zxOption = {};
      // 处理xAxis  x轴数据
      const xAxis = Object.keys(this.orderData.data[0]);

      const xAxisData = {
        data: xAxis
      };
      // x轴
      zxOption.xAxis = xAxisData;
      // y轴
      zxOption.yAxis = {};
      // 图例
      zxOption.legend = xAxisData;
      // y轴对应数据
      zxOption.series = [];
      xAxis.forEach(key => {
        zxOption.series.push({
          name: key,
          data: this.orderData.data.map(item => item[key]),
          type: "line"
        });
      });
      console.log(zxOption);
      // 使用刚指定的配置项和数据显示图表。
      echartszx.setOption(zxOption);
    });
  }
};
</script>
<style lang="less" scoped>
.user {
  display: flex;
  align-items: center;
  border-bottom: 1px solid #ccc;
  padding-bottom: 20px;
  margin-bottom: 20px;
  img {
    margin-right: 40px;
    width: 150px;
    height: 150px;
    border-radius: 50%;
  }
  .userDetail {
    // 用户详情信息
    .name {
      // 用户名
      font-size: 32px;
      margin-bottom: 10px;
    }
    .access {
      // 角色
      color: #999;
    }
  }
}

.user-info {
  // 登录时间和地点
  p {
    line-height: 28px;
    font-size: 14px;
    color: #999;
    span {
      color: #666;
      margin-left: 60px;
    }
  }
}
// 右侧学习数据
.num {
  display: flex;
  flex-wrap: wrap; // 强制换行
  justify-content: space-between;

  .icon {
    // 图表
    width: 80px;
    height: 80px;
    font-size: 30px;
    text-align: center;
    line-height: 80px;
    color: white;
  }
  .detail {
    display: flex;
    flex-direction: column;
    justify-content: center;
    margin-left: 15px;
    .dataPrice {
      font-size: 30px;
      margin-bottom: 10px;
      line-height: 30px;
      height: 30px;
    }
    .dataDesc {
      color: #999;
      font-size: 14px;
      text-align: center;
    }
  }
  .el-card {
    // 右侧卡片
    width: 32%;
    margin-bottom: 20px;
  }
}
.tbStyle {
  // 底部图表容器样式
  margin-top: 20px;
  display: flex;
  justify-content: space-between;
}
</style>

效果
在这里插入图片描述

7.2.2、柱状图部分完善

接下来完成我们主页左下角柱状图部分
Home.vue

<template>
  <el-row>
    <!-- span是所占的比例 -->
    <el-col :span="8" style="padding-right:10px">
      <el-card class="box-card">
        <div class="user">
          <!-- 用户信息 -->
          <img src="../assets/tx.png" alt />
          <div class="userDetail">
            <p class="name">前端初见</p>
            <p class="access">管理员</p>
          </div>
        </div>
        <div class="user-info">
          <p>
            上次登录时间:
            <span>2023-3-29</span>
          </p>
          <p>
            上次登录地点:
            <span>中山</span>
          </p>
        </div>
      </el-card>
      <el-card style="margin-top: 20px; height:380px" class="box-card">
        <!-- 静态 -->
        <!-- <el-table :data="tableData" style="width: 100%">
          <el-table-column prop="name" label="技术栈"></el-table-column>
          <el-table-column prop="todayBuy" label="今日学习人数" ></el-table-column>
          <el-table-column prop="monthBuy" label="本月学习人数"></el-table-column>
          <el-table-column prop="totalBuy" label="学习总数据"></el-table-column>
        </el-table>-->

        <!-- 遍历生成表格 -->
        <el-table :data="tableData" style="width: 100%">
          <el-table-column v-for="(val, key) in tableLabel" :key="key" :prop="key" :label="val" />
        </el-table>
      </el-card>
    </el-col>
    <el-col :span="16" style="padding-left:10px">
      <!-- 右侧区域 -->
      <div class="num">
        <el-card
          :body-style="{display:'flex',padding:0}"
          v-for="item in countData"
          :key="item.name"
        >
          <i :style="{background:item.color}" class="icon" :class="`el-icon-${item.icon}`"></i>
          <div class="detail">
            <p class="dataPrice">{{ item.value }}</p>
            <p class="dataDesc">{{ item.name }}</p>
          </div>
        </el-card>
      </div>
      <el-card style="height:280px">
        <!-- 折线图 -->
        <div ref="echartszx" style="height:280px"></div>
      </el-card>
      <div class="tbStyle">
        <el-card style="height:260px;width:48%;">
          <!-- 柱状图 -->
          <div ref="echartszzt" style="height:260px;"></div>
        </el-card>
        <el-card style="height:260px;width:48%;">
          <!-- 饼图 -->
        </el-card>
      </div>
    </el-col>
  </el-row>
</template>
<script>
import { getData } from "../api";
// 引入封装好的请求方法
import * as echarts from "echarts";
// 引入echarts
export default {
  data() {
    return {
      // tableData表格数据
      tableData: [],
      // orderData折线图数据
      orderData: [],
      // userData用户数据
      userData:[],
      // tableLabel根据表格整理表格表格label数据
      tableLabel: {
        name: "技术栈",
        todayBuy: "今日学习人数",
        monthBuy: "本月学习人数",
        totalBuy: "学习总数据"
      },
      // countData 学习数据统计
      countData: [
        {
          name: "今日学习数据",
          value: 1234,
          icon: "s-data",
          color: "deeppink"
        },
        {
          name: "今日收藏文章",
          value: 210,
          icon: "star-on",
          color: "red"
        },
        {
          name: "今日查看数据",
          value: 1234,
          icon: "thumb",
          color: "chartreuse"
        },
        {
          name: "本月学习数据",
          value: 1234,
          icon: "s-data",
          color: "deeppink"
        },
        {
          name: "本月收藏文章",
          value: 210,
          icon: "star-on",
          color: "red"
        },
        {
          name: "本月查看数据",
          value: 1234,
          icon: "thumb",
          color: "chartreuse"
        }
      ]
    };
  },
  mounted() {
    // mounted挂载
    getData().then(res => {
      // 获取res.data.data.tableData是返回的table数据
      this.tableData = res.data.data.tableData;
      // 图表数据
      console.log(this.tableData);
      this.orderData = res.data.data.orderData;
      // orderData折线图数据
      this.userData=res.data.data.userData
      // 基于准备好的dom,初始化echarts实例
      // 获取dom元素,然后初始化echarts
      const echartszx = echarts.init(this.$refs.echartszx);
      // 指定图表的配置和数据
      let zxOption = {};
      // 处理xAxis  x轴数据
      const xAxis = Object.keys(this.orderData.data[0]);

      const xAxisData = {
        data: xAxis
      };
      // x轴
      zxOption.xAxis = xAxisData;
      // y轴
      zxOption.yAxis = {};
      // 图例
      zxOption.legend = xAxisData;
      // y轴对应数据
      zxOption.series = [];
      xAxis.forEach(key => {
        zxOption.series.push({
          name: key,
          data: this.orderData.data.map(item => item[key]),
          type: "line"
        });
      });
      console.log(zxOption);
      // 使用刚指定的配置项和数据显示图表。
      echartszx.setOption(zxOption);

      // 柱状图
      const echartszzt = echarts.init(this.$refs.echartszzt);
      // 柱状图配置
      let zztOption = {
        legend: {
          // 图例文字颜色
          textStyle: {
            color: "#333"
          }
        },
        grid: {
          left: "20%"
        },
        // 提示框
        tooltip: {
          trigger: "axis"
        },
        xAxis: {
          type: "category", // 类目轴
          data: this.userData.map(item=>item.date),
          axisLine: {
            lineStyle: {
              color: "#17b3a3"
            }
          },
          axisLabel: {
            interval: 0,
            color: "#333"
          }
        },
        yAxis: [
          {
            type: "value",
            axisLine: {
              lineStyle: {
                color: "#17b3a3"
              }
            }
          }
        ],
        color: ["#2ec7c9", "#b6a2de"],
        series: [
          {
            name:'阅读新增用户',
            data:this.userData.map(item=>item.new),
            type:'bar'
          },
          {
            name:'阅读活跃用户',
            data:this.userData.map(item=>item.active),
            type:'bar'
          }
        ]
      };
      echartszzt.setOption(zztOption)
    });
  }
};
</script>
<style lang="less" scoped>
.user {
  display: flex;
  align-items: center;
  border-bottom: 1px solid #ccc;
  padding-bottom: 20px;
  margin-bottom: 20px;
  img {
    margin-right: 40px;
    width: 150px;
    height: 150px;
    border-radius: 50%;
  }
  .userDetail {
    // 用户详情信息
    .name {
      // 用户名
      font-size: 32px;
      margin-bottom: 10px;
    }
    .access {
      // 角色
      color: #999;
    }
  }
}

.user-info {
  // 登录时间和地点
  p {
    line-height: 28px;
    font-size: 14px;
    color: #999;
    span {
      color: #666;
      margin-left: 60px;
    }
  }
}
// 右侧学习数据
.num {
  display: flex;
  flex-wrap: wrap; // 强制换行
  justify-content: space-between;

  .icon {
    // 图表
    width: 80px;
    height: 80px;
    font-size: 30px;
    text-align: center;
    line-height: 80px;
    color: white;
  }
  .detail {
    display: flex;
    flex-direction: column;
    justify-content: center;
    margin-left: 15px;
    .dataPrice {
      font-size: 30px;
      margin-bottom: 10px;
      line-height: 30px;
      height: 30px;
    }
    .dataDesc {
      color: #999;
      font-size: 14px;
      text-align: center;
    }
  }
  .el-card {
    // 右侧卡片
    width: 32%;
    margin-bottom: 20px;
  }
}
.tbStyle {
  // 底部图表容器样式
  margin-top: 20px;
  display: flex;
  justify-content: space-between;
}
</style>

效果
在这里插入图片描述

7.2.3、饼图部分完善

接下来完成图表的最后一部分饼状图,相对于折线图和柱状图来说简单一些。
Home.vue

<template>
  <el-row>
    <!-- span是所占的比例 -->
    <el-col :span="8" style="padding-right:10px">
      <el-card class="box-card">
        <div class="user">
          <!-- 用户信息 -->
          <img src="../assets/tx.png" alt />
          <div class="userDetail">
            <p class="name">前端初见</p>
            <p class="access">管理员</p>
          </div>
        </div>
        <div class="user-info">
          <p>
            上次登录时间:
            <span>2023-3-29</span>
          </p>
          <p>
            上次登录地点:
            <span>中山</span>
          </p>
        </div>
      </el-card>
      <el-card style="margin-top: 20px; height:380px" class="box-card">
        <!-- 静态 -->
        <!-- <el-table :data="tableData" style="width: 100%">
          <el-table-column prop="name" label="技术栈"></el-table-column>
          <el-table-column prop="todayBuy" label="今日学习人数" ></el-table-column>
          <el-table-column prop="monthBuy" label="本月学习人数"></el-table-column>
          <el-table-column prop="totalBuy" label="学习总数据"></el-table-column>
        </el-table>-->

        <!-- 遍历生成表格 -->
        <el-table :data="tableData" style="width: 100%">
          <el-table-column v-for="(val, key) in tableLabel" :key="key" :prop="key" :label="val" />
        </el-table>
      </el-card>
    </el-col>
    <el-col :span="16" style="padding-left:10px">
      <!-- 右侧区域 -->
      <div class="num">
        <el-card
          :body-style="{display:'flex',padding:0}"
          v-for="item in countData"
          :key="item.name"
        >
          <i :style="{background:item.color}" class="icon" :class="`el-icon-${item.icon}`"></i>
          <div class="detail">
            <p class="dataPrice">{{ item.value }}</p>
            <p class="dataDesc">{{ item.name }}</p>
          </div>
        </el-card>
      </div>
      <el-card style="height:280px">
        <!-- 折线图 -->
        <div ref="echartszx" style="height:280px"></div>
      </el-card>
      <div class="tbStyle">
        <el-card style="height:260px;width:48%;">
          <!-- 柱状图 -->
          <div ref="echartszzt" style="height:260px;"></div>
        </el-card>
        <el-card style="height:260px;width:48%;">
          <!-- 饼图 -->
          <div ref="echartsbt" style="height:250px;"></div>
        </el-card>
      </div>
    </el-col>
  </el-row>
</template>
<script>
import { getData } from "../api";
// 引入封装好的请求方法
import * as echarts from "echarts";
// 引入echarts
export default {
  data() {
    return {
      // tableData表格数据
      tableData: [],
      // orderData折线图数据
      orderData: [],
      // userData用户数据
      userData: [],
      // videoData视频数据
      videoData: [],
      // tableLabel根据表格整理表格表格label数据
      tableLabel: {
        name: "技术栈",
        todayBuy: "今日学习人数",
        monthBuy: "本月学习人数",
        totalBuy: "学习总数据"
      },

      // countData 学习数据统计
      countData: [
        {
          name: "今日学习数据",
          value: 1234,
          icon: "s-data",
          color: "deeppink"
        },
        {
          name: "今日收藏文章",
          value: 210,
          icon: "star-on",
          color: "red"
        },
        {
          name: "今日查看数据",
          value: 1234,
          icon: "thumb",
          color: "chartreuse"
        },
        {
          name: "本月学习数据",
          value: 1234,
          icon: "s-data",
          color: "deeppink"
        },
        {
          name: "本月收藏文章",
          value: 210,
          icon: "star-on",
          color: "red"
        },
        {
          name: "本月查看数据",
          value: 1234,
          icon: "thumb",
          color: "chartreuse"
        }
      ]
    };
  },
  mounted() {
    // mounted挂载
    getData().then(res => {
      // 获取res.data.data.tableData是返回的table数据
      this.tableData = res.data.data.tableData;
      // 图表数据
      console.log(this.tableData);
      this.orderData = res.data.data.orderData;
      // orderData折线图数据
      this.userData = res.data.data.userData;
      // userData 柱状图数据
      this.videoData = res.data.data.videoData;
      // videoData 饼图数据
      // 基于准备好的dom,初始化echarts实例
      // 获取dom元素,然后初始化echarts
      const echartszx = echarts.init(this.$refs.echartszx);
      // 指定图表的配置和数据
      let zxOption = {};
      // 处理xAxis  x轴数据
      const xAxis = Object.keys(this.orderData.data[0]);

      const xAxisData = {
        data: xAxis
      };
      // x轴
      zxOption.xAxis = xAxisData;
      // y轴
      zxOption.yAxis = {};
      // 图例
      zxOption.legend = xAxisData;
      // y轴对应数据
      zxOption.series = [];
      xAxis.forEach(key => {
        zxOption.series.push({
          name: key,
          data: this.orderData.data.map(item => item[key]),
          type: "line"
        });
      });
      console.log(zxOption);
      // 使用刚指定的配置项和数据显示图表。
      echartszx.setOption(zxOption);

      // 柱状图
      const echartszzt = echarts.init(this.$refs.echartszzt);
      // 柱状图配置
      let zztOption = {
        legend: {
          // 图例文字颜色
          textStyle: {
            color: "#333"
          }
        },
        grid: {
          left: "20%"
        },
        // 提示框
        tooltip: {
          trigger: "axis"
        },
        xAxis: {
          type: "category", // 类目轴
          data: this.userData.map(item => item.date),
          axisLine: {
            lineStyle: {
              color: "#17b3a3"
            }
          },
          axisLabel: {
            interval: 0,
            color: "#333"
          }
        },
        yAxis: [
          {
            type: "value",
            axisLine: {
              lineStyle: {
                color: "#17b3a3"
              }
            }
          }
        ],
        color: ["#2ec7c9", "#b6a2de"],
        series: [
          {
            name: "阅读新增用户",
            data: this.userData.map(item => item.new),
            type: "bar"
          },
          {
            name: "阅读活跃用户",
            data: this.userData.map(item => item.active),
            type: "bar"
          }
        ]
      };
      echartszzt.setOption(zztOption);

      // 饼图
      const echartsbt = echarts.init(this.$refs.echartsbt);
      const btOption = {
        tooltip: {
          trigger: "item"
        },
        color: [
          "#0f78f4",
          "#dd536b",
          "#9462e5",
          "#a6a6a6",
          "#e1bb22",
          "#39c362",
          "#3ed1cf"
        ],
        series: [{ data: this.videoData, type: "pie" }]
      };
      echartsbt.setOption(btOption)
    });
  }
};
</script>
<style lang="less" scoped>
.user {
  display: flex;
  align-items: center;
  border-bottom: 1px solid #ccc;
  padding-bottom: 20px;
  margin-bottom: 20px;
  img {
    margin-right: 40px;
    width: 150px;
    height: 150px;
    border-radius: 50%;
  }
  .userDetail {
    // 用户详情信息
    .name {
      // 用户名
      font-size: 32px;
      margin-bottom: 10px;
    }
    .access {
      // 角色
      color: #999;
    }
  }
}

.user-info {
  // 登录时间和地点
  p {
    line-height: 28px;
    font-size: 14px;
    color: #999;
    span {
      color: #666;
      margin-left: 60px;
    }
  }
}
// 右侧学习数据
.num {
  display: flex;
  flex-wrap: wrap; // 强制换行
  justify-content: space-between;

  .icon {
    // 图表
    width: 80px;
    height: 80px;
    font-size: 30px;
    text-align: center;
    line-height: 80px;
    color: white;
  }
  .detail {
    display: flex;
    flex-direction: column;
    justify-content: center;
    margin-left: 15px;
    .dataPrice {
      font-size: 30px;
      margin-bottom: 10px;
      line-height: 30px;
      height: 30px;
    }
    .dataDesc {
      color: #999;
      font-size: 14px;
      text-align: center;
    }
  }
  .el-card {
    // 右侧卡片
    width: 32%;
    margin-bottom: 20px;
  }
}
.tbStyle {
  // 底部图表容器样式
  margin-top: 20px;
  display: flex;
  justify-content: space-between;
}
</style>

效果
在这里插入图片描述

8、通用管理后台(面包屑和tag部分)

经过我们的完善,首页还剩顶部的一个面包屑功能待完善,接下来就完善这部分。之前我们在CommonHeader.vue写了一个静态的首页文字,接下来进行修改成我们真正的面包屑

总结

在这里插入图片描述

持续更新中

如果这篇【文章】有帮助到你💖,希望可以给我点个赞👍,创作不易,如果有对前端端或者对python感兴趣的朋友,请多多关注💖💖💖,咱们一起探讨和努力!!!
👨‍🔧 个人主页 : 前端初见

  • 1
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端初见

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值