banner
不断学习,不断进步

使用vue脚手架搭建后台管理系统

Scroll down

本项目是基于vue+element-ui进行开发的一个后台管理系统,适用于刚学习完vue或者是后端开发人员去了解前端知识的一个新手项目,该项目是根据b站Ailen老师的分享进行总结。

vue后台管理系统

使用Yarn包管理工具

  • 速度快
  • 离线模式
  • 安装版本统一
  • 更简洁的输出
  • 更好的语义化

vue-cli 脚手架的搭建

前提需安装node.js和npm

查看nodejs和npm

安装vue-cli

安装vue-cli

使用yarn命令安装需提前安装yarn,使用

1
npm i -g yarn

使用如下命令检查是否安装成功

1
vue -V

使用如下命令创建脚手架文件

1
vue create “自己文件名”  // 不可使用驼峰命名

选择vue版本

vue版本

使用yarn包管理工具,创建完成后,使用如下命令启动脚手架

1
npm run serve

项目启动成功

安装element-ui

参考网站组件 | Element进行安装

在入口文件main.js 写以下代码使用element-ui

1
2
3
4
5
6
7
8
9
10
11
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';

Vue.use(ElementUI); //全局注入

new Vue({
el: '#app',
render: h => h(App)
});

按需引用组件

首先,安装 babel-plugin-component:

1
npm install babel-plugin-component -D

在babel.config.js下修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
["@babel/preset-env", { "modules": false }]
],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}

接下来,如果你只希望引入部分组件,比如Button和Row,那么需要在 main.js 中写入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
import Vue from 'vue'
import App from './App.vue'
import {Row,Button} from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.config.productionTip = false
//Vue.use(ElementUI);
Vue.use(Row);
Vue.use(Button);
new Vue({
render: h => h(App),
}).$mount('#app')

这样做可以减少项目打包的体积

vue router

vue的官方路由,用以下代码安装

1
npm  i vue-router@3.6.5

在src目录下新建router文件夹,再新建index.js,在index.js配置相关路由。如果在一个模块化工程中使用它,必须要通过 Vue.use() 明确地安装路由功能:

1
2
3
4
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

在src文件夹下创建views文件夹,从这个文件夹构造各个组件。

创建Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<template>
<div>
<h1>我是home</h1>
</div>
</template>


<script>
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';

export default {
//import 引入的组件需要注入到对象中才能使用
components: {},
props: {},
data() {
//这里存放数据
return {

};
},
//计算属性 类似于 data 概念
computed: {},
//监控 data 中的数据变化
watch: {},
//方法集合
methods: {

},
//生命周期 - 创建完成(可以访问当前 this 实例)
created() {

},
//生命周期 - 挂载完成(可以访问 DOM 元素)
mounted() {

},
beforeCreate() { }, //生命周期 - 创建之前
beforeMount() { }, //生命周期 - 挂载之前
beforeUpdate() { }, //生命周期 - 更新之前
updated() { }, //生命周期 - 更新之后
beforeDestroy() { }, //生命周期 - 销毁之前
destroyed() { }, //生命周期 - 销毁完成
activated() { }, //如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>
<style scoped></style>

创建User.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<template>
<div>
<h1>我是user</h1>
</div>
</template>


<script>
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';

export default {
//import 引入的组件需要注入到对象中才能使用
components: {},
props: {},
data() {
//这里存放数据
return {

};
},
//计算属性 类似于 data 概念
computed: {},
//监控 data 中的数据变化
watch: {},
//方法集合
methods: {

},
//生命周期 - 创建完成(可以访问当前 this 实例)
created() {

},
//生命周期 - 挂载完成(可以访问 DOM 元素)
mounted() {

},
beforeCreate() { }, //生命周期 - 创建之前
beforeMount() { }, //生命周期 - 挂载之前
beforeUpdate() { }, //生命周期 - 更新之前
updated() { }, //生命周期 - 更新之后
beforeDestroy() { }, //生命周期 - 销毁之前
destroyed() { }, //生命周期 - 销毁完成
activated() { }, //如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>
<style scoped></style>

在index.js 引入组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Vue from 'vue'
import VueRouter from 'vue-router'
//1.创建路由组件
import Home from '@/views/Home'
import User from '@/views/User'
Vue.use(VueRouter)

//2.将路由与组件进行映射
const routes = [
{ path: '/home', component: Home },
{ path: '/user', component: User }
]

//3. 创建 router 实例,然后传 `routes` 配置
const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
})

// 对外进行包入

export default router

在mian.js 入口文件挂载路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Vue from 'vue'
import App from './App.vue'
import {Row,Button} from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import router from './router'

Vue.config.productionTip = false
//Vue.use(ElementUI);
Vue.use(Row);
Vue.use(Button);
new Vue({
//此处挂载路由
router,
render: h => h(App),
}).$mount('#app')

直接启动不会跳转,需要在App.vue挂载路由出口

1
2
3
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>

路由跳转成功

嵌套路由

减少菜单栏及头部或者脚部的重复开发

在view文件夹创建Main.vue 作为主路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<template>
<div>
<h1>main</h1>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
</template>


<script>
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';

export default {
//import 引入的组件需要注入到对象中才能使用
components: {},
props: {},
data() {
//这里存放数据
return {

};
},
//计算属性 类似于 data 概念
computed: {},
//监控 data 中的数据变化
watch: {},
//方法集合
methods: {

},
//生命周期 - 创建完成(可以访问当前 this 实例)
created() {

},
//生命周期 - 挂载完成(可以访问 DOM 元素)
mounted() {

},
beforeCreate() { }, //生命周期 - 创建之前
beforeMount() { }, //生命周期 - 挂载之前
beforeUpdate() { }, //生命周期 - 更新之前
updated() { }, //生命周期 - 更新之后
beforeDestroy() { }, //生命周期 - 销毁之前
destroyed() { }, //生命周期 - 销毁完成
activated() { }, //如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>
<style scoped></style>

在index.js下配置主路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import Vue from 'vue'
import VueRouter from 'vue-router'
//1.创建路由组件
import Home from '@/views/Home'
import User from '@/views/User'
import Main from '@/views/Main'
Vue.use(VueRouter)

//2.将路由与组件进行映射
const routes = [
//主路由
{
path:'/',
component:Main,
children:[
//子路由
{ path: 'home', component: Home },
{ path: 'user', component: User }
]
}
]

//3. 创建 router 实例,然后传 `routes` 配置
const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
})

// 对外进行包入

export default router

注意需要在主路由也加上路由出口

整体UI搭建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>
<el-container>
<el-header>Header</el-header>
<el-container>
<el-aside width="200px">Aside</el-aside>
<el-main>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</div>
</template>

在components下创建菜单组件CommonAside.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<template>
<el-menu default-active="1-4-1" 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>


<script>
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';

export default {
//import 引入的组件需要注入到对象中才能使用
components: {},
props: {},
data() {
//这里存放数据
return {
isCollapse: fasle //取消展开
};
},
methods: {
handleOpen(key, keyPath) {
console.log(key, keyPath);
},
handleClose(key, keyPath) {
console.log(key, keyPath);
}
},
//计算属性 类似于 data 概念
computed: {},
//监控 data 中的数据变化
watch: {},
//方法集合
methods: {

},
//生命周期 - 创建完成(可以访问当前 this 实例)
created() {

},
//生命周期 - 挂载完成(可以访问 DOM 元素)
mounted() {

},
beforeCreate() { }, //生命周期 - 创建之前
beforeMount() { }, //生命周期 - 挂载之前
beforeUpdate() { }, //生命周期 - 更新之前
updated() { }, //生命周期 - 更新之后
beforeDestroy() { }, //生命周期 - 销毁之前
destroyed() { }, //生命周期 - 销毁完成
activated() { }, //如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>
<style scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
}
</style>

在Main.js中引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<template>
<div>
<el-container>
<el-header>Header</el-header>
<el-container>
<el-aside width="200px">
<Common-aside></Common-aside>
</el-aside>
<el-main>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</div>
</template>


<script>
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
import CommonAside from '@/components/CommonAside.vue'
export default {
//import 引入的组件需要注入到对象中才能使用
components: { CommonAside },
props: {},
data() {
//这里存放数据
return {

};
},
//计算属性 类似于 data 概念
computed: {},
//监控 data 中的数据变化
watch: {},
//方法集合
methods: {

},
//生命周期 - 创建完成(可以访问当前 this 实例)
created() {

},
//生命周期 - 挂载完成(可以访问 DOM 元素)
mounted() {

},
beforeCreate() { }, //生命周期 - 创建之前
beforeMount() { }, //生命周期 - 挂载之前
beforeUpdate() { }, //生命周期 - 更新之前
updated() { }, //生命周期 - 更新之后
beforeDestroy() { }, //生命周期 - 销毁之前
destroyed() { }, //生命周期 - 销毁完成
activated() { }, //如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>
<style scoped></style>

修改侧边菜单,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
<template>
<el-menu default-active="1-4-1" class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose"
:collapse="isCollapse">
<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 index="1" 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>


<script>
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';

export default {
//import 引入的组件需要注入到对象中才能使用
components: {},
props: {},
data() {
//这里存放数据
return {
isCollapse: false,
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: "首页",
icon: "setting",
url: "Other/PageOne",
},
{
path: "/page2",
name: "page2",
label: "首页",
icon: "setting",
url: "Other/PageTwo",
},
],
},
]
};
},
methods: {
handleOpen(key, keyPath) {
console.log(key, keyPath);
},
handleClose(key, keyPath) {
console.log(key, keyPath);
}
},
//计算属性 类似于 data 概念
computed: {
//进行数据过滤
//有子菜单
noChildren(){
return this.menuData.filter(item=>!item.children);
},
//没有子菜单
hasChildren(){
return this.menuData.filter(item=>item.children);
}
},
//监控 data 中的数据变化
watch: {},
//方法集合
methods: {

},
//生命周期 - 创建完成(可以访问当前 this 实例)
created() {

},
//生命周期 - 挂载完成(可以访问 DOM 元素)
mounted() {

},
beforeCreate() { }, //生命周期 - 创建之前
beforeMount() { }, //生命周期 - 挂载之前
beforeUpdate() { }, //生命周期 - 更新之前
updated() { }, //生命周期 - 更新之后
beforeDestroy() { }, //生命周期 - 销毁之前
destroyed() { }, //生命周期 - 销毁完成
activated() { }, //如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>
<style scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
}
</style>

引用less解析器

1
npm i less@4.1.2 less-loader@6.0.0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<style lang="less" scoped >
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
}
.el-menu{
height: 100vh;
h3 {
color: #fff;
text-align: center;
line-height: 48px;
font-size: 16px;
font-weight: 400px;
}
}
</style>

清除默认样式,不然会出现贴边现象,在App.vue 下加入如下代码

1
2
3
4
5
6
<script lang="less">
html,body,h3{
margin:0;
padding:0
}
</script>

为新加的菜单添加路由,创建组件并且在index.js引入创建的组件并加入路由,(mall.vue,pageTow.vue,pageOne.vue)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import Vue from 'vue'
import VueRouter from 'vue-router'
//1.创建路由组件
import Home from '@/views/Home'
import User from '@/views/User'
import Main from '@/views/Main'
import Mall from '@/views/Mall'
import PageOne from '@/views/PageOne'
import PageTow from '@/views/PageTow'
Vue.use(VueRouter)

//2.将路由与组件进行映射
const routes = [
//主路由
{
path: '/',
component: Main,
redirect:'/home', //重定向
children: [
//子路由
{ path: 'home', component: Home },//首页
{ path: 'user', component: User },//用户管理
{ path: 'mall', component: Mall },//商品管理
{ path: 'page1', component: PageOne },
{ path: 'page2', component: PageTow }
]
}
]

//3. 创建 router 实例,然后传 `routes` 配置
const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
})

// 对外进行包入

export default router

(这里注意路由重定向)

目前,会出现一个错误,如果重复点击菜单,控制台会报重复跳转的错误,这里需要在点击菜单前判断(route表示为当前页面的路由,router为路由实例),在clickMenu方法修改为

1
2
3
4
5
6
7
8
clickMenu(item){
//页面跳转
//console.log(item);
//如果当前路由与跳转路由不一致,才跳转
if(this.$route.path!=item.path&&!(this.$route.path=='/home'&&(item.path=='/'))){
this.$router.push(item.path);
}
}

头部UI设计,创建CommonHeader.vue文件,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<template>
<div class="head-container">
<div class="l-content">
<el-button icon="el-icon-menu">
</el-button>
<span class="text">首页</span>
</div>
<div class="r-rontent">
<el-dropdown>
<span class="el-dropdown-link">
<img class="user" src="../assets/images/avart.jpg" 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>
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';

export default {
//import 引入的组件需要注入到对象中才能使用
components: {},
props: {},
data() {
//这里存放数据
return {

};
},
//计算属性 类似于 data 概念
computed: {},
//监控 data 中的数据变化
watch: {},
//方法集合
methods: {

},
//生命周期 - 创建完成(可以访问当前 this 实例)
created() {

},
//生命周期 - 挂载完成(可以访问 DOM 元素)
mounted() {

},
beforeCreate() { }, //生命周期 - 创建之前
beforeMount() { }, //生命周期 - 挂载之前
beforeUpdate() { }, //生命周期 - 更新之前
updated() { }, //生命周期 - 更新之后
beforeDestroy() { }, //生命周期 - 销毁之前
destroyed() { }, //生命周期 - 销毁完成
activated() { }, //如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>
<style lang="less" scoped>
.head-container {
padding-left: 20px;
height: 60px;
background-color: #333;
display: flex;
justify-content: space-between;
align-items: center;

.text {
font-size: 14px;
color: #fff;
padding: 10px;
}
.r-rontent{
.user{
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 20px;
}
}
}
</style>

创建完成后注意要在Main.vue中引入该头部组件。结果如下图:

结果

Vuex实现左侧菜单折叠

1
npm i vuex@3.6.2

创建store文件夹,再创建store.js和tab.js(用于菜单管理)代码分别为

1
2
3
4
5
6
7
8
9
10
11
12
import Vue from 'vue'
import Vuex from 'vuex'
import tab from './tab';

Vue.use(Vuex);// 全局注入

//创建vuex的实例 (对外包入)
export default new Vuex.Store({
modules:{
tab
}
})
1
2
3
4
5
6
7
8
9
10
11
12
//管理菜单相关的数据(es6语法,对象导出)
export default {
state: {
isCollapse: false //控制菜单的展开还是收起
},
mutations:{
//修改菜单展开和收起的方法
collapseMenu(state){
state.isCollapse=!state.isCollapse
}
}
}

再入口文件引入该组件

1
2
3
4
5
6
7
8
import store from './store'


new Vue({
router,
store,
render: h => h(App),
}).$mount('#app')

再头部组件的按钮加入click事件,用来出发store里的collapseMenu方法,改变isCollapse的值

1
2
3
handleMenu(){
this.$store.commit('collapseMenu')
}

在菜单组件中处理修改的值,需要要在computed下去处理数据,切记要删掉data里的isCollapse属性

1
2
3
isCollapse(){
return this.$store.state.tab.isCollapse
}

其他问题,在菜单测边框的h3标题下改为3元判断符

1
{{ isCollapse ? '后台' : "通用后台管理系统" }}

首页UI搭建

用户信息

使用element-ui的layout布局,保证页面自适应大小,并且使用card模板创建用户信息,头像img使用flex布局

flex 垂直居中

1
2
3
4
display: flex;
//纵轴
align-items: center;
margin-right: 40px;

用户信息代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<template>
<el-row>
<el-col :span="8">
<el-card class="box-card" shadow="hover">
<div class="user">
<img src="../assets/images/avart.jpg">
<div class="userinfo">
<p class="name">Admin</p>
<p class="root">超级管理员</p>
</div>
</div>
<div class="login-info">
<p>上次登录的时间:<span>2023-3-17</span></p>
<p>上次登录的地点:<span>武汉</span></p>
</div>
</el-card>
</el-col>
<el-col :span="16">
<div class="grid-content bg-purple-light"></div>
</el-col>
</el-row>
</template>


<script>
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';

export default {
//import 引入的组件需要注入到对象中才能使用
components: {},
props: {},
data() {
//这里存放数据
return {

};
},
//计算属性 类似于 data 概念
computed: {},
//监控 data 中的数据变化
watch: {},
//方法集合
methods: {

},
//生命周期 - 创建完成(可以访问当前 this 实例)
created() {

},
//生命周期 - 挂载完成(可以访问 DOM 元素)
mounted() {

},
beforeCreate() { }, //生命周期 - 创建之前
beforeMount() { }, //生命周期 - 挂载之前
beforeUpdate() { }, //生命周期 - 更新之前
updated() { }, //生命周期 - 更新之后
beforeDestroy() { }, //生命周期 - 销毁之前
destroyed() { }, //生命周期 - 销毁完成
activated() { }, //如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>
<style lang="less" scoped>
.user {
display: flex;
//纵轴
align-items: center;
margin-right: 40px;
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #ccc;
img {
width: 150px;
height: 150px;
border-radius: 50%;
}
.userinfo {
.name {
font-size: 32px;
margin-bottom: 10px;
margin-left: 30px;
}
.root {
color: #999999;
margin-left: 30px;
}
}
}
.login-info {
p {
line-height: 28px;
font-size: 14px;
color: #999999;
span {
margin-left: 60px;
}
}
}
</style>

结果如下:

结果

购买统计部分

使用elment-ui中的表格模板,并于v-for遍历tableData中的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
<template>
<el-row>
<el-col :span="8">
<el-card class="box-card" shadow="hover">
<div class="user">
<img src="../assets/images/avart.jpg">
<div class="userinfo">
<p class="name">Admin</p>
<p class="root">超级管理员</p>
</div>
</div>
<div class="login-info">
<p>上次登录的时间:<span>2023-3-17</span></p>
<p>上次登录的地点:<span>武汉</span></p>
</div>
</el-card>
<el-card class="box-card" style="margin-top: 20px;height: 460px;">
<el-table :data="tableData" style="width: 100%">
<el-table-column v-for="(key,val) in tableLabel" :prop="val" :label="key" >
</el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :span="16">
<div class="grid-content bg-purple-light"></div>
</el-col>
</el-row>
</template>


<script>
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';

export default {
//import 引入的组件需要注入到对象中才能使用
components: {},
props: {},
data() {
//这里存放数据
return {
tableData: [
{
name: 'oppo',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
},
{
name: 'vivo',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
},
{
name: '苹果',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
},
{
name: '小米',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
},
{
name: '三星',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
},
{
name: '魅族',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
}
],
tableLabel:{
name:'课程',
todayBuy:'今日购买',
monthBuy:'本月购买',
totalBuy:'总购买'
}
};
},
//计算属性 类似于 data 概念
computed: {},
//监控 data 中的数据变化
watch: {},
//方法集合
methods: {

},
//生命周期 - 创建完成(可以访问当前 this 实例)
created() {

},
//生命周期 - 挂载完成(可以访问 DOM 元素)
mounted() {

},
beforeCreate() { }, //生命周期 - 创建之前
beforeMount() { }, //生命周期 - 挂载之前
beforeUpdate() { }, //生命周期 - 更新之前
updated() { }, //生命周期 - 更新之后
beforeDestroy() { }, //生命周期 - 销毁之前
destroyed() { }, //生命周期 - 销毁完成
activated() { }, //如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>
<style lang="less" scoped>
.user {
display: flex;
//纵轴
align-items: center;
margin-right: 40px;
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #ccc;

img {
width: 150px;
height: 150px;
border-radius: 50%;
}

.userinfo {
.name {
font-size: 32px;
margin-bottom: 10px;
margin-left: 30px;
}

.root {
color: #999999;
margin-left: 30px;
}
}
}

.login-info {
p {
line-height: 28px;
font-size: 14px;
color: #999999;

span {
margin-left: 60px;
}
}
}
</style>

结果如下图:

结果

订单统计实现

这里需要左右布局,这里可以想到flex布局,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
<template>
<el-row>
<el-col :span="8">
<el-card class="box-card" shadow="hover">
<div class="user">
<img src="../assets/images/avart.jpg">
<div class="userinfo">
<p class="name">Admin</p>
<p class="root">超级管理员</p>
</div>
</div>
<div class="login-info">
<p>上次登录的时间:<span>2023-3-17</span></p>
<p>上次登录的地点:<span>武汉</span></p>
</div>
</el-card>
<el-card class="box-card" style="margin-top: 20px;height: 460px;">
<el-table :data="tableData" style="width: 100%">
<el-table-column v-for="(key,val) in tableLabel" :prop="val" :label="key" :key="key+''">
</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="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-col>
</el-row>
</template>


<script>
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';

export default {
//import 引入的组件需要注入到对象中才能使用
components: {},
props: {},
data() {
//这里存放数据
return {
tableData: [
{
name: 'oppo',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
},
{
name: 'vivo',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
},
{
name: '苹果',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
},
{
name: '小米',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
},
{
name: '三星',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
},
{
name: '魅族',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
}
],
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",
},
]
};
},
//计算属性 类似于 data 概念
computed: {},
//监控 data 中的数据变化
watch: {},
//方法集合
methods: {

},
//生命周期 - 创建完成(可以访问当前 this 实例)
created() {

},
//生命周期 - 挂载完成(可以访问 DOM 元素)
mounted() {

},
beforeCreate() { }, //生命周期 - 创建之前
beforeMount() { }, //生命周期 - 挂载之前
beforeUpdate() { }, //生命周期 - 更新之前
updated() { }, //生命周期 - 更新之后
beforeDestroy() { }, //生命周期 - 销毁之前
destroyed() { }, //生命周期 - 销毁完成
activated() { }, //如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>
<style lang="less" scoped>
.user {
display: flex;
//纵轴
align-items: center;
margin-right: 40px;
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #ccc;

img {
width: 150px;
height: 150px;
border-radius: 50%;
}

.userinfo {
.name {
font-size: 32px;
margin-bottom: 10px;
margin-left: 30px;
}

.root {
color: #999999;
margin-left: 30px;
}
}
}

.login-info {
p {
line-height: 28px;
font-size: 14px;
color: #999999;

span {
margin-left: 60px;
}
}
}
.num {
margin-left: 20px;
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;
.price{
font-size: 30px;
margin-bottom: 10px;
margin-left: 30px;
line-height: 30px;
height: 30px;
}
.desc {
font-size: 14px;
text-align: center;
color:#999999;
margin-left: 30px;
}
}
.el-card {
width: 32%;
margin-bottom: 20px;
}
}
</style>

结果如下图:

结果

axios的基本使用

axios安装

1
npm install axios

axios的封装

axios实例以及拦截器的简单封装,在src文件夹创建utils文件夹,并且再次文件下创建request.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import axios from "axios"

const http=axios.create({
//通用请求的地址前缀
baseURL:'/api',
//超时时间
timeout:10000,

});

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});


export default http

封装api,在src文件夹创建api文件夹,并且再次文件下创建index.js

1
2
3
4
5
6
7
8
import http from '@/utils/request'

//请求首页数据
export const getData =()=>{
//返回一个promise对象
return http.get('/home/getData');

}

在home.vue 中使用 封装好的api

1
2
3
4
5
6
7
8
import {getData} from '../api'


created() {
getData().then((data)=>{
console.log(data);
});
},

结果如下:

结果

mock数据模拟

mock.js的安装

1
npm i mockj 

在api文件夹下创建mock.js,同时在api文件夹下创建mockServeData文件夹,并创建home.js文件用于返回首页数据,同时在入口文件引入mock.js

mock.js:

1
2
3
4
import Mock from 'mockjs'
import homeApi from './mockServeData/home'
//定义mock请求拦截
Mock.mock('/api/home/getData','get',homeApi.getStatisticalData);

home.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//首页数据
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
}
]
}
}
}
}

入口文件引入

1
import './api/mock'

结果展示:

结果

首页可视化图表样式

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
<template>
<el-row>
<el-col :span="8">
<el-card class="box-card" shadow="hover">
<div class="user">
<img src="../assets/images/avart.jpg">
<div class="userinfo">
<p class="name">Admin</p>
<p class="root">超级管理员</p>
</div>
</div>
<div class="login-info">
<p>上次登录的时间:<span>2023-3-17</span></p>
<p>上次登录的地点:<span>武汉</span></p>
</div>
</el-card>
<el-card class="box-card" style="margin-top: 20px;height: 460px;">
<el-table :data="tableData" style="width: 100%">
<el-table-column v-for="(key, val) in tableLabel" :prop="val" :label="key" :key="key + ''">
</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="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;margin-left: 20px;">
<!--折线图-->
</el-card>
<div class="graph">
<el-card style="height: 260px;"></el-card>
<el-card style="height: 260px;"></el-card>
</div>
</el-col>
</el-row>
</template>


<script>
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
import { getData } from '../api'
export default {
//import 引入的组件需要注入到对象中才能使用
components: {},
props: {},
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",
},
]
};
},
//计算属性 类似于 data 概念
computed: {},
//监控 data 中的数据变化
watch: {},
//方法集合
methods: {

},
//生命周期 - 创建完成(可以访问当前 this 实例)
created() {
getData().then(({ data }) => {
const { tableData } = data.data;
this.tableData = tableData;
});
},
//生命周期 - 挂载完成(可以访问 DOM 元素)
mounted() {

},
beforeCreate() { }, //生命周期 - 创建之前
beforeMount() { }, //生命周期 - 挂载之前
beforeUpdate() { }, //生命周期 - 更新之前
updated() { }, //生命周期 - 更新之后
beforeDestroy() { }, //生命周期 - 销毁之前
destroyed() { }, //生命周期 - 销毁完成
activated() { }, //如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>
<style lang="less" scoped>
.user {
display: flex;
//纵轴
align-items: center;
margin-right: 40px;
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #ccc;

img {
width: 150px;
height: 150px;
border-radius: 50%;
}

.userinfo {
.name {
font-size: 32px;
margin-bottom: 10px;
margin-left: 30px;
}

.root {
color: #999999;
margin-left: 30px;
}
}
}

.login-info {
p {
line-height: 28px;
font-size: 14px;
color: #999999;

span {
margin-left: 60px;
}
}
}

.num {
margin-left: 20px;
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;

.price {
font-size: 30px;
margin-bottom: 10px;
margin-left: 30px;
line-height: 30px;
height: 30px;
}

.desc {
font-size: 14px;
text-align: center;
color: #999999;
margin-left: 30px;
}
}

.el-card {
width: 32%;
margin-bottom: 20px;
}
}
.graph {
margin-left: 20px;
margin-top: 20px;
display: flex;
justify-content: space-between; //左右贴边
.el-card{
width: 48%;
}
}
</style>

echarts表格使用

安装

1
npm i echarts@5.1.2

引入依赖

1
import * as echarts from 'echarts'

折线图设计,代码如下(详情参考Apache ECharts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
<template>
<el-row>
<el-col :span="8">
<el-card class="box-card" shadow="hover">
<div class="user">
<img src="../assets/images/avart.jpg">
<div class="userinfo">
<p class="name">Admin</p>
<p class="root">超级管理员</p>
</div>
</div>
<div class="login-info">
<p>上次登录的时间:<span>2023-3-17</span></p>
<p>上次登录的地点:<span>武汉</span></p>
</div>
</el-card>
<el-card class="box-card" style="margin-top: 20px;height: 400px;">
<el-table :data="tableData" style="width: 100%">
<el-table-column v-for="(key, val) in tableLabel" :prop="val" :label="key" :key="key + ''">
</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="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;margin-left: 20px;">
<!--折线图-->
<div ref="echarts1" style="height: 280px;"></div>
</el-card>
<div class="graph">
<el-card style="height: 200px;"></el-card>
<el-card style="height: 200px;"></el-card>
</div>
</el-col>
</el-row>
</template>


<script>
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
import { getData } from '../api'
//引入echarts
import * as echarts from 'echarts'
export default {
//import 引入的组件需要注入到对象中才能使用
components: {},
props: {},
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",
},
],
};
},
//计算属性 类似于 data 概念
computed: {},
//监控 data 中的数据变化
watch: {},
//方法集合
methods: {

},
//生命周期 - 创建完成(可以访问当前 this 实例)
created() {
getData().then(({ data }) => {
const { tableData } = data.data;
this.tableData = tableData;
//console.log(data);
});
},
//生命周期 - 挂载完成(可以访问 DOM 元素)
mounted() {
getData().then(({ data }) => {
const echarts1 = echarts.init(this.$refs.echarts1);
//指定图表的配置项和数据
var echarts1Option = { };
//处理xAxis
const { orderData } = data.data;
const xAxis=Object.keys(orderData.data[0]);
echarts1Option.xAxis={
data:xAxis
};
//处理图例
echarts1Option.legend={
data:xAxis
}
echarts1Option.yAxis={
data:[]
}
echarts1Option.series=[];
xAxis.forEach(key=>{
echarts1Option.series.push({
name:key,
data:orderData.data.map(item=>item[key]),
type:'line'
})
})
console.log(echarts1Option);
console.log(orderData);
//根据配置显示图表
echarts1.setOption(echarts1Option);
});
},
beforeCreate() { }, //生命周期 - 创建之前
beforeMount() { }, //生命周期 - 挂载之前
beforeUpdate() { }, //生命周期 - 更新之前
updated() { }, //生命周期 - 更新之后
beforeDestroy() { }, //生命周期 - 销毁之前
destroyed() { }, //生命周期 - 销毁完成
activated() { }, //如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>
<style lang="less" scoped>
.user {
display: flex;
//纵轴
align-items: center;
margin-right: 40px;
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #ccc;

img {
width: 150px;
height: 150px;
border-radius: 50%;
}

.userinfo {
.name {
font-size: 32px;
margin-bottom: 10px;
margin-left: 30px;
}

.root {
color: #999999;
margin-left: 30px;
}
}
}

.login-info {
p {
line-height: 28px;
font-size: 14px;
color: #999999;

span {
margin-left: 60px;
}
}
}

.num {
margin-left: 20px;
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;

.price {
font-size: 30px;
margin-bottom: 10px;
margin-left: 30px;
line-height: 30px;
height: 30px;
}

.desc {
font-size: 14px;
text-align: center;
color: #999999;
margin-left: 30px;
}
}

.el-card {
width: 32%;
margin-bottom: 20px;
}
}

.graph {
margin-left: 20px;
margin-top: 20px;
display: flex;
justify-content: space-between; //左右贴边

.el-card {
width: 48%;
}
}
</style>

结果展示:

结果

其余柱状图和饼状图代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
<template>
<el-row>
<el-col :span="8">
<el-card class="box-card" shadow="hover">
<div class="user">
<img src="../assets/images/avart.jpg">
<div class="userinfo">
<p class="name">Admin</p>
<p class="root">超级管理员</p>
</div>
</div>
<div class="login-info">
<p>上次登录的时间:<span>2023-3-17</span></p>
<p>上次登录的地点:<span>武汉</span></p>
</div>
</el-card>
<el-card class="box-card" style="margin-top: 20px;height: 400px;">
<el-table :data="tableData" style="width: 100%">
<el-table-column v-for="(key, val) in tableLabel" :prop="val" :label="key" :key="key + ''">
</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="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;margin-left: 20px;">
<!--折线图-->
<div ref="echarts1" style="height: 280px;"></div>
</el-card>
<div class="graph">
<el-card style="height: 200px;">
<div ref="echarts2" style="height: 200px;"></div>
</el-card>
<el-card style="height: 200px;">
<div ref="echarts3" style="height: 180px;"></div>
</el-card>
</div>
</el-col>
</el-row>
</template>


<script>
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
import { getData } from '../api'
//引入echarts
import * as echarts from 'echarts'
export default {
//import 引入的组件需要注入到对象中才能使用
components: {},
props: {},
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",
},
],
};
},
//计算属性 类似于 data 概念
computed: {},
//监控 data 中的数据变化
watch: {},
//方法集合
methods: {

},
//生命周期 - 创建完成(可以访问当前 this 实例)
created() {
getData().then(({ data }) => {
const { tableData } = data.data;
this.tableData = tableData;
//console.log(data);
});
},
//生命周期 - 挂载完成(可以访问 DOM 元素)
mounted() {
getData().then(({ data }) => {
const echarts1 = echarts.init(this.$refs.echarts1);
//指定图表的配置项和数据
var echarts1Option = {};
//处理xAxis
const { orderData, userData,videoData } = data.data;
const xAxis = Object.keys(orderData.data[0]);
echarts1Option.xAxis = {
data: xAxis
};
//处理图例
echarts1Option.legend = {
data: xAxis
}
echarts1Option.yAxis = {
data: []
}
echarts1Option.series = [];
xAxis.forEach(key => {
echarts1Option.series.push({
name: key,
data: orderData.data.map(item => item[key]),
type: 'line'
})
})
//console.log(echarts1Option);
console.log(data.data);
//根据配置显示图表
echarts1.setOption(echarts1Option);
//柱状图
const echarts2 = echarts.init(this.$refs.echarts2);
var 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);
var echarts3Option = {
tooltip: {
trigger: "item",
},
color: [
"#0f78f4",
"#dd536b",
"#9462e5",
"#a6a6a6",
"#e1bb22",
"#39c362",
"#3ed1cf",
],
series: [
{
data:videoData,
type:'pie'
}
]
}
echarts3.setOption(echarts3Option);
});
},
beforeCreate() { }, //生命周期 - 创建之前
beforeMount() { }, //生命周期 - 挂载之前
beforeUpdate() { }, //生命周期 - 更新之前
updated() { }, //生命周期 - 更新之后
beforeDestroy() { }, //生命周期 - 销毁之前
destroyed() { }, //生命周期 - 销毁完成
activated() { }, //如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>
<style lang="less" scoped>
.user {
display: flex;
//纵轴
align-items: center;
margin-right: 40px;
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #ccc;

img {
width: 150px;
height: 150px;
border-radius: 50%;
}

.userinfo {
.name {
font-size: 32px;
margin-bottom: 10px;
margin-left: 30px;
}

.root {
color: #999999;
margin-left: 30px;
}
}
}

.login-info {
p {
line-height: 28px;
font-size: 14px;
color: #999999;

span {
margin-left: 60px;
}
}
}

.num {
margin-left: 20px;
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;

.price {
font-size: 30px;
margin-bottom: 10px;
margin-left: 30px;
line-height: 30px;
height: 30px;
}

.desc {
font-size: 14px;
text-align: center;
color: #999999;
margin-left: 30px;
}
}

.el-card {
width: 32%;
margin-bottom: 20px;
}
}

.graph {
margin-left: 20px;
margin-top: 20px;
display: flex;
justify-content: space-between; //左右贴边

.el-card {
width: 48%;
}
}
</style>

首页完成,结果如下:

结果

头部面包屑设计

在Commonheader .vue加入

1
2
3
4
<!--面包屑-->
<el-breadcrumb separator="/">
<el-breadcrumb-item v-for="item in tags" :key="item.path" :to="{ path: item.path }">{{ item.label }}</el-breadcrumb-item>
</el-breadcrumb>

使用vuex管理表头面包屑的数组,在store文件下的tab.js中的state加入tabList,用来获取菜单路由,进行增加,删除。

1
2
3
4
5
6
7
8
9
tabList: [
{
path: "/",
name: "home",
label: "首页",
icon: "s-home",
url: "Home/Home",
},
] //面包

在 mutations加入更新面包屑方法,其主要操作tabList

1
2
3
4
5
6
7
8
9
10
11
//更新面包屑数据(val为传进的数据)
selectMenu(state, val) {
console.log(val);
//判断添加的数据是否为默认state(首页)
if (val.name != 'home') {
const index = state.tabList.findIndex(item => item.name == val.name);
if (index == -1) {
state.tabList.push(val);
}
}
},

在Commonheader .vue获取tabList

1
2
3
4
5
6
7
import { mapState } from 'vuex';
computed: {
//es6 语法 解构
...mapState({
tags :state=>state.tab.tabList
})
},

导航栏tag设计

组件加入CommonTags

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<template>
<div class="tags">
<el-tag v-for=" (item, index) in tags" :key="item.path" :effect="$route.name == item.name ? 'dark' : 'plain'"
:closable="item.name != 'home'" @click="changedMenu(item)" @close="closeMenu(item, index)" size="small">
{{ item.label }}
</el-tag>
</div>
</template>


<script>
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
import { mapState, mapMutations } from 'vuex';
export default {
//import 引入的组件需要注入到对象中才能使用
components: {},
props: {},
data() {
//这里存放数据
return {

};
},
//计算属性 类似于 data 概念
computed: {
...mapState({
tags: state => state.tab.tabList
})
},
//监控 data 中的数据变化
watch: {},
//方法集合
methods: {
...mapMutations({
close:'closeTags'
}),

//点击tag跳转的功能
changedMenu(item) {
//路由跳转的第二种方式
console.log(item);
this.$router.push({ name: item.name });
},

closeMenu(item, index) {
const length = this.tags.length-1;
this.close(item);
//另外一种调用store中的方法(辅助函数)

console.log(this.tags,index);
//删除之后的跳转逻辑
if (item.name !== this.$route.name) {
return;
}
//表示的是删除最后一项
if (index == length) {
this.$router.push({
name: this.tags[index - 1].name
})
} else {
this.$router.push({
name: this.tags[index].name
})
}
}
},
//生命周期 - 创建完成(可以访问当前 this 实例)
created() {

},
//生命周期 - 挂载完成(可以访问 DOM 元素)
mounted() {

},
beforeCreate() { }, //生命周期 - 创建之前
beforeMount() { }, //生命周期 - 挂载之前
beforeUpdate() { }, //生命周期 - 更新之前
updated() { }, //生命周期 - 更新之后
beforeDestroy() { }, //生命周期 - 销毁之前
destroyed() { }, //生命周期 - 销毁完成
activated() { }, //如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>
<style lang="less" scoped>
.tags {
margin-left: 220px;
margin-top: 5px;

.el-tag {
margin-right: 15px;
cursor: pointer; //小手的样式
}
}
</style>

其更改导航tag仍使用vuex经行管理

1
2
3
4
5
6
//删除指定的tags数据
closeTags(state, item) {

const index = state.tabList.findIndex(val => val.name == item.name);
state.tabList.splice(index, 1);
}

结果展示

home8

用户管理页面功能

新增用户及新增用户表单验证

使用element-ui的表单及dialog对话框去显示新增页面,其对话框嵌套表单实现新增

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 <div class="manage-header">
<el-button @click="dialogVisible = true" type="primary">
+新增
</el-button>
</div>


<el-dialog title="新增用户" :visible.sync="dialogVisible" width="40%" :before-close="handleClose">
<el-form ref="form" :model="form" label-width="80px" :inline="true" :rules="rules">
<el-form-item label=" 姓名" prop="name">
<el-input v-model="form.name" placeholder="请输入姓名"></el-input>
</el-form-item>
<el-form-item label="年龄" prop="age">
<el-input v-model="form.age" placeholder="请输入姓名"></el-input>
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-select v-model="form.sex" placeholder="请选择性别">
<el-option label="男" value="1"></el-option>
<el-option label="女" value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item label="出生日期" prop="birth">
<el-date-picker v-model="form.birth" type="date" placeholder="选择日期">
</el-date-picker>
</el-form-item>
<el-form-item label="地址" width="100%" prop="addr">
<el-input v-model="form.addr" placeholder="请输入地址" style="width: 100%;"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="cancel">取 消</el-button>
<el-button type="primary" @click="submit()">确 定</el-button>
</span>
</el-dialog>

data内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
dialogVisible: false,
form: {
name: '',
age: '',
sex: '',
birth: '',
addr: ''
},
rules: {
name: [
{
required: true, message: "请输入姓名", trigger: 'blur'
}
],
age: [
{
required: true, message: "请输入年龄", trigger: 'blur'
}
],
sex: [
{
required: true, message: "请选择性别", trigger: 'blur'
}
],
birth: [
{
required: true, message: "请选择出生日期", trigger: 'blur'
}
],
addr: [
{
required: true, message: "请输入地址", trigger: 'blur'
}
],
},

方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//提交用户表单
submit() {
this.$refs.form.validate((valid) => {
if (valid) {
//后续对表单数据的处理

//关闭弹弹窗
this.dialogVisible = false;
//清空form数据
this.$refs.form.resetFields();
}
});
},
//弹窗关闭前触发的方法
handleClose() {
this.$refs.form.resetFields();
this.dialogVisible = false;
},
//点击取消出发的方法
cancel() {
this.handleClose();
}
},

结果如图:

结果

表格数据展示

使用mock.js模拟后端系统发送请求获取数据,其内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import Mock from 'mockjs'

// get请求从config.url获取参数,post从config.body中获取参数
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 {
/**
* 获取列表
* 要带参数 name, page, limt; name可以不填, page,limit有默认值。
* @param name, page, limit
* @return {{code: number, count: number, data: *[]}}
*/
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
}
},
/**
* 增加用户
* @param name, addr, age, birth, sex
* @return {{code: number, data: {message: string}}}
*/
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: '添加成功'
}
}
},
/**
* 删除用户
* @param id
* @return {*}
*/
deleteUser: config => {
const { id } = param2Obj(config.url)
if (!id) {
return {
code: -999,
message: '参数不正确'
}
} else {
List = List.filter(u => u.id !== id)
return {
code: 20000,
message: '删除成功'
}
}
},
/**
* 批量删除
* @param config
* @return {{code: number, data: {message: string}}}
*/
batchremove: config => {
let { ids } = param2Obj(config.url)
ids = ids.split(',')
List = List.filter(u => !ids.includes(u.id))
return {
code: 20000,
data: {
message: '批量删除成功'
}
}
},
/**
* 修改用户
* @param id, name, addr, age, birth, sex
* @return {{code: number, data: {message: string}}}
*/
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: '编辑成功'
}
}
}
}

其mock定义请求路径:

1
2
3
4
5
//用户列表数据
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/getUser','get', user.getUserList);

在index.js下定义api接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import http from '@/utils/request'

//请求首页数据
export const getData = () => {
//返回一个promise对象
return http.get('/home/getData');

}

export const getUser = (params) => {
//返回用户列表
return http.get('/user/getUser',params)
}

export const addUser = (data) => {
return http.post('/user/add', data)
}

export const editUser = (data) => {
return http.post('/user/edit', data)
}

export const deltUser = (data) => {
return http.post('/user/del', data)
}

在user.vue在created生命周期下定义方法,返回表格数据:

1
2
3
4
5
created() {
getUser().then(({ data }) => {
this.tableData = data.list;
});
},

新增,删除,修改,分页方法代码如下:

新增:

1
2
3
4
handleAdd() {
this.modalType = 0;
this.dialogVisible = true;
},

删除:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
handleDel(row) {
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deltUser(row.id).then(() => {
this.$message({
type: 'success',
message: '删除成功!'
});
this.getList();
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},

修改:

1
2
3
4
5
6
handleEdit(row) {
this, this.modelTape = 1;
this.dialogVisible = true;
this.form = JSON.parse(JSON.stringify(row)) //深拷贝 如果直接传row会直接修改数据

},

分页:

1
2
3
4
handlePage(val) {
this.pageData.page = val;
this.getList();
},

查询表格:

1
2
3
4
//列表的查询
onSubmit(){
this.getList();
}

此处getList()是对获取列表数据的封装:

1
2
3
4
5
6
getList() {
getUser({ params: { ...this.userForm, ...this.pageData } }).then(({ data }) => {
this.tableData = data.list;
this.total = data.count || 0;
});
},

其user.vue 最终代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
<template>
<div class="manage">
<div class="manage-header" style="margin-bottom: 10px;">
<el-button @click="handleAdd()" type="primary">
+新增
</el-button>
<el-form :model="userForm" :inline="true">
<el-form-item label="请输入姓名">
<el-input v-model="userForm.name" placeholder="请输入姓名"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">查询</el-button>
</el-form-item>
</el-form>
</div>
<div class="common-table">
<el-table :data="tableData" style="width: 100%" height="90%" stripe>
<el-table-column prop="name" label="姓名">
</el-table-column>
<el-table-column prop="sex" label="性别">
<template slot-scope="scope">
<span>{{ scope.row.sex == 1 ? '男' : '女' }}</span>
</template>
</el-table-column>
<el-table-column prop="age" label="地址">
</el-table-column>
<el-table-column prop="birth" label="出生日期">
</el-table-column>
<el-table-column prop="addr" label="地址">
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="danger" size="mini" @click="handleDel(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pager">
<el-pagination layout="prev, pager, next" :total="total" @current-change="handlePage">
</el-pagination>
</div>
</div>
<el-dialog title="新增用户" :visible.sync="dialogVisible" width="40%" :before-close="handleClose">
<el-form ref="form" :model="form" label-width="80px" :inline="true" :rules="rules">
<el-form-item label=" 姓名" prop="name">
<el-input v-model="form.name" placeholder="请输入姓名"></el-input>
</el-form-item>
<el-form-item label="年龄" prop="age">
<el-input v-model="form.age" placeholder="请输入姓名"></el-input>
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-select v-model="form.sex" placeholder="请选择性别">
<el-option label="男" :value="1"></el-option>
<el-option label="女" :value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item label="出生日期" prop="birth">
<el-date-picker v-model="form.birth" type="date" placeholder="选择日期" value-format="yyyy-MM-dd">
</el-date-picker>
</el-form-item>
<el-form-item label="地址" width="100%" prop="addr">
<el-input v-model="form.addr" placeholder="请输入地址" style="width: 100%;"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="cancel">取 消</el-button>
<el-button type="primary" @click="submit()">确 定</el-button>
</span>
</el-dialog>
</div>
</template>


<script>
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
import { getUser, editUser, addUser, deltUser } from '../api'
export default {
//import 引入的组件需要注入到对象中才能使用
components: {},
props: {},
data() {
//这里存放数据
return {
dialogVisible: false,
form: {
name: '',
age: '',
sex: '',
birth: '',
addr: ''
},
rules: {
name: [
{
required: true, message: "请输入姓名", trigger: 'blur'
}
],
age: [
{
required: true, message: "请输入年龄", trigger: 'blur'
}
],
sex: [
{
required: true, message: "请选择性别", trigger: 'blur'
}
],
birth: [
{
required: true, message: "请选择出生日期", trigger: 'blur'
}
],
addr: [
{
required: true, message: "请输入地址", trigger: 'blur'
}
],
},
tableData: [

],
modelTape: 0,//0表示新增1弹窗,1表示修改
total: 0,
pageData: {
page: 1,
limit: 10
},
userForm: {
name: ''
}

};
},
//计算属性 类似于 data 概念
computed: {},
//监控 data 中的数据变化
watch: {},
//方法集合
methods: {

//提交用户表单
submit() {
this.$refs.form.validate((valid) => {
if (valid) {
//后续对表单数据的处理
if (this.modelTape == 0) {
addUser(this.form).then(() => {
//刷新当前列表
this.getList();
})
} else {
editUser(this.form).then(() => {
//刷新当前列表
this.getList();
})
}
//关闭弹弹窗
this.dialogVisible = false;
//清空form数据
this.$refs.form.resetFields();
}
});
},
//弹窗关闭前触发的方法
handleClose() {
this.$refs.form.resetFields();
this.dialogVisible = false;
},
//点击取消出发的方法
cancel() {
this.handleClose();
},
// 获取数据公共方法
getList() {
getUser({ params: { ...this.userForm, ...this.pageData } }).then(({ data }) => {
this.tableData = data.list;
this.total = data.count || 0;
});
},
handleAdd() {
this.modalType = 0;
this.dialogVisible = true;
},
handleEdit(row) {
this, this.modelTape = 1;
this.dialogVisible = true;
this.form = JSON.parse(JSON.stringify(row)) //深拷贝 如果直接传row会直接修改数据

},
handleDel(row) {
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deltUser(row.id).then(() => {
this.$message({
type: 'success',
message: '删除成功!'
});
this.getList();
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
handlePage(val) {
this.pageData.page = val;
this.getList();
},
//列表的查询
onSubmit(){
this.getList();
}
},
//生命周期 - 创建完成(可以访问当前 this 实例)
created() {
this.getList();
},
//生命周期 - 挂载完成(可以访问 DOM 元素)
mounted() {

},
beforeCreate() { }, //生命周期 - 创建之前
beforeMount() { }, //生命周期 - 挂载之前
beforeUpdate() { }, //生命周期 - 更新之前
updated() { }, //生命周期 - 更新之后
beforeDestroy() { }, //生命周期 - 销毁之前
destroyed() { }, //生命周期 - 销毁完成
activated() { }, //如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>
<style lang="less" scoped>
.manage {
height: 90%;
.manage-header{
display: flex;
justify-content: space-between;
align-items: center;
}
.common-table {
position: relative;
height: calc(100% - 62px);

.pager {
position: absolute;
bottom: 0;
right: 20px;
}
}
}
</style>

结果如图:

结果

登录功能

登陆页面样式设计,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<template>
<div>
<el-form lable-width="70px" :model="form" :rules="rules" class="login-container" :inline="true">
<h3 class="login_title">系统登录</h3>
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input style="margin-left: 15px;" type="password" v-model="form.password" placeholder="请输入密码"></el-input>
</el-form-item>
<el-form-item>
<el-button style="margin-left: 105px;margin-top: 20px;" type="primary">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>


<script>
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';

export default {
//import 引入的组件需要注入到对象中才能使用
components: {},
props: {},
data() {
//这里存放数据
return {
form: {
username: '',
password: ''
},
rules: {
username: [
{ required: true, trigger: 'blur', message: "请输入用户名" }
],
password: [
{ required: true, trigger: 'blur', message: "请输入密码" }
]
}
};
},
//计算属性 类似于 data 概念
computed: {},
//监控 data 中的数据变化
watch: {},
//方法集合
methods: {

},
//生命周期 - 创建完成(可以访问当前 this 实例)
created() {

},
//生命周期 - 挂载完成(可以访问 DOM 元素)
mounted() {

},
beforeCreate() { }, //生命周期 - 创建之前
beforeMount() { }, //生命周期 - 挂载之前
beforeUpdate() { }, //生命周期 - 更新之前
updated() { }, //生命周期 - 更新之后
beforeDestroy() { }, //生命周期 - 销毁之前
destroyed() { }, //生命周期 - 销毁完成
activated() { }, //如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>
<style lang="less" scoped>
.login-container {
width: 350px;
border: 1px solid #eaeaea;
margin-left: 40%;
margin-top: 15%;
padding: 35px 35px 15px 35px;
background-color: #fff;
border-radius: 15px;
box-shadow: 0 0 25px #cac6c6;
box-sizing:border-box;
.el-input {
width: 180px;

}
.login_title{
text-align: center;
margin-bottom: 40px;
color: #333;
}

}
</style>

结果如图:

登录样式

token

安装cookie

1
npm i js-cookie@3.0.1
1
2
3
4
5
6
7
8
9
10
//登陆方法
submit(){
//token 信息
//此处会获取随机数
const token=Mock.Random.guid();
// token 信息存入cookie用于不同页面间的通信
Cookie.set('token',token);
//跳转到首页
this.$router.push('home');
}

使用全局前置导航守卫并配置到入口文件下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//添加全局前置导航
router.beforeEach((to, from, next) => {
//token存不存在
const token = Cookies.get('token');
if (!token && to.name != 'login') {
//token不存在跳转登录页且當前頁面不是login
next({ name: 'login' })
} else if (token && to.name == 'login') {
//已经登录,跳转到首页
next({ name: 'home' })
} else {
next()
}
})

目前已经可以实现跳转,但是登录数据是写死的,这里通过mock模拟后端接口发送token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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: '/',
name: 'home',
label: '首页',
icon: 's-home',
url: 'home/index'
},
{
path: '/mall',
name: 'mall',
label: '商品管理',
icon: 'video-play',
url: 'mall/index'
},
{
path: '/user',
name: 'user',
label: '用户管理',
icon: 'user',
url: 'User/index'
},
{
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'
}
]
}
],
token: Mock.Random.guid(),
message: '获取成功'
}
}
} else if (username === 'xiaoxiao' && password === 'xiaoxiao') {
return {
code: 20000,
data: {
menu: [
{
path: '/',
name: 'home',
label: '首页',
icon: 's-home',
url: 'home/index'
},
{
path: '/video',
name: 'video',
label: '商品管理',
icon: 'video-play',
url: 'mall/index'
}
],
token: Mock.Random.guid(),
message: '获取成功'
}
}
} else {
return {
code: -999,
data: {
message: '密码错误'
}
}
}

}
}

配置mock数据拦截器

1
Mock.mock(/api\/premission\/getMenu/,'post',permission.getMenu)

定义api接口

1
2
3
export const getMenu =(data) =>{
return http.post('/premission/getMenu',data);
}

重新修改登录方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//登陆方法
submit() {
//token 信息
//此处会获取随机数
//const token=Mock.Random.guid();
// token 信息存入cookie用于不同页面间的通信
//Cookie.set('token',token);
//跳转到首页

//表单校验
this.$refs.form.validate((valid) => {
if (valid) {
getMenu(this.form).then(({ data }) => {
if (data.code == 20000) {
Cookie.set('token', data.data.token)
this.$router.push('home');
} else {
this.$message({
showClose: true,
message: data.data.message,
type: 'error'
});
}
})
}
})
}

在头部组件的下拉框加入退出功能:

1
2
3
4
5
6
7
8
9
handleClick(command) {
if (command == 'cancel') {
//清楚cookie的token信息
Cookie.remove('token');
//跳转登录页面
this.$router.push('login');
}

}

mock模拟后端发送数据进行登录跳转。

菜单权限功能

  1. 不同登录的账号,会有不同的菜单权限
  2. 通过输入url地址来显示页面
  3. 对于菜单的数据在不同页面之间的数据通信

在store中定义菜单数据:

1
2
3
4
5
6
7
8
menu:[

]

//设置menu的数据
setMenu(state,val){
state.menu=val;
}

在login的登录方法下添加该功能:

1
2
//获取菜单数据,存入store中
this.$store.commit('setMenu',data.data.menu)

在公共菜单组件下的computed方法下获取动态菜单数据

1
2
3
menuData(){
return this.$store.state.tab.menu;
}

删除原来固定的菜单组件,改用获取动态menuData。

通过Cookie保存保存页面数据完成不同页面之间的通信。

修改setMenu方法及获取menuData的计算方法:

1
2
3
4
5
//设置menu的数据
setMenu(state,val){
state.menu=val;
Cookies.set('menu',JSON.stringify(val));
}
1
2
3
4
5
menuData(){
//如果缓存中没有从当前store中获取

return JSON.parse(Cookies.get('menu'))||this.$store.state.tab.menu;
}

动态注册路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//动态注册路由
addMenu(state, router) {
//判断缓存中是否有数据
if (!Cookies.get('menu')) {
return
}
const menu = JSON.parse(Cookies.get('menu'))
state.menu = menu
//处理动态路由的数据
const menuArry = [];
menu.forEach(item => {
if (item.children) {
item.children = item.children.map(item => {
item.component = () => import(`../views/${item.url}`)
return item;
})
menuArry.push(...item.children)
} else {
item.component = () => import(`../views/${item.url}`)
menuArry.push(item);
}
})
console.log(menuArry)
//路由的动态注册
menuArry.forEach(item=>{
router.addRoute('Main',item);
})
}

如果刷新发现home页面空白,在vue重新创建的时候加入动态路由注册方法。

1
2
3
4
5
6
7
8
new Vue({
router,
store,
render: h => h(App),
created(){
store.commit('addMenu',router)
}
}).$mount('#app')

项目源码:

1
https://gitee.com/gj2002298/vue-manage

改代码仅供学习。

其他文章