一、前言
在现代Web开发中,前端框架与数据可视化工具的结合能够显著提升用户体验。本文将介绍如何使用Vue2和ECharts构建一个通用的后台管理系统页面。利用Vue2的组件化特性,可以高效管理应用状态与UI交互,而ECharts则提供多样的图表类型,便于展示数据分析结果。通过整合这两者,开发者能够快速构建出既美观又功能强大的后台管理系统,实现数据的动态展示与实时更新,从而帮助用户做出更明智的决策。
二、技术栈
Vue2+Vuex+Echarts+Element-ui+Axios+Mock.js
Vue.js:前端框架,用于构建用户界面。Vue Router:用于页面路由管理。Vuex:状态管理库,用于管理应用的共享状态。ECharts:用于数据可视化展示。Mock.js:用于模拟后端接口数据。三、页面区域结构
页面整体分为三个区域,CommandAside区域、CommandHeader区域、HomePage区域,HomePage区域又分为左侧数据和右侧图标区域。图表区域又分为折线图、饼状图、柱状图区域。
1.MainPage代码实现
<el-container>
: Element UI 提供的布局容器,用于构建页面的整体结构。
<el-aside>
: 侧边栏区域,这里包含了 CommonAside
组件,通常用于放置导航或菜单。
<el-header>
: 页头区域,这里包含了 CommonHeader
组件,通常用于显示标题或工具条。
<el-main>
: 主内容区域,这里包含 <router-view>
,这是 Vue Router 的占位符,用于渲染匹配的子路由组件。
<template> <el-container> <el-aside width="auto"> <common-aside /> </el-aside> <el-container> <!--使用小驼峰命名法 --> <el-header> <common-header /> </el-header> <el-main> <!-- 二级路由出口 --> <router-view></router-view> </el-main> </el-container> </el-container></template><!-- 样式区域 --><style lang="less">.el-header { background-color: #333;}</style><script>import CommonAside from '@/components/CommonAside.vue'import CommonHeader from '@/components/CommonHeader.vue'export default { name: 'MainPage', data() { return { } }, components: { CommonAside, CommonHeader }}</script>
2.HomePage首页搭建
整体布局:使用 <el-row>
和 <el-col>
来构建响应式布局。:gutter="20"
指定了列之间的间隔为 20 像素。
左侧边栏区域 (<el-col :span="8">
):
包含两个主要部分:用户信息和一个表格。
用户信息:使用 <el-card>
组件来显示用户头像和基本信息(用户名及角色)。显示上次登录时间和地点。
表格:另一个 <el-card>
组件中嵌入了 <el-table>
,通过 v-for
动态生成表格的列。
右侧区域 (<el-col :span="16">
):
包含统计数据和图表。
统计信息:使用 v-for
迭代 countData
数组生成多个 <el-card>
,每个卡片展示一个统计项(如图标、价格和描述)。
折线图区域:一个 <el-card>
用于显示折线图,通过 ref="echart"
以便后续在 JavaScript 中引用。
饼状图区域:一个包含两个 <el-card>
的容器,用于显示不同的图表,分别通过 ref
引用。
<template> <el-row class="home" :gutter="20"> <!-- 左侧边栏区域 gutter="20" 表示左右两列之间的间隔为20像素。:offset="8" 表示该列向右偏移8个单位--> <el-col :span="8"> <el-card shadow=" hover"> <!-- 用户信息头像区域 --> <div class="user"> <!-- <img :src="getImageUrl('user')" class="user" /> --> <img src="../assets/images/user.png" alt=""> <div class="user-info"> <p class="name">Admin</p> <p class="access">超级管理员</p> </div> </div> <!-- 登录信息区域布局 --> <div class="login-info"> <p>上次登录时间:<span>2022-7-11</span></p> <p>上次登录的地点:<span>北京</span></p> </div> </el-card> <!-- 底部卡片区域 shadow="hover" 应用到一个元素上时,该元素在鼠标悬停时会显示特定的阴影--> <el-card style="margin-top: 20px; height: 410px;" shadow="hover" class="table"> <el-table :data="tableData"> <el-table-column v-for="(val, key) in tableLabel" :key="key" :prop="key" :label="val"> </el-table-column> </el-table> </el-card> </el-col> <!-- 这是右侧区域 --> <el-col :span="16"> <div class=" num"> <el-card v-for="item in countData" :key="item.name" :body-style="{ display: 'flex', padding: 0 } "> <i class="icons" :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;" class="top-echart"> <!--折线图区域 --> <div ref="echart" style="height: 280px;"></div> </el-card> <!-- 右侧底部饼状图区域 --> <div class="graph"> <el-card style="height: 210px;"> <div ref="userEchart" style="height: 240px"></div> </el-card> <el-card style="height: 210px;"> <div ref="videoEchart" style="height: 240px"></div> </el-card> </div> </el-col> </el-row></template>
3.HomePage首页中在mounted()中进行数据渲染逻辑实现
<script>import { getHomeData } from "@/api";//引入echartsimport * as echarts from "echarts";export default { name: 'HomePage', 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: "goods", color: "#5ab1ef", }, { name: "本月支付订单", value: 1234, icon: "success", color: "#2ec7c9", }, { name: "本月收藏订单", value: 210, icon: "star-on", color: "#ffb980", }, { name: "本月未支付订单", value: 1234, icon: "goods", color: "#5ab1ef", }, ], observer: null, xOptions: { // 图例文字颜色 textStyle: { color: "#333", }, legend: {}, grid: { left: "20%", }, // 提示框 tooltip: { trigger: "axis", }, xAxis: { type: "category", // 类目轴 data: [], axisLine: { lineStyle: { color: "#17b3a3", }, }, axisLabel: { interval: 0, color: "#333", }, }, yAxis: [ { type: "value", axisLine: { lineStyle: { color: "#17b3a3", }, }, }, ], color: ["#2ec7c9", "#b6a2de", "#5ab1ef", "#ffb980", "#d87a80", "#8d98b3"], series: [], }, pieOptions: { tooltip: { trigger: "item", }, legend: {}, color: [ "#0f78f4", "#dd536b", "#9462e5", "#a6a6a6", "#e1bb22", "#39c362", "#3ed1cf", ], series: [] }, } }, methods: { getImageUrl(user) { return new URL(`../assets/images/${user}.png`, import.meta.url).href; } }, mounted() { getHomeData().then(async ({ data }) => { const { tableData } = data.data; console.log(tableData); this.tableData = tableData; // 在当前位置进行echarsts,初始化echarts实 // this.getChartData(); const { orderData, userData, videoData } = data.data; console.log(111); console.log(orderData, userData, videoData); // 对第一个图表的xAxis和series赋值 this.xOptions.xAxis.data = orderData.date; this.xOptions.series = Object.keys(orderData.data[0]).map(val => ({ name: val, data: orderData.data.map(item => item[val]), type: "line" })); // one const OneEcharts = echarts.init(this.$refs["echart"]); OneEcharts.setOption(this.xOptions); // 对第二个图表的xAxis和series赋值 this.xOptions.xAxis.data = userData.map(item => item.date); this.xOptions.series = [ { name: "新增用户", data: userData.map(item => item.new), type: "bar", }, { name: "活跃用户", data: userData.map(item => item.active), type: "bar", } ]; // two const TwoEcharts = echarts.init(this.$refs["userEchart"]); TwoEcharts.setOption(this.xOptions); // 对第三个图表的series赋值 this.pieOptions.series = [ { data: videoData, type: "pie", }, ]; // three const ThreeEcharts = echarts.init(this.$refs["videoEchart"]); ThreeEcharts.setOption(this.pieOptions); // ResizeObserver 如果监视的容器大小变化,如果改变会执行传递的回调 this.observer = new ResizeObserver(entries => { OneEcharts.resize(); TwoEcharts.resize(); ThreeEcharts.resize(); }); // 如果这个容器存在 if (this.$refs["echart"]) { // 则调用监视器的observe方法,监视这个容器的大小 this.observer.observe(this.$refs["echart"]); } }) }}</script>
四、使用mock.js模拟后端接口数据
1.安装mock.js
npm i mockjs
2.创建mockData,新建home.js文件储存HomePage数据
如何创建 mock 数据文件,并在其中定义 HomePage 所需的数据结构。
// 首页数组//用户的数组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: 200, data: { // 左侧底部card数据 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, }, ], // 饼图 videoData: [ { name: "小米", value: 2999 }, { name: "苹果", value: 5999 }, { name: "vivo", value: 1500 }, { name: "oppo", value: 1999 }, { name: "魅族", value: 2200 }, { name: "三星", value: 4500 }, ], // 柱状图 orderData: { date: [ "2019-10-01", "2019-10-02", "2019-10-03", "2019-10-04", "2019-10-05", "2019-10-06", "2019-10-07", ], data: [ { 苹果: 3839, 小米: 1423, 华为: 4965, oppo: 3334, vivo: 2820, 一加: 4751, }, { 苹果: 3560, 小米: 2099, 华为: 3192, oppo: 4210, vivo: 1283, 一加: 1613, }, { 苹果: 1864, 小米: 4598, 华为: 4202, oppo: 4377, vivo: 4123, 一加: 4750, }, { 苹果: 2634, 小米: 1458, 华为: 4155, oppo: 2847, vivo: 2551, 一加: 1733, }, { 苹果: 3622, 小米: 3990, 华为: 2860, oppo: 3870, vivo: 1852, 一加: 1712, }, { 苹果: 2004, 小米: 1864, 华为: 1395, oppo: 1315, vivo: 4051, 一加: 2293, }, { 苹果: 3797, 小米: 3936, 华为: 3642, oppo: 4408, vivo: 3374, 一加: 3874, }, ], }, // 折线图 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 }, ] }, } }}
3. 创建Mock.js创建后端请求接口
import Mock from 'mockjs'//引入数据import homeApi from './mockServeData/home'Mock.mock('/api/home/getData', 'get', homeApi.getStatisticalData)
4.页面中引入使用发起异步请求
import { getHomeData } from "@/api";
mounted() { getHomeData().then(async ({ data }) => { const { tableData } = data.data; console.log(tableData); this.tableData = tableData; // 在当前位置进行echarsts,初始化echarts实 // this.getChartData(); const { orderData, userData, videoData } = data.data; console.log(111); console.log(orderData, userData, videoData); // 对第一个图表的xAxis和series赋值 this.xOptions.xAxis.data = orderData.date; this.xOptions.series = Object.keys(orderData.data[0]).map(val => ({ name: val, data: orderData.data.map(item => item[val]), type: "line" })); // one const OneEcharts = echarts.init(this.$refs["echart"]); OneEcharts.setOption(this.xOptions); // 对第二个图表的xAxis和series赋值 this.xOptions.xAxis.data = userData.map(item => item.date); this.xOptions.series = [ { name: "新增用户", data: userData.map(item => item.new), type: "bar", }, { name: "活跃用户", data: userData.map(item => item.active), type: "bar", } ]; // two const TwoEcharts = echarts.init(this.$refs["userEchart"]); TwoEcharts.setOption(this.xOptions); // 对第三个图表的series赋值 this.pieOptions.series = [ { data: videoData, type: "pie", }, ]; // three const ThreeEcharts = echarts.init(this.$refs["videoEchart"]); ThreeEcharts.setOption(this.pieOptions); // ResizeObserver 如果监视的容器大小变化,如果改变会执行传递的回调 this.observer = new ResizeObserver(entries => { OneEcharts.resize(); TwoEcharts.resize(); ThreeEcharts.resize(); }); // 如果这个容器存在 if (this.$refs["echart"]) { // 则调用监视器的observe方法,监视这个容器的大小 this.observer.observe(this.$refs["echart"]); } })
五、使用Vuex进行状态管理
1.安装Vuex
npm i vuex
2.main.js中挂载Vuex
// Vue.use(VueRouter)new Vue({ render: h => h(App), router, // 将 router 注入到根实例 store}).$mount('#app')
3.在index.js创建Vuex实例
import Vue from 'vue'import Vuex from 'vuex'import tab from './tab'Vue.use(Vuex)//创建vuex的实例export default new Vuex.Store({ state: { }, modules: { tab }})
4.示例tab.js导出
提供 tab.js 的示例代码,展示如何管理选项卡的状态。
export default { state: { isCollapse: false//控制菜单的展开还是收齐 }, mutations: { // 定义菜单收齐的方法 collapseMenu(state) { state.isCollapse = !state.isCollapse } }}
5.CommandHeader组件中使用,头部菜单栏折叠与显示
在 CommandHeader 组件中使用 Vuex 管理菜单栏的显示与隐藏。
computed: { //没有子菜单 noChilden() { return this.menuData.filter(item => !item.children) }, hasChilden() { return this.menuData.filter(item => item.children) }, // 控制菜单栏的收起与折叠 isCollapse() { return this.$store.state.tab.isCollapse } }
六、完整页面实现
七、代码仓库地址
https://gitee.com/tanzero/back-office.git 后续还在更新中~
八、参考视频
30-vue通用后台管理(面包屑&tag介绍)_哔哩哔哩_bilibilivue项目实战,vue3项目实战,vue2+element-ui项目,vue3项目实战(已完结)_哔哩哔哩_bilibili30-vue通用后台管理(面包屑&tag介绍)_哔哩哔哩_bilibili
九、总结
本项目致力于使用Vue2+Vuex+Echarts+Element-ui+Axios+Mock.js构建一个功能强大的后台管理系统,旨在提升用户体验和开发效率。通过引入 Vue Router,项目能够实现灵活的页面路由管理,使得不同功能模块的切换更加顺畅。同时,使用 Vuex 进行全局状态管理,可以有效地处理应用中的共享数据,确保各个组件之间的数据一致性。为了方便前端开发,利用 Mock.js 模拟后端接口数据,允许开发者在没有真实后端服务的情况下,依然能够进行数据渲染与交互测试。首页设计采用组件化结构,通过 ECharts 实现数据可视化,帮助用户更直观地理解数据趋势。通过本项目,希望大家可以一起了解前端项目开发的流程,一起学习~