项目简介
实现功能
用VUE框架搭建一个通用后台管理系统,组件化、模块化实现用户登陆、后台管理、页面显示和跳转等功能。

技术栈

参考文档
vue通用后台管理系统(一)
vue通用后台管理系统(二)
vue通用后台管理系统(三)
vue通用后台管理系统(四)
实现流程
环境配置
Vue安装及环境配置、开发工具_vue开发工具
创建项目
查看环境配置

创建项目
创建:vue create my-app




更改命名规范设置
![![[屏幕截图 2023-12-04 123016.png]]](https://www.qianduange.cn/upload/article/136683f44ec449729e8998a90271e963.png)
导入Element-UI
官网网址
Element - 网站快速成型工具
安装
注意版本的兼容性,这里选择安装2.15.13版本
![![[Pasted image 20231204112854.png]]](https://www.qianduange.cn/upload/article/0b6318d2fd43417bbd2ffe54b2cad225.png)
导入
| |
| import ElementUI from 'element-ui' |
| import 'element-ui/lib/theme-chalk/index.css'; |
| |
| Vue.use(ElementUI) |
复制
![![[Pasted image 20231204113857.png]]](https://www.qianduange.cn/upload/article/026b455b90d549c483243f997074b5f0.png)
测试
![![[Pasted image 20231204114432.png]]](https://www.qianduange.cn/upload/article/0b38888f34da420a98d5eaa4fc16a136.png)
![![[Pasted image 20231204114532.png]]](https://www.qianduange.cn/upload/article/2840e843e62d418b987b39c80a769927.png)
less
cnpm i less@4.1.2 npm i less-loader@6.0.0
![![[Pasted image 20231204173305.png]]](https://www.qianduange.cn/upload/article/cb3cbd09c3124e39a4e9bc956def8a92.png)
Vuex
安装:cnpm i vuex@3.6.2
Anxios
安装:cnpm install axios@0.27.2
Echarts
安装:cnpm i echarts@5.1.2
Vue-Router
官网
Vue Router
安装
cnpm i vue-router@3.6.5
![![[Pasted image 20231204115258.png]]](https://www.qianduange.cn/upload/article/3b9a417b6c334b87afc7b685a65550f1.png)
测试
创建相关文件目录文件和组件
![![[屏幕截图 2023-12-04 145310 1.png]]](https://www.qianduange.cn/upload/article/b1bc7bedd6be428c92764bb1a73ac976.png)
router文件下的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) |
| |
| const routes = [ |
| { path: '/home', component: Home }, |
| { path: '/user', component: User } |
| ] |
| |
| const router = new VueRouter({ |
| routes, |
| }) |
| |
| export default router |
复制
在main.js中进行导入和挂载
![[屏幕截图 2023-12-04 145945.png]]
| |
| import router from './router' |
| |
| new Vue({ |
| |
| router, |
| render: h => h(App), |
| }).$mount('#app') |
复制
在需要渲染的地方(App.vue)添加代码
<router-view></router-view>
嵌套路由
创建Main组件并在router的index.js文件中修改路由
![![[屏幕截图 2023-12-04 151351.png]]](https://www.qianduange.cn/upload/article/2972fc1376c24172b9611d4639a49d44.png)
| import Main from '../views/Main.vue' |
| |
| const routes = [ |
| { |
| path: '/', |
| component: Main, |
| children: [ |
| { path: 'home', component: Home }, |
| { path: 'user', component: User } |
| ] |
| } |
| ] |
复制
在Main组件中设置路由出口
![![[Pasted image 20231204152333.png]]](https://www.qianduange.cn/upload/article/397b0b3e4ee143e9bf139064ad85729b.png)
Container布局容器
结构显示框图
![![[Pasted image 20231204153631.png]]](https://www.qianduange.cn/upload/article/68982905bb1847c281fb74d1e254ecbe.png)
Main.vue代码
![![[屏幕截图 2023-12-04 153743 1.png]]](https://www.qianduange.cn/upload/article/3f932513ab3a4af38c42c1fe052664ba.png)
| <el-container> |
| <el-aside width="200px">Aside</el-aside> |
| <el-container> |
| <el-header>Header</el-header> |
| <el-main> |
| <router-view></router-view> |
| </el-main> |
| </el-container> |
| </el-container> |
复制
左侧菜单栏的引入
创建CommonAside组件
将ElementUI中的导航菜单导入进来,注意一个容器内只允许容纳一个ElementUI标签。
在Main组件中导入并渲染
![![[屏幕截图 2023-12-04 162002.png]]](https://www.qianduange.cn/upload/article/9ba09766964c4d2485f6f6a05e906b72.png)
根据menuData对菜单栏进行调整
在CommonAside组件中导入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' |
| } |
| ] |
| } |
| ] |
复制
计算属性分类menuData
| computed: { |
| |
| noChildren() { |
| return this.menuData.filter(item => !item.children) |
| }, |
| |
| hasChildren() { |
| return this.menuData.filter(item => item.children) |
| }, |
| } |
复制
对menuData中的对象进行遍历的代码
| <template> |
| <el-menu |
| default-active="1-4-1" |
| class="el-menu-vertical-demo" |
| @open="handleOpen" |
| @close="handleClose" |
| :collapse="isCollapse" |
| background-color="#454545" |
| text-color="#ffffff" |
| active-text-color="#00ffff"> |
| |
| <el-menu-item v-for="item in noChildren" :key="item.name" :index="item.name"> |
| <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">{{ subItem.label }}</el-menu-item> |
| </el-menu-item-group> |
| </el-submenu> |
| </el-menu> |
| </template> |
复制
样式调整
CommonAside.vue
| <style lang="less" scoped> |
| .el-menu-vertical-demo:not(.el-menu--collapse) { |
| width: 200px; |
| min-height: 400px; |
| } |
| .el-menu{ |
| border-right: none; |
| height: 100vh; |
| |
| h3 { |
| color: #fff; |
| text-align: center; |
| line-height: 48px; |
| font-size: 16px; |
| font-weight: 400; |
| } |
| } |
| </style> |
复制
Main.vue
| <style lang="less" scoped> |
| el-header{ |
| padding: 0; |
| } |
| </style> |
复制
App.vue
| <!-- 清除边框 --> |
| <style lang="less"> |
| html, body, h3{ |
| margin:0px; |
| padding: 0px; |
| } |
| </style> |
复制
效果
![[Pasted image 20231204175502.png]]
实现点击菜单栏进行路由跳转
![[屏幕截图 2023-12-04 203236.png]]
在mothods中添加方法
| clickMenu(item){ |
| |
| if(this.$route.path !== item.path && !(this.$route.path === '/home' && item.path == '/')){ |
| this.$router.push(item.path); |
| } |
| } |
复制
创建CommonHeader组件并导入
![[屏幕截图 2023-12-04 204327.png]]
CommonHeader模板代码及其基本样式
| <template> |
| <div class="headerContainer"> |
| <div class="l-content"> |
| <el-button style="margin-right:20px" icon="el-icon-menu" size="mini"></el-button> |
| <el-breadcrumb separator="/"> |
| <el-breadcrumb-item>首页</el-breadcrumb-item> |
| </el-breadcrumb> |
| </div> |
| <div class="r-content"> |
| <el-dropdown> |
| <span class="el-dropdown-link"> |
| <img class="user" src="../assets/images/user.png" alt=""> |
| </span> |
| <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> |
| </script> |
| |
| <style lang="less" scoped> |
| .headerContainer{ |
| padding: 0 20px; |
| background-color: #333; |
| height: 60px; |
| display: flex; |
| justify-content: space-between;//左右分离 |
| align-items: center;//垂直居中 |
| .l-content{ |
| display: flex; |
| align-items: center; |
| /deep/.el-breadcrumb__item { |
| .el-breadcrumb__inner { |
| font-weight: normal; |
| &.is-link{ |
| color: #999 |
| } |
| } |
| &:last-child { |
| .el-breadcrumb__inner { |
| color: #FFF |
| } |
| } |
| } |
| } |
| .r-content{ |
| .user { |
| width: 40px; |
| height: 40px; |
| border-radius: 50%; |
| } |
| } |
| .text { |
| color: #fff; |
| font-size: 14px; |
| margin-left: 10px; |
| } |
| } |
| </style> |
复制
组件通信-vuex
创建store目录及其index.js和tab.js文件
![![[Pasted image 20231204222333.png]]](https://www.qianduange.cn/upload/article/35a37b30f4fd4961b235b08770c5ac26.png)
index.js代码
| import Vue from 'vue' |
| import Vuex from 'vuex' |
| import tab from './tab' |
| Vue.use(Vuex) |
| export default new Vuex.Store({ |
| modules:{ |
| tab, |
| } |
| }) |
复制
tab.js代码
| export default { |
| state:{ |
| }, |
| mutations: { |
| } |
| } |
复制
导入并挂载store
![![[屏幕截图 2023-12-04 222458.png]]](https://www.qianduange.cn/upload/article/c080e1413fc8428497f7b1b5fee62a3e.png)
组件通信方式实现菜单栏的展开折叠
vuex部分
![![[屏幕截图 2023-12-04 225146.png]]](https://www.qianduange.cn/upload/article/d263c4afb4f94c7a9101522b698699b8.png)
CommonHeader组件部分
![![[屏幕截图 2023-12-04 225424.png]]](https://www.qianduange.cn/upload/article/910cf981965249fb965f50167a1490a3.png)
![![[屏幕截图 2023-12-04 225542.png]]](https://www.qianduange.cn/upload/article/1f7ef4ea94344932ae10d8046bbb36cc.png)
CommonAside部分
![![[Pasted image 20231204225755.png]]](https://www.qianduange.cn/upload/article/d438f167700e4b1aa188f23793b26b59.png)
![![[屏幕截图 2023-12-04 225824.png]]](https://www.qianduange.cn/upload/article/bb9b2605c343482d9190aaa5891ab761.png)
系统名在折叠时的显示
<h3>{{ isCollapse ? '后台':'通用后台管理系统' }}</h3>
首页
Layout布局
| <template> |
| <el-row> |
| <el-col :span="8"> |
| <div ></div> |
| </el-col> |
| <el-col :span="16"> |
| <div ></div> |
| </el-col> |
| </el-row> |
| </template> |
复制
Home组件基本模板及左上角名片代码
| <template> |
| <el-row> |
| <el-col :span="8"> |
| <el-card class="box-card"> |
| <div class="user"> |
| <img src="../assets/images/user.png" alt=""> |
| <div class="userinfo"> |
| <p class="name">Admin</p> |
| <p class="access">管理员</p> |
| <p></p> |
| </div> |
| </div> |
| <div class="login-info"> |
| <p>上次登陆地点: <span>广东 珠海</span></p> |
| <p>上次登陆时间: <span>20231224</span></p> |
| </div> |
| </el-card> |
| <el-zard style="margin-top: 20px; height: 460px"></el-card |
| </el-col> |
| <el-col :span="16"> |
| <div ></div> |
| </el-col> |
| </el-row> |
| </template> |
| <script> |
| export default { |
| name: 'Home', |
| data() { |
| return { |
| } |
| } |
| } |
| </script> |
| <style lang="less" scoped> |
| .user{ |
| display: flex; |
| align-items: center; |
| padding-bottom: 20px; |
| margin-bottom: 20px; |
| img{ |
| margin-right: 40px; |
| width: 150px; |
| height: 150px; |
| border-radius: 50%; |
| } |
| .userinfo { |
| .name { |
| font-size: 32px; |
| margin-bottom: 10px; |
| } |
| .access{ |
| color: #999999; |
| } |
| } |
| } |
| .login-info { |
| p { |
| line-height: 28px; |
| font-size: 14px; |
| color:#999999; |
| span { |
| color: #666; |
| margin-left: 60px; |
| } |
| } |
| } |
| </style> |
| |
复制
右侧订单的实现
countData数据
| countData: [ |
| { |
| name: "今日支付订单", |
| value: 1234, |
| icon: "success", |
| color: "#2ec7c9", |
| }, |
| { |
| name: "今日收藏订单", |
| value: 210, |
| icon: "star-on", |
| color: "#ffb980", |
| }, |
| { |
| name: "今日未支付订单", |
| value: 1234, |
| icon: "s-goods", |
| color: "#5ab1ef", |
| }, |
| { |
| name: "本月支付订单", |
| value: 1234, |
| icon: "success", |
| color: "#2ec7c9", |
| }, |
| { |
| name: "本月收藏订单", |
| value: 210, |
| icon: "star-on", |
| color: "#ffb980", |
| }, |
| { |
| name: "本月未支付订单", |
| value: 1234, |
| icon: "s-goods", |
| color: "#5ab1ef", |
| }, |
| ], |
复制
代码实现
| <div class="num"> |
| <el-card v-for="item in countData" :key="item.name" :body-style="{ display: 'flex', padding: 0 }"> |
| <i class="icon" :class="`el-icon-${item.icon}`" :style="{ background: item.color }"></i> |
| <div class="detail"> |
| <p class="price">¥{{ item.value }}</p> |
| <p class="desc">{{ item.name }}</p> |
| </div> |
| </el-card> |
| </div> |
复制
Axios
创建utils目录及其request.js文件
| import axios from 'axios' |
| const http = axios.create({ |
| baseURL: '/api', |
| timeout: 10000, |
| }) |
| |
| 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 |
复制
创建api目录及其index.js文件
| |
| import http from '../utils/request' |
| |
| export const getData = () => { |
| |
| return http.get('/home/getData') |
| } |
复制
Mock数据模拟
安装
cnpm i mockjs@1.1.0
配置后端数据模拟
![![[Pasted image 20231205092011.png]]](https://www.qianduange.cn/upload/article/63a3b5630690496aadbc82bffd11f681.png)
mock.js代码
| import Mock from 'mockjs' |
| import homeApi from './mockServeData/home' |
| import user from './mockServeData/user' |
| import permission from './mockServeData/permission' |
| |
| Mock.mock('/api/home/getData', homeApi.getStatisticalData); |
| |
| |
| Mock.mock('/api/user/add', 'post', user.createUser); |
| Mock.mock('/api/user/edit', 'post', user.updateUser); |
| Mock.mock('/api/user/del', 'post', user.deleteUser); |
| Mock.mock(/api\/user\/get/, user.getUserList); |
| Mock.mock(/api\/permission\/getMenu/, 'post', permission.getMenu) |
复制
在main.js文件中引入
import './api/mock'
home.js代码
| |
| import Mock from 'mockjs' |
| |
| let List = [] |
| export default { |
| getStatisticalData: () => |
| |
| 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 |
| } |
| ] |
| } |
| } |
| } |
| } |
复制
permission.js代码
| import Mock from 'mockjs' |
| export default { |
| getMenu: config => { |
| const { username, password } = JSON.parse(config.body) |
| |
| |
| if (username === 'admin' && password === 'admin') { |
| return { |
| code: 20000, |
| data: { |
| menu: [ |
| { |
| path: '/home', |
| name: 'home', |
| label: '首页', |
| icon: 's-home', |
| url: 'Home.vue' |
| }, |
| { |
| path: '/mall', |
| name: 'mall', |
| label: '商品管理', |
| icon: 'video-play', |
| url: 'Mall.vue' |
| }, |
| { |
| path: '/user', |
| name: 'user', |
| label: '用户管理', |
| icon: 'user', |
| url: 'User.vue' |
| }, |
| { |
| label: '其他', |
| icon: 'location', |
| children: [ |
| { |
| path: '/page1', |
| name: 'page1', |
| label: '页面1', |
| icon: 'setting', |
| url: 'PageOne.vue' |
| }, |
| { |
| path: '/page2', |
| name: 'page2', |
| label: '页面2', |
| icon: 'setting', |
| url: 'PageTwo.vue' |
| } |
| ] |
| } |
| ], |
| token: Mock.Random.guid(), |
| message: '获取成功' |
| } |
| } |
| } else if (username === 'xiaoxiao' && password === 'xiaoxiao') { |
| return { |
| code: 20000, |
| data: { |
| menu: [ |
| { |
| path: '/home', |
| name: 'home', |
| label: '首页', |
| icon: 's-home', |
| url: 'Home.vue' |
| }, |
| { |
| path: '/video', |
| name: 'video', |
| label: '商品管理', |
| icon: 'video-play', |
| url: 'Mall.vue' |
| } |
| ], |
| token: Mock.Random.guid(), |
| message: '获取成功' |
| } |
| } |
| } else { |
| return { |
| code: -999, |
| data: { |
| message: '密码错误' |
| } |
| } |
| } |
| } |
| } |
复制
user.js代码
| import Mock from 'mockjs' |
| |
| function param2Obj (url) { |
| const search = url.split('?')[1] |
| if (!search) { |
| return {} |
| } |
| return JSON.parse( |
| '{"' + |
| decodeURIComponent(search) |
| .replace(/"/g, '\\"') |
| .replace(/&/g, '","') |
| .replace(/=/g, '":"') + |
| '"}' |
| ) |
| } |
| let List = [] |
| const count = 200 |
| for (let i = 0; i < count; i++) { |
| List.push( |
| Mock.mock({ |
| id: Mock.Random.guid(), |
| name: Mock.Random.cname(), |
| addr: Mock.mock('@county(true)'), |
| 'age|18-60': 1, |
| birth: Mock.Random.date(), |
| sex: Mock.Random.integer(0, 1) |
| }) |
| ) |
| } |
| export default { |
| |
| |
| |
| |
| |
| |
| getUserList: config => { |
| const { name, page = 1, limit = 20 } = param2Obj(config.url) |
| console.log('name:' + name, 'page:' + page, '分页大小limit:' + limit) |
| const mockList = List.filter(user => { |
| if (name && user.name.indexOf(name) === -1 && user.addr.indexOf(name) === -1) return false |
| return true |
| }) |
| const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1)) |
| return { |
| code: 20000, |
| count: mockList.length, |
| list: pageList |
| } |
| }, |
| |
| |
| |
| |
| |
| createUser: config => { |
| const { name, addr, age, birth, sex } = JSON.parse(config.body) |
| console.log(JSON.parse(config.body)) |
| List.unshift({ |
| id: Mock.Random.guid(), |
| name: name, |
| addr: addr, |
| age: age, |
| birth: birth, |
| sex: sex |
| }) |
| return { |
| code: 20000, |
| data: { |
| message: '添加成功' |
| } |
| } |
| }, |
| |
| |
| |
| |
| |
| deleteUser: config => { |
| const { id } = JSON.parse(config.body) |
| if (!id) { |
| return { |
| code: -999, |
| message: '参数不正确' |
| } |
| } else { |
| List = List.filter(u => u.id !== id) |
| return { |
| code: 20000, |
| message: '删除成功' |
| } |
| } |
| }, |
| |
| |
| |
| |
| |
| batchremove: config => { |
| let { ids } = param2Obj(config.url) |
| ids = ids.split(',') |
| List = List.filter(u => !ids.includes(u.id)) |
| return { |
| code: 20000, |
| data: { |
| message: '批量删除成功' |
| } |
| } |
| }, |
| |
| |
| |
| |
| |
| updateUser: config => { |
| const { id, name, addr, age, birth, sex } = JSON.parse(config.body) |
| const sex_num = parseInt(sex) |
| List.some(u => { |
| if (u.id === id) { |
| u.name = name |
| u.addr = addr |
| u.age = age |
| u.birth = birth |
| u.sex = sex_num |
| return true |
| } |
| }) |
| return { |
| code: 20000, |
| data: { |
| message: '编辑成功' |
| } |
| } |
| } |
| } |
复制
Home.vue代码
| <template> |
| <el-row> |
| <el-col :span="8" style="padding-right: 10px"> |
| <el-card class="box-card"> |
| <div class="user"> |
| <img src="../assets/images/user.png" alt=""> |
| <div class="userinfo"> |
| <p class="name">Admin</p> |
| <p class="access">SuperAdmin</p> |
| </div> |
| </div> |
| <div class="login-info"> |
| <p>上次登录时间:<span>20231123</span></p> |
| <p>上次登录地点:<span>珠海</span></p> |
| </div> |
| </el-card> |
| <el-card style="margin-top:20px; height:460px"> |
| <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 v-for="item in countData" :key="item.name" :body-style="{ display: 'flex', padding: 0 }"> |
| <i class="icon" :class="`el-icon-${item.icon}`" :style="{ background: item.color }"></i> |
| <div class="detail"> |
| <p class="price">¥{{ item.value }}</p> |
| <p class="desc">{{ item.name }}</p> |
| </div> |
| </el-card> |
| </div> |
| <el-card style="height: 280px"> |
| |
| <div ref="echarts1" style="height: 280px;"></div> |
| </el-card> |
| <div class="graph"> |
| <el-card style="height: 260px"> |
| <div ref="echarts2" style="height: 260px;"></div> |
| </el-card> |
| <el-card style="height: 260px"> |
| <div ref="echarts3" style="height: 240px;"></div> |
| </el-card> |
| </div> |
| </el-col> |
| </el-row> |
| </template> |
| |
| <script> |
| import { getData } from '../api' |
| import * as echarts from 'echarts' |
| import { toRefs } from 'vue'; |
| import { ref } from 'vue'; |
| import { onMounted } from 'vue'; |
| export default { |
| data() { |
| return { |
| tableData: [], |
| tableLabel: { |
| name: '课程', |
| todayBuy: '今日购买', |
| monthBuy: '本月购买', |
| totalBuy: '总购买' |
| }, |
| countData: [ |
| { |
| name: "今日支付订单", |
| value: 1234, |
| icon: "success", |
| color: "#2ec7c9", |
| }, |
| { |
| name: "今日收藏订单", |
| value: 210, |
| icon: "star-on", |
| color: "#ffb980", |
| }, |
| { |
| name: "今日未支付订单", |
| value: 1234, |
| icon: "s-goods", |
| color: "#5ab1ef", |
| }, |
| { |
| name: "本月支付订单", |
| value: 1234, |
| icon: "success", |
| color: "#2ec7c9", |
| }, |
| { |
| name: "本月收藏订单", |
| value: 210, |
| icon: "star-on", |
| color: "#ffb980", |
| }, |
| { |
| name: "本月未支付订单", |
| value: 1234, |
| icon: "s-goods", |
| color: "#5ab1ef", |
| }, |
| ], |
| } |
| }, |
| mounted() { |
| getData().then(({ data }) => { |
| const { tableData } = data.data; |
| this.tableData = tableData; |
| |
| const echarts1 = echarts.init(this.$refs.echarts1) |
| |
| var echarts1option = {} |
| |
| const { orderData, userData, videoData } = data.data |
| const xAxis = Object.keys(orderData.data[0]) |
| const xAxisData = { data: xAxis } |
| echarts1option.xAxis = xAxisData; |
| echarts1option.yAxis = {} |
| echarts1option.legend = xAxisData; |
| echarts1option.series = [] |
| |
| xAxis.forEach(key => { |
| echarts1option.series.push({ |
| name: key, |
| data: orderData.data.map(item => item[key]), |
| type: 'line', |
| }) |
| }) |
| |
| echarts1.setOption(echarts1option) |
| |
| const echarts2 = echarts.init(this.$refs.echarts2) |
| const echarts2option = { |
| legend: { |
| |
| textStyle: { |
| color: "#333", |
| }, |
| }, |
| grid: { |
| left: "20%", |
| }, |
| |
| tooltip: { |
| trigger: "axis", |
| }, |
| xAxis: { |
| type: "category", |
| data: 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: userData.map(item => item.new), |
| type: 'bar' |
| }, |
| { |
| name: '活跃用户', |
| data: userData.map(item => item.active), |
| type: 'bar' |
| } |
| ], |
| } |
| |
| echarts2.setOption(echarts2option) |
| |
| const echarts3 = echarts.init(this.$refs.echarts3) |
| const echarts3option = { |
| tooltip: { |
| trigger: "item", |
| }, |
| color: [ |
| "#0f78f4", |
| "#dd536b", |
| "#9462e5", |
| "#a6a6a6", |
| "#e1bb22", |
| "#39c362", |
| "#3ed1cf", |
| ], |
| series: [ |
| { |
| data: videoData, |
| type: 'pie', |
| }, |
| ], |
| } |
| echarts3.setOption(echarts3option); |
| }) |
| } |
| } |
| </script> |
| <style lang="less" scoped> |
| .user { |
| padding-bottom: 20px; |
| margin-bottom: 20px; |
| border-bottom: 1px solid #ccc; |
| display: flex; |
| align-items: center; |
| img { |
| margin-right: 40px; |
| width: 150px; |
| height: 150px; |
| border-radius: 50%; |
| } |
| .userinfo { |
| .name { |
| font-size: 32px; |
| margin-bottom: 10px; |
| } |
| .access { |
| color: #999999; |
| } |
| } |
| } |
| .login-info { |
| p { |
| line-height: 28px; |
| font-size: 14px; |
| color: #999999; |
| span { |
| color: #666666; |
| margin-left: 60px; |
| } |
| } |
| } |
| .num { |
| display: flex; |
| flex-wrap: wrap; |
| justify-content: space-between; |
| .icon { |
| width: 80px; |
| height: 80px; |
| font-size: 30px; |
| color: #fff; |
| text-align: center; |
| line-height: 80px; |
| } |
| .detail { |
| margin-left: 15px; |
| display: flex; |
| flex-direction: column; |
| justify-content: center; |
| .price { |
| padding: none; |
| font-size: 30px; |
| margin-top: 10px; |
| margin-bottom: 10px; |
| line-height: 30px; |
| height: 30px; |
| } |
| .desc { |
| font-size: 10px; |
| color: #999; |
| text-align: center; |
| } |
| } |
| .el-card { |
| width: 32%; |
| margin-bottom: 20px; |
| } |
| } |
| .graph { |
| margin-top: 20px; |
| display: flex; |
| justify-content: space-between; |
| .el-card { |
| width: 48% |
| } |
| } |
| </style> |
复制