Vue2 + ElementUI = 后台管理系统
1. vue脚手架
用来创建vue项目的工具包
创建项目:
npm install -g vue-cli
vue init webpack VueDemo
开发环境运行:
cd VueDemo
npm install
npm run dev
生产环境打包发布(webpack打包)
npm run build
使用静态服务器工具包:
npm install -g serve
serve dist
http://localhost:5000
新建 vue.config.js
module.exports = {
pages:{
index:{
entry:'src/main.js'
}
},
lintOnSave:false//关闭语法检查
}
2 路由 vueRouter
- 在 src/router/index.js中写入路由信息,
- 在 src/App.vue 中使用路由组件,
<router-view />
,查看路由视图
· src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Main from '../views/Main.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Main',
component: Main
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
· src/App.vue
<template>
<div id="app">
<router-link to="/">Main</router-link> |
<router-link to="/about">about</router-link>
<router-view />
</div>
</template>
3 element-ui
引入element-ui
- 全局引入
- 按需引入(一般项目按需引入)
- 更改配置 babel.config.js
@Babel/preset-env
· babel.config.js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
- 在Main.vue 中 引入 element-ui 的部分组件,需要在main.js 中注册 · main.js
import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import store from './store'
// import ElementUI from 'element-ui';
// import 'element-ui/lib/theme-chalk/index.css';
// Vue.use(ElementUI);
import { Menu, MenuItem, MenuItemGroup,Submenu,Button, Select,Radio,Container,Aside,Header,Main } from 'element-ui';
Vue.use(Button);
Vue.use(Select);
Vue.use(Radio);
Vue.use(Container);
Vue.use(Aside);
Vue.use(Header);
Vue.use(Menu);
Vue.use(Main);
Vue.use(MenuItem);
Vue.use(MenuItemGroup);
Vue.use(Submenu);
// Vue.component(Button.name, Button);
// Vue.component(Select.name, Select);
// Vue.component(Radio.name, Radio);
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
4 Container 布局
src/views/Main.vue
<template>
<el-container style="height:100%">
<el-aside width="auto">aside</el-aside>
<el-container>
<el-header>Header</el-header>
<el-main>Main</el-main>
</el-container>
</el-container>
</template>
5 组件
侧边栏组件UI-CommonAside.vue
- 创建侧边栏组件src/components/CommonAside.vue, 复制粘贴侧边栏组件UI。
- 在src/views/Main.vue中引入组件
import CommonAside from "../components/CommonAside.vue"
,注册组件components: {CommonAside}
并使用组件标签<common-aside></common-aside>
· src/components/CommonAside.vue
<template>
<el-menu 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>
一级菜单和二级菜单
src/components/CommonAside.vue
- 写入菜单数据
- 计算属性,判断 有无子菜单
- 通过:band 和 v-for 去绑定和循环,实现菜单列表
<template>
<el-menu class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose" :collapse="isCollapse">
<h3>通用后台管理系统</h3>
<el-menu-item :index="item.path" v-for="item in noChildren" :key="item.path">
<i :class="'el-icon-'+ item.icon"></i>
<span slot="title">{{item.label}}</span>
</el-menu-item>
<el-submenu :index="item.label" v-for="item in hasChildren" :key="item.path">
<template slot="title">
<i :class="'el-icon-'+ item.icon"></i>
<span slot="title">{{item.label}}</span>
</template>
<el-menu-item-group>
<el-menu-item :index="subItem.path" v-for="(subItem, subIndex) in item.children" :key="subIndex">
<i :class="'el-icon-'+ subItem.icon"></i>
<span slot="title">{{subItem.label}}</span>
</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</template>
<style scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
height: 100vh;
border-right: 0;
}
.el-menu-item, .el-submenu{
text-align: left;
}
</style>
<script>
export default {
data() {
return {
isCollapse: true,
//菜单 1 写入菜单数据
menu:[
{
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);
}
},
// 2 计算属性,判断 有无子菜单
computed: {
noChildren() {
return this.menu.filter((item) => !item.children)
},
hasChildren(){
return this.menu.filter((item) => item.children)
}
}
}
</script>
Menu 样式
src/components/CommonAside.vue
<el-menu
class="el-menu-vertical-demo"
:collapse="isCollapse"
background-color="#545c64"
text-color="fff"
active-text-color="#ffd04b">
CommonHeader 组件(下拉列表)
-
创建Header组件src/components/CommonHeader.vue , 复制粘贴el-header组件UI。
-
在src/views/Main.vue中引入组件
import CommonHeader from "../components/CommonHeader.vue"
,注册组件components: {CommonHeader}
并使用组件标签<common-header>首页</common-header>
· src/components/CommonHeader.vue
<template>
<header>
<div class="l-content">
<el-button plain icon="el-icon-menu" size="mini"></el-button>
<h3 style="color:#fff"></h3>
</div>
<div class="r-content">
<el-dropdown trigger="click" size="mini">
<span class="el-dropdown-link">
<img src="userImg" class="user" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>个人中心</el-dropdown-item>
<el-dropdown-item>退出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</header>
</template>
<script>
export default {
components: {
},
// 定义属性
data() {
return {
userImg: require('../assets/user.png'),
}
},
// 计算属性,会监听依赖属性值随之变化
computed: {
},
// 监控data中的数据变化
watch: {},
// 方法集合
methods: {
},
}
</script>
<style lang='scss' scoped>
header {
display: flex;
height:100%;
align-items: center;
justify-content:space-between;
}
.l-content{
display:flex;
align-item:center;
.el-button{
margin-right:20px
}
}
.r-content{
.user{
width:40px;
height:40px;
border-radio: 50%;
}
}
</style>
侧边栏的折叠收缩(Vuex)
说明:点击 header ,实现侧边栏的折叠收缩
注意:这里使用 Vuex 状态管理,实现了跨组件传值 ,
· 1 store/tab.js
export default{
state:{
isCollapse: false,
},
mutations:{
collapseMenu(state){
state.isCollapse = !state.isCollapse;
}
}
}
·2 store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import tab from './tab'//当前目录下的tab
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
tab
}
})
3.将isCollapse改为方法,去判断是否折叠 // isCollapse: false,//这里写在computed中
· src/components/CommonAside.vue
<el-menu class="el-menu-vertical-demo" :collapse="isCollapse" background-color="#545c64" text-color="fff" active-text-color="#ffd04b">
<h3 v-show="!isCollapse">通用后台管理系统</h3>
<h3 v-show="isCollapse">后台</h3>
computed: {
noChildren() {
return this.menu.filter((item) => !item.children)
},
hasChildren(){
return this.menu.filter((item) => item.children)
},
isCollapse(){
return this.$store.state.tab.isCollapse
}
}
· src/components/CommonHeader.vue
<el-button plain icon="el-icon-menu" size="mini" @click="clickHandler(item)"></el-button>
methods: {
clickHandler(){
this.$store.commit('collapseMenu');
}
},
home 组件布局 (router)
- router/index.js 加入子路由
const routes = [
{
path: '/',
name: 'Main',
component: Main,
children: [
{
path: '/',
name:'home',
component: ()=>import('@/views/Home/Home')//路由懒加载
}
]
},
- 新建Home组件 views/Home/Home.vue
<template>
<div>
这是Home的组件
</div>
</template>
home组件用户卡片部分
<template>
<el-row class="home" :gutter="20">
<el-col :span="8" style="margin-top:20px">
<el-card shadow="hover">
<div class="user">
<img :src="userImg"/>
<div class="userinfo">
<p class="name">Admin</p>
<p class="access">超级管理员</p>
</div>
</div>
<div class="login-info">
<p>上次登录时间: <span>2021-12-20</span></p>
<p>上次登录地点: <span>苏州</span></p>
</div>
</el-card>
</el-col>
<el-col :span="16"></el-col>
</el-row>
</template>
<script>
export default {
components: {
},
// 定义属性
data() {
return {
userImg:require('../../assets/user.png')
}
},
// 计算属性,会监听依赖属性值随之变化
computed: {
},
// 监控data中的数据变化
watch: {},
// 方法集合
methods: {
},
}
</script>
<style lang='sass' scoped>
@import "~@/assets/scss/home"
</style>
home 组件购买统计
<el-card style="margin-top:20px">
<el-table :data="tableData">
<el-table-column
show-overflow-tooltip
v-for="(val,key) in tableLabel"
:key="key"
:prop="key"
:label="val"
>
</el-table-column>
</el-table>
</el-card>
data() {
return {
userImg:require('../../assets/user.png'),
tableData:[],//这里需要放入数据,或者mock虚拟数据
tableLabel:{
name: '课程',
todayBuy:'今天购买',
mouthBuy:'本月购买',
totalBuy:'总购买',
}
}
},
home 组件订单展示
<el-col :span="16" style="margin-top:20px">
<div class="num">
<el-card
shadow="hover"
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="num">¥{{item.value}}</p>
<p class="txt">{{item.name}}</p>
</div>
</el-card>
</div>
<el-card shadow="hover" style="height:280px"></el-card>
<div class="graph" >
<el-card shadow="hover" style="height:260px"></el-card>
<el-card shadow="hover" style="height:260px"></el-card>
</div>
</el-col>
axios 基本使用
- 下载安装axios
cnpm install axios
main.js
import https from 'axios'
Vue.prototype.$http = http
2 Home.vue
mounted() {
this.$http.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
二次封装的axios的实现(拦截器)
在请求之前(添加一些参数)、请求之后,加入拦截器,可以对公共错误进行集中处理。
- 新建 src/api/axios.js
// 拦截器
import axios from 'axios';
//引入config/index.js
import config from '../config/index'
//设置配置 开发环境和生产环境不一样
const baseUrl = process.env.NODE_ENV === 'development' ? config.baseUrl.dev :config.baseUrl.pro
class HttpRequest {
constructor(baseUrl){
this.baseUrl = baseUrl
}
getInsideConfig() {
const config = {
baseUrl:this.baseUrl,
header:{
}
}
return config
}
interceptors(instance) {
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
console.log('拦截处理请求')
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 对响应数据做点什么
console.log('处理响应')
return response.data;
}, function (error) {
// 对响应错误做点什么
console.log(error)
return Promise.reject(error);
});
}
// {
// baseUrl:'./api-'
// }
request(options){//request(option)是调用上面HttpRequest的方法,options是传入一些参数对象
//请求
// /api/getList /api/getHome
const instance = axios.create()
options = {...(this.getInsideConfig()),...options}//技巧
this.interceptors(instance)
return instance(options)
}
}
export default new HttpRequest(baseUrl)
options = {...(this.getInsideConfig()),...options}
- 新建src/config/index.js config 这里的事api/axios里面的一些配置
export default{
title:'admin',
baseUrl:{
//开发环境
dev:'./api/',
// 生产环境
pro:''
}
}
二次封装的axios的用法
- 新建 scr/api/data.js
import axios from './axios'
export const getMenu = () => {
return axios.request({
url:'menu',
method:'GET',
})
}
2 Home.vue 引入并使用
<script>
import {} from "../../api/data.js"
mounted() {
getMenu().then((res)=>{
console.log(res)
})
}
}
</script>
mock 数据模拟的实现
mock.js 生成随机数据,拦截ajax请求
1.安装
cnpm i mockjs -S
- 新建 api/mock.js
import Mock from "mockjs"
import homeApi from "./mockServerData/home"
Mock.mock('api/home/getData',homeApi.getStatisticalData)
-
新建 api/mockServerData/home.js 导入数据
-
api/data.js 添加getHome方法
import axios from './axios'
export const getMenu = () => {
return axios.request({
url:'menu',
method:'GET',
})
}
export const getHome = () => {
return axios.request({
url:'/home/getData',
method:'GET',
})
}
- main.js 中引入mock.js
if(process.env.NODE_EN === 'development') require('@/api/mock')
- 使用mock
Echarts 应用
- 安装echarts
- 加入
ref="userEchart"
Home.vue
<el-card shadow="hover" style="height:280px" ></el-card>
<div style="height:280px" ref="echart"></div>
<div class="graph" >
<el-card shadow="hover" style="height:260px"></el-card>
<div style="height:280px" ref="userEchart"> </div>
<el-card shadow="hover" style="height:260px"></el-card>
<div style="height:280px" ref="videoEchart"> </div>
- 展示
methods:{
getTableData(){
getHome().then((res)=>{
this.tableData = res.data.tableData
// 折线图的展示
const order = res.data.orderData
// console.log(order)
this.echartsData.order.xAxis.data = order.data
let keyArray = Object.keys(order.data[0])
keyArray.forEach((key)=>{
this.echartsData.order.series.push({
name: key,
data: order.data.map((item) => item[key]),
type: "line",
})
})
const myEchartsOrder = echarts.init(this.$refs.echart)//refs不要直接写在el-组件上,用div
myEchartsOrder.setOptions(this.echartsOrder.order)
// 用户图
this.echartsData.user.xAxis.data = red.data.userData.map((item)=> item.data)
this.echartsData.user.series.push({
name:'新增用户',
data: res.data.userData.map((item) => item.new),
type:'bar'
});
this.echartsData.user.series.push({
name:'活跃用户',
data: res.data.userData.map((item) => item.active),
type:'bar'
});
const myEchartsUser = echarts.init(this.$refs.userEchart)//refs不要直接写在el-组件上,用div
myEchartsUser.setOptions(this.myEchartsUser.user)
// 饼状图
this.echartsData.video.series.push({
data: res.data.videoData,
type:'pie'
});
const myEchartsVideo = echarts.init(this.$refs.videoEchart)//refs不要直接写在el-组件上,用div
myEchartsVideo.setOptions(this.myEchartsVideo.user)
})
}
},
Echarts 封装
1.新建components/ECharts.vue组件
根据是否有x轴,来区分
<template>
<div ref="echart">
</div>
</template>
<script>
export default {
components: {
},
props:{
charData: {
type: Object,
default(){
return{
xData:[],
series,
}
}
},
isAxisChart: {
type: Boolean,
default: true,
}
}
// 定义属性
data() {
return {
axisOption:{
legend: {
// 图例文字颜色
textStyle: {
color: "#333",
},
},
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: [],
},
normalOption: {
tooltip: {
trigger: "item",
},
color: [
"#0f78f4",
"#dd536b",
"#9462e5",
"#a6a6a6",
"#e1bb22",
"#39c362",
"#3ed1cf",
],
series: [],
},
echart: null,
};
},
// 计算属性,会监听依赖属性值随之变化
computed: {
},
// 监控data中的数据变化
watch: {},
// 方法集合
methods: {
},
}
</script>
<style lang='sass' scoped>
</style>
- 判断不一样的data,通过watch监听
// 2 监控data中的数据变化
watch: {
charData{
handler:function(){
this.initChart();
},
deep:true,
}
},
3、4.init初始化chart
// 方法集合
methods: {
initChart() {
this.initChartData();
// 设置echarts表格了
//
if (this.echart) {
this.echart.setOption(this.options);
} else {
this.echart = echarts.init(this.$refs.echart);
this.echart.setOption(this.options);
}
},
initChartData() {
if (this.isAxisChart) {
this.axisOption.xAxis.data = this.chartData.xData;
this.axisOption.series = this.chartData.series;
} else {
this.normalOption.series = this.chartData.series;
}
},
},
5 计算属性,会监听依赖属性值随之变化
// 5 计算属性,会监听依赖属性值随之变化
computed: {
options(){
return this.isAxisChart ? this.axisOption :this.normalOption
}
},
封装折线图、饼状图和条形图
home.vue
<el-card shadow="hover" style="height: 280px">
<echart :chartData="echartData.order" style="height: 280px"></echart>
</el-card>
<div class="graph">
<el-card shadow="hover" style="height: 260px">
<echart :chartData="echartData.user" style="height: 240px"></echart>
</el-card>
<el-card shadow="hover" style="height: 260px">
<echart
:chartData="echartData.video"
style="height: 240px"
:isAxisChart="false"
></echart>
</el-card>
methods: {
getTableData() {
getHome().then((res) => {
// console.log(res);
this.tableData = res.data.tableData;
// 折线图的展示
const order = res.data.orderData;
let keyArray = Object.keys(order.data[0]);
// const myEchartsOrder = echarts.init(this.$refs.echart);
// myEchartsOrder.setOption(this.echartsData.order);
// 传给组件的值
this.echartData.order.xData = order.date;
keyArray.forEach((key) => {
this.echartData.order.series.push({
name: key,
data: order.data.map((item) => item[key]),
type: "line",
});
});
// 用户图
this.echartData.user.xData = res.data.userData.map((item) => item.date);
this.echartData.user.series.push({
name: "新增用户",
data: res.data.userData.map((item) => item.new),
type: "bar",
});
this.echartData.user.series.push({
name: "活跃用户",
data: res.data.userData.map((item) => item.active),
type: "bar",
});
this.echartData.video.series.push({
data: res.data.videoData,
type: "pie",
});
});
},
},
mounted() {
this.getTableData();
},
};
转载自:https://juejin.cn/post/7046930026985422862