安装ElementUI

官方文档

1
npm i element-ui -S

快速上手

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// main.js
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';

Vue.config.productionTip = false;

Vue.use(ElementUI);

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

官网组件

随便复制一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- Home.vue -->
<template>
<div class="home">
<el-row>
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</el-row>
</div>
</template>

<script>
// @ is an alias to /src

export default {
name: 'Home',
};
</script>

运行看看页面

1
npm run serve

创建数据库

导入sql语句 创建表

复制 vue_api_server 到项目的同级目录

修改连接数据库的配置

运行 vue_api_server 里的 app.js

用 rest client 插件 发请求测试

1
2
3
4
5
6
7
8
9
10
@baseurl=http://127.0.0.1:8888/api/private/v1/

###
POST {{baseurl}}login
Content-Type: application/json

{
"username": "admin",
"password": "123456"
}

创建登录页面

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- views/Login.vue -->
<template>
<div class="login">
login page
</div>
</template>

<style lang="less" scoped>
.login {
background-color: #2b4b6b;
height: 100vh;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
// router/index.js
...
import Login from '../views/Login.vue';
...
const routes = [
...
{
path: '/login',
name: 'Login',
component: Login,
},
];
1
2
3
4
// assets/index.less
body {
margin: 0;
}
1
2
3
4
// main.js
...
import './assets/index.less';
...

使用Element组件完善页面

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
<template>
<div class="login">
<div class="login_box">
<div class="logo_box">
<img src="../assets/logo.png" alt="" />
</div>

<div class="form_box" ref="mydiv">
<!-- model: 表单数据对象(和v-model一起绑定数据 不绑定数据输入不了) -->
<!-- rules: 表单验证规则对象 -->
<el-form
:rules="form.rules"
ref="form"
class="demo-ruleForm"
:model="form.data"
>
<!-- prop: 表单数据的键名(和rules配合使用) -->
<el-form-item prop="username">
<el-input
type="text"
autocomplete="off"
v-model="form.data.username"
placeholder="请输入用户名"
prefix-icon="el-icon-user-solid"
<!-- 输入框里的图标 -->
></el-input>
<!-- autocomplete 原生属性,自动补全 -->
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
autocomplete="off"
v-model="form.data.password"
prefix-icon="el-icon-lock"
placeholder="请输入密码"
@keyup.enter.native="submit"
<!-- 事件修饰符 .native 可以监听自定义组件的原生事件原理监听在组件模板的根元素中子元素可以通过事件冒泡传递上来 -->
></el-input>
</el-form-item>
<el-form-item class="btn_box">
<el-button type="primary" @click="submit">提交</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
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
// npm i axois 下载axois模块
<script>
import axios from 'axios'; // 导入axios模块

export default { // 默认导出
name: 'Login', // 模块命名
data() {
return {
form: {
data: {
username: '',
password: '',
},
rules: { // 表单验证规则
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }, // trigger触发的条件
{
min: 2,
max: 16,
message: '用户名长度在2~16个字符之间',
trigger: 'blur',
},
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{
min: 6,
max: 16,
message: '密码长度在6~16个字符之间',
trigger: 'blur',
},
],
},
},
};
},
methods: {
submit() {
// 在提交之前,要先判断一下表单数据是否合法
this.$refs.form
.validate() // 不传入回调函数,则会返回一个 promise 布尔值
.then(() => {
return axios({
method: 'POST',
url: 'http://127.0.0.1:8888/api/private/v1/login',
data: this.form.data,
});
})
.then((res) => {
if (res.data.meta.status === 200) {
this.$message({
message: res.data.meta.msg, // 返回成功信息
type: 'success', // 绿色提示框
});
localStorage.setItem('token', res.data.data.token); // 存储token值
} else {
this.$message.error(res.data.meta.msg); // 红色错误框 返回失败信息
}
})
.catch(() => {
// console.log(res);
});
},
},
mounted() {
// 如果ref引用的是一个原始HTML元素,那么拿到的就是js原始的dom对象
// console.log(this.$refs.mydiv);
// 如果ref引用的是一个Vue组件元素,那么拿到的就是该Vue组件的实例对象
// console.log(this.$refs.hehe.sayHi());
},
};
</script>

完成登录功能,优化登录页面

新建plugins文件夹 把axios和element封装一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// plugins/axios.js
import Vue from 'vue';
import axios from 'axios';

const request = axios.create({
// 创建一个新的axios
baseURL: 'http://127.0.0.1:8888/api/private/v1/',
});

Vue.prototype.__apis = {
login(data) {
return request({
method: 'post',
url: '/login',
data,
});
},
};
1
2
3
4
5
6
// plugins/element.js
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 在main.js里面导入
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import './plugins/element.js';
import './assets/index.less';
import './plugins/axios.js';

Vue.config.productionTip = false;

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

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
// Loing.vue 优化
methods: {
submit() {
this.$refs.form
.validate()
.then(() => {
return this.__apis.login(this.form.data);
})
.then((res) => {
if (res.data.meta.status === 200) {
this.$message({
message: res.data.meta.msg,
type: 'success',
});
localStorage.setItem('token', res.data.data.token);
this.$router.push('/'); // 跳转到主页
} else {
this.$message.error(res.data.meta.msg);
}
})
.catch(() => {

});
},
reset() {
this.$refs.form.resetFields();
},
},

退出登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- Home.vue -->
<template>
<div class="home">
<el-row>
<el-button @click="logout">退出登录</el-button>
</el-row>
</div>
</template>

<script>
// @ is an alias to /src

export default {
name: 'Home',
methods: {
logout() {
localStorage.removeItem('token');
this.$router.push('/login');
},
},
};
</script>

路由守卫

如果用户没有登录(没有token),但是直接通过 URL 访问特定页面,需要重新导航到登录页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// index.js
...
// 为路由对象,添加 beforeEach 导航守卫,to表示将要访问的路径,from表示从哪里来,next是下一个要做的操作
router.beforeEach((to, from, next) => {
// 如果用户访问的登录页,直接放行
if (to.path === '/login') {
next();
} else {
// 从 sessionStorage 中获取到 保存的 token 值
const token = localStorage.getItem('token');
// 没有token,强制跳转到登录页
if (token) {
next();
} else {
next('/login');
}
}
});
...

完善主页

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
<!-- Home.vue -->
<template>
<div class="home">
<el-container>
<el-header>
<div
@click="$route.path === '/welcome' ? '' : $router.push('/welcome')"
>
<img src="../assets/logo.png" alt="" />
<span>电商后台管理系统</span>
</div>
<el-button @click="logout" type="info">退出</el-button>
<!-- 退出按钮 -->
</el-header>
<el-container>
<el-aside :width="isCollapse ? 'auto' : '200px'">
<el-menu
:collapse="isCollapse"
:collapse-transition="false"
default-active="2"
class="el-menu-vertical-demo"
background-color="#333744"
text-color="#fff"
unique-opened
active-text-color="rgb(64, 158, 255)"
>
<!-- 是否水平折叠收起菜单(仅在 mode 为 vertical(垂直) 时可用) -->
<!-- default-active 当前激活菜单的 index -->
<!-- unique-opened 是否只保持一个子菜单的展开 -->
<el-submenu v-for="v in menus" :index="v.id + ''" :key="v.id">
<template #title>
<i :class="menuIconMapForLevel1[v.id]"></i>
<span>{{ v.authName }}</span>
</template>
<template #default>
<el-menu-item
v-for="v2 in v.children"
:index="v2.path"
:key="v2.id"
>
<router-link :to="v2.path">
<i class="el-icon-menu"></i>
{{ v2.authName }}
</router-link>
</el-menu-item>
</template>
</el-submenu>
</el-menu>
<div class="collapse_toggle" @click="isCollapse = !isCollapse">
<i
:class="isCollapse ? 'el-icon-arrow-right' : 'el-icon-arrow-left'"
></i>
</div>
</el-aside>
<el-container>
<el-main>
<router-view></router-view>
</el-main>
<el-footer>©2020 VueShop (香)-学习性-2017-0020</el-footer>
</el-container>
</el-container>
</el-container>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// plugins/axios.js  

// 请求拦截器,所有的请求在发送出去之前,都会先经过这里
request.interceptors.request.use((config) => {
if (!config.url.startsWith('/login')) {
// 除了登录API以外,都要加上token请求头
config.headers.Authorization = localStorage.getItem('token');
}
return config;
});

...
getLeftMenus() { // 用来获取左侧菜单列表
return request({
method: 'get',
url: '/menus',
// headers: { Authorization: localStorage.getItem('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
// Home.vue
<script>
// @ is an alias to /src

export default {
name: 'Home',
data() {
return {
menus: [],
menuIconMapForLevel1: {
125: 'el-icon-s-custom',
103: 'el-icon-lock',
101: 'el-icon-goods',
102: 'el-icon-s-order',
145: 'el-icon-s-data',
}, // 通过id设置图标
isCollapse: false, // 侧边栏开关
};
},
methods: {
logout() {
localStorage.removeItem('token'); // 清空token
this.$router.push('/login'); // 退出回到登录页
},
},
created() {
this.__apis.getLeftMenus().then((data) => {
// console.log(data.data);
if (data.data.meta.status === 200) {
this.$message({
message: '登录成功', // 获取用户信息成功
type: 'success',
});

this.menus = data.data.data; // 把数据存到组件内用来渲染
// console.log(this.menus);
}
});
},
};
</script>

创建用户信息子组件

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
<!-- views/Users.vue -->
<template>
<div class="users">
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>用户管理</el-breadcrumb-item>
<el-breadcrumb-item>用户列表</el-breadcrumb-item>
</el-breadcrumb>
<el-card class="box-card">
<el-row :gutter="20">
<el-col :span="8">
<el-input placeholder="请输入内容" class="input-with-select">
<el-button slot="append" icon="el-icon-search"></el-button>
</el-input>
</el-col>
<el-col :span="16">
<el-button type="primary">添加用户</el-button>
</el-col>
</el-row>
<el-table border :data="user" style="width: 100%;">
<el-table-column prop="id" label="#" width="180"> </el-table-column>
<el-table-column prop="username" label="姓名" width="180">
</el-table-column>
<el-table-column prop="email" label="邮箱"> </el-table-column>
<el-table-column prop="mobile" label="电话"> </el-table-column>
<el-table-column prop="role_name" label="角色"> </el-table-column>
<el-table-column prop="mg_state" label="状态">
<template #default="{ row }">
<el-switch
v-model="row.mg_state"
active-color="#13ce66"
inactive-color="#ff4949"
>
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作">
<el-button type="primary" size="mini" icon="el-icon-edit"></el-button>
<el-button
type="warning"
size="mini"
icon="el-icon-delete"
></el-button>
<el-button
type="danger"
size="mini"
icon="el-icon-setting"
></el-button>
</el-table-column>
</el-table>
<!-- 当每页条数改变时触发:@size-change -->
<!-- 当前页码改变时触发:@current-change -->
<!-- 当前页码:current-page -->
<!-- 每页显示条数的可选项:page-sizes -->
<!-- 当前的每页显示条数:page-size -->
<!-- 控制显示哪些布局内容:layout -->
<!-- 总条数:total -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pagenum"
:page-sizes="[1, 3, 5, 8, 10]"
:page-size="pagesize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
>
</el-pagination>
</el-card>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
// plugins/axios.js
...
getUsers(data) { // 获取用户列表数据
return request({
method: 'GET',
url: '/users',
params: 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
36
37
38
39
40
41
42
43
// views/Users.vue
<script>
export default {
name: 'User',
data() {
return {
query: '',
pagenum: 1,
pagesize: 1,
total: 1,
user: [],
};
},
methods: {
handleSizeChange(size) {
this.pagesize = size;
this.getUsers();
},
handleCurrentChange(current) {
this.pagenum = current;
this.getUsers();
},
getUsers() { // 获取
this.__apis
.getUsers({
query: this.query,
pagenum: this.pagenum,
pagesize: this.pagesize,
})
.then((res) => {
if (res.data.meta.status === 200) {
this.user = res.data.data.users;
this.pagenum = res.data.data.pagenum;
this.total = res.data.data.total;
}
});
},
},
created() {
this.getUsers();
},
};
</script>
1
2
3
4
5
6
<!-- views/Welcome.vue -->
<template>
<div class="welcome">

</div>
</template>
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
// router/index.js

import Welcome from '../views/Welcome.vue';
import Users from '../views/Users.vue';
...
const routes = [
{
path: '/',
name: 'Home',
component: Home,
redirect: '/welcome',
children: [
{
path: '/welcome',
name: 'Welcome',
component: Welcome,
},
{
path: '/users',
name: 'Users',
component: Users,
},
],
},
{
path: '/login',
name: 'Login',
component: Login,
},
];
...

完成

自己gitee完成的

项目原地址