主要文件:
各个文件内的代码:
// 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
}
]
}
}
}
}
api/index.js
import http from "@/utils/request"
// 请求首页数据
export const getDate = () => {
// 会返回一个 promise 对象
return http.get('/home/getData')
}
api/mock.js
import Mock from 'mockjs'
import homeApi from '@/api/mockServeData/home'
// 定义mock请求拦截
// 请求地址 接口请求类型 处理函数
Mock.mock('/api/home/getData', 'get', homeApi.getStatisticalData)
<template>
<el-menu
default-active="1-4-1"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
:collapse="isCollapse"
active-text-color="#ffd04b"
background-color="#545c64"
text-color="#fff"
>
<h3>{{ isCollapse ? '后台' : '通用后台管理系统' }}</h3>
<el-menu-item @click="cliclMenu(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.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.name">
<el-menu-item @click="cliclMenu(subItem)" :index="subItem.path">{{ subItem.label }}</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</template>
<script>
export default {
data() {
return {
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);
},
// 点击菜单
cliclMenu(item) {
// 当页面的路由与跳转的路由路径不一致的时候才允许跳转
if(this.$route.path !== item.path && !(this.$route.path === '/home' && (item.path === '/'))) { // $route是路径参数,route 主要用于获取当前路由信息
this.$router.push(item.path) // $router是路由对象,router 则是用于进行路由操作
}
}
},
computed: {
// 没有子菜单
noChildren() {
return this.menuData.filter(item => !item.children)
},
// 有子菜单
hasChildren() {
return this.menuData.filter(item => item.children)
},
isCollapse() {
return this.$store.state.tab.isCollapse
}
}
};
</script>
<style lang="less" scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
}
.el-menu {
height: 100vh;
border-right: none;
h3 {
color: #fff;
text-align: center;
line-height: 48px;
font-size: 16px;
font-weight: 400px;
}
}
</style>
<template>
<div class="header-container">
<div class="l-content">
<el-button @click="handleMenu" icon="el-icon-menu" size="mini"></el-button>
<!-- 面包屑 -->
<span class="text">首页</span>
</div>
<div class="r-content">
<el-dropdown>
<span class="el-dropdown-link">
<img class="user" src="@/assets/images/user.jpg">
</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>
export default {
data() {
return { }
},
methods: {
handleMenu() {
this.$store.commit('collapseMenu')
}
}
}
</script>
<style lang="less" scoped>
.header-container {
padding: 0px 20px;
background-color: #333;
height: 60px;
display: flex;
justify-content: space-between;
align-items: center;
.text {
color: #fff;
font-size: 14px;
margin-left: 10px;
}
.r-content {
.user {
width: 40px;
height: 40px;
border-radius: 50px;
}
}
}
</style>
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/views/Home.vue'
import User from '@/views/User.vue'
import Mian from '@/views/Main.vue'
import Mall from '@/views/Mall.vue'
import PageOne from '@/views/PageOne.vue'
import PageTwo from '@/views/PageTwo.vue'
Vue.use(VueRouter)
const routes = [
// 主路由
{
path: '/',
component: Mian,
redirect: '/home', //重定向
children: [
{ path: '/home', component: Home }, //首页
{ path: '/user', component: User }, //用户管理
{ path: '/mall', component: Mall }, //商品管理
{ path: '/page1', component: PageOne }, //商品管理
{ path: '/page2', component: PageTwo }, //商品管理
]
}
]
const router = new VueRouter({
routes
})
export default router
import Vue from 'vue'
import Vuex from 'Vuex'
import tab from './tab'
Vue.use(Vuex)
// 创建vuex的实例
export default new Vuex.Store({
modules: {
tab
}
})
// 管理菜单相关的数据
export default {
state: {
isCollapse: false //用于控制菜单的展开还是收起
},
mutations: {
// 修改菜单展开还是收起的方法
collapseMenu(state) {
state.isCollapse = !state.isCollapse
}
}
}
import axios from "axios"
const http = axios.create({
// 通用请求地址前缀
baseURL: '/api',
timeout: 10000, // 超时时间 10s
})
// 添加请求拦截器
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
<template>
<el-row>
<el-col :span="8" style="padding-right: 10px">
<el-card class="box-card">
<div class="user">
<img src="@/assets/images/user.jpg">
<div class="userinfo">
<p class="name">Admin</p>
<p class="access">超级管理员</p>
</div>
</div>
<div class="login-info">
<p>上次登录时间:<span>2023-12-8</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-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" 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 { getDate } from "@/api";
import * as echarts from 'echarts'
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() {
getDate().then(({ data }) => {
const { tableData, userData, videoData } = data.data
this.tableData = tableData
//折线图
// 基于准备好的dom,初始化echarts实例
const echarts1 = echarts.init(this.$refs.echarts1)
// 绘制图表
var echarts1Option = {}
// 处理x轴
const { orderData } = data.data
const xAxis = Object.keys(orderData.data[0])
const xAxisData = {
data: xAxis
}
echarts1Option.title = {
text: '课程数据'
},
echarts1Option.xAxis = xAxisData
// 处理y轴
echarts1Option.yAxis = {}
//legend 用于与下面的data匹配数据
echarts1Option.legend = xAxisData
// series
echarts1Option.series = []
xAxis.forEach(key => {
echarts1Option.series.push({
name: key, // 图上面的名称识别每个数据
data: orderData.data.map(item => item[key]), // 每个name的具体数据
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 {
width: 150px;
height: 150px;
border-radius: 50%;
margin-right: 40px;
}
.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;
text-align: center;
line-height: 80px;
color: #fff;
}
.detail {
display: flex;
flex-direction: column;
justify-content: center;
margin-left: 15px;
.price {
font-size: 30px;
margin-bottom: 10px;
line-height: 30px;
height: 30px;
}
.desc {
color: #999999;
font-size: 14px;
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>
<template>
<div>
<div class="common-layout">
<el-container>
<el-aside width="auto">
<!-- 左侧区域 -->
<common-aside />
</el-aside>
<el-container>
<!-- 顶部区域 -->
<el-header>
<common-header />
</el-header>
<!-- 主体区域 -->
<el-main><router-view></router-view></el-main>
</el-container>
</el-container>
</div>
</div>
</template>
<script>
import CommonAside from '@/components/CommonAside.vue'
import CommonHeader from '@/components/CommonHeader.vue'
export default {
data() {
return { }
},
components: {
CommonAside,
CommonHeader
}
}
</script>
<style scoped>
.el-header {
padding: 0
}
</style>
<template>
<h1>我是Mall</h1>
</template>
<script>
export default {
data() {
return { }
}
}
</script>
<style>
</style>
<template>
<h1>我是PageOne</h1>
</template>
<script>
export default {
data() {
return { }
}
}
</script>
<style>
</style>
<template>
<h1>我是PageTwo</h1>
</template>
<script>
export default {
data() {
return { }
}
}
</script>
<style>
</style>
<template>
<h1>我是User</h1>
</template>
<script>
export default {
data() {
return { }
}
}
</script>
<style>
</style>
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
<style lang="less">
h3,html,body,p {
margin: 0;
padding: 0;
}
</style>
// main.ts
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import store from '@/store/index'
import '@/api/mock'
Vue.config.productionTip = false
Vue.use(ElementUI )
new Vue({
store,
router,
render: h => h(App),
}).$mount('#app')
其他记录:
利用mock后台数据模拟:(没有后端数据,前端自己给自己提供数据)
定义:前端模拟后端的工具,用于拦截前端发起的请求,返回数据
1、下载安装 pnpm install mockjs
2、定义mock请求拦截,在main.js中引入,在api/mock.js中配置