主要文件:
image.png
各個文件內(nèi)的代碼:
image.png
// mock數(shù)據(jù)模擬
import Mock from 'mockjs'
// 圖表數(shù)據(jù)
let List = []
export default {
getStatisticalData: () => {
//Mock.Random.float 產(chǎn)生隨機數(shù)100到8000之間 保留小數(shù) 最小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"
// 請求首頁數(shù)據(jù)
export const getDate = () => {
// 會返回一個 promise 對象
return http.get('/home/getData')
}
api/mock.js
import Mock from 'mockjs'
import homeApi from '@/api/mockServeData/home'
// 定義mock請求攔截
// 請求地址 接口請求類型 處理函數(shù)
Mock.mock('/api/home/getData', 'get', homeApi.getStatisticalData)
image.png
<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 ? '后臺' : '通用后臺管理系統(tǒng)' }}</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) {
// 當頁面的路由與跳轉(zhuǎn)的路由路徑不一致的時候才允許跳轉(zhuǎn)
if(this.$route.path !== item.path && !(this.$route.path === '/home' && (item.path === '/'))) { // $route是路徑參數(shù)蓬抄,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>
image.png
<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>
image.png
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
image.png
import Vue from 'vue'
import Vuex from 'Vuex'
import tab from './tab'
Vue.use(Vuex)
// 創(chuàng)建vuex的實例
export default new Vuex.Store({
modules: {
tab
}
})
image.png
// 管理菜單相關的數(shù)據(jù)
export default {
state: {
isCollapse: false //用于控制菜單的展開還是收起
},
mutations: {
// 修改菜單展開還是收起的方法
collapseMenu(state) {
state.isCollapse = !state.isCollapse
}
}
}
image.png
import axios from "axios"
const http = axios.create({
// 通用請求地址前綴
baseURL: '/api',
timeout: 10000, // 超時時間 10s
})
// 添加請求攔截器
http.interceptors.request.use(function (config) {
// 在發(fā)送請求之前做些什么
return config;
}, function (error) {
// 對請求錯誤做些什么
return Promise.reject(error);
});
// 添加響應攔截器
http.interceptors.response.use(function (response) {
// 對響應數(shù)據(jù)做點什么
return response;
}, function (error) {
// 對響應錯誤做點什么
return Promise.reject(error);
});
export default http
image.png
<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: '課程數(shù)據(jù)'
},
echarts1Option.xAxis = xAxisData
// 處理y軸
echarts1Option.yAxis = {}
//legend 用于與下面的data匹配數(shù)據(jù)
echarts1Option.legend = xAxisData
// series
echarts1Option.series = []
xAxis.forEach(key => {
echarts1Option.series.push({
name: key, // 圖上面的名稱識別每個數(shù)據(jù)
data: orderData.data.map(item => item[key]), // 每個name的具體數(shù)據(jù)
type: 'line' // 統(tǒng)計圖的類型
})
})
// 使用剛指定的配置項和數(shù)據(jù)顯示圖表
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>
image.png
<template>
<div>
<div class="common-layout">
<el-container>
<el-aside width="auto">
<!-- 左側(cè)區(qū)域 -->
<common-aside />
</el-aside>
<el-container>
<!-- 頂部區(qū)域 -->
<el-header>
<common-header />
</el-header>
<!-- 主體區(qū)域 -->
<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>
image.png
<template>
<h1>我是Mall</h1>
</template>
<script>
export default {
data() {
return { }
}
}
</script>
<style>
</style>
image.png
<template>
<h1>我是PageOne</h1>
</template>
<script>
export default {
data() {
return { }
}
}
</script>
<style>
</style>
image.png
<template>
<h1>我是PageTwo</h1>
</template>
<script>
export default {
data() {
return { }
}
}
</script>
<style>
</style>
image.png
<template>
<h1>我是User</h1>
</template>
<script>
export default {
data() {
return { }
}
}
</script>
<style>
</style>
image.png
<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>
image.png
// 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后臺數(shù)據(jù)模擬:(沒有后端數(shù)據(jù),前端自己給自己提供數(shù)據(jù))
定義:前端模擬后端的工具,用于攔截前端發(fā)起的請求吧雹,返回數(shù)據(jù)
1骨杂、下載安裝 pnpm install mockjs
2涂身、定義mock請求攔截雄卷,在main.js中引入,在api/mock.js中配置