项目演示地址:https://hujiyi.github.io/acme-world-web/


Leancloud 数据存储

LeanCloud(原 AVOS Cloud) 是针对移动应用的一站式云端服务,专注于为应用开发者提供工具和平台。提供包括LeanStorage 数据存储LeanMessage 通信服务LeanAnalytics 统计分析LeanModules 拓展模块等四大类型的后端云服务。当前项目使用了LeanStorage 数据存储来实现论坛数据的保存。

Leancloud SDK 的初始化配置信息

LeanCloud 国内默认的是华北节点,但是去年华北节点的文件服务器因为某些原因,出现了域名服务方面的问题。导致文件可以上传,但是没办法访问,所以这里建设使用 华东节点

登录 Leancloud华东节点 (没有账号的请先注册),然后创建应用,应用名称自己取,操作类似下图:

应用创建成功后,点击该应用最左边的 数据存储 图标,进入应用管理界面,如下图:

在右侧菜单选择:设置 -> 应用凭证, 即可看到当前应用的 AppID, AppKey, REST API服务器地址 等初始化 Leancloud SDK 所需要的信息,如下图:

请将上图中的 AppID, AppKey, REST API服务器地址 (画圈、打勾的那三个)信息复制后,粘贴到 src/api/config/lc.config.js 文件中完成初始化信息配置。

Leancloud SDK 数据存储

Leancloud 应用管理界面选择:数据存储 -> 结构化数据, 列表显示当前应用以下划线开头的内置的Class, leancloud 的每一个 Class 就相当于数据库的一个表。

名为 _User 的内置 Class 用于保存用户信息的, 除了原来的字段,我们也可以添加更多的字段如:头像、昵称等。

账号服务类

src/api/ 文件夹下新建一个名为 service 的文件夹,并在该文件夹中添加文件:account_service.js

打开新建的 src/api/service/account_service.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
import LC from 'leancloud-storage';  // 导入 leancloud 数据存储模块

/**
* 账号管理的类
*/
class AccountService {

/**
* 注册新用户账号
* @param {*} username
* @param {*} password
* @param {*} email
* @returns 注册返回的状态码: 202: "用户名已被注册", 203: "邮箱已被注册", "-1": "无法连接到服务器"
*
*/
async signUp(username, password, email = '') {
try {
let user = new LC.User();
user.setUsername(username);
user.setPassword(password);
// 添加一个 昵称 字段,默认为用户名
user.set('nickname', username);
if (email) {
user.setEmail(email);
}
let response = await user.signUp();
console.log('注册账号成功:', response);
return {
"status_code": "ok", // 注册状态码
"user": response, // 用户
};
} catch (e) {
console.log('注册账号失败:', e.code, e);
return {
// 注册返回的状态码: 202: "用户名已被注册", 203: "邮箱已被注册", "-1": "无法连接到服务器"
"status_code": e.code, // 注册状态码
"user": null, // 注册失败,返回用户为 null
};
}
}


/**
* 用户登录
* @param {*} username
* @param {*} password
* @returns 登录状态代码 和用户
* 200: 登录成功
* 210: 密码错误;
* 211:用户不存在,
* 219: 重试次数太多
* -1 : 请求被终止,一般是网络有问题
*/
async logIn(username, password) {
try {
let user = await LC.User.logIn(username, password);
if (user) {
console.log('登录成功:', user);
return {
"status_code": "ok", // 登录状态码
"user": user, // 用户
};
}
} catch (e) {
// e.code
// 210: 密码错误;
// 211:用户不存在,
// -1 Error: Request has been terminated
console.log('登录失败:', e.code, e);
return {
"status_code": e.code,
"user": null,
};
}
return null;
}

/**
* 退出登录
* @returns 无返回值
*/
async logOut() {
return LC.User.logOut();
}

/**
* 通过电子邮件重置密码
* @param {*} email
* @returns 状态码:
* "200": 发送邮件成功;
* "1": "请不要往同一个邮件地址发送太多邮件",
* "205": "此电子邮箱没有被注册使用",
* "-1": "请求被终止,请检查网络连接状况"
*/
async passwordResetByEmail(email) {
try {
let response = await LC.User.requestPasswordReset(email);
console.log(response.code, typeof response);
return { "status_code": "ok" };
} catch (e) {
console.log("发送邮件:", e.code, e);
return {
"status_code": e.code, // 发送邮件不成功,返回错误代码
}
}
}

/**
* 获取当前用户的 session token
* @returns session token
*/
async getToken() {
return LC.User.current().getSessionToken();
}

}

export default new AccountService();

leancloud SDK 用户的相关文档请查看:https://leancloud.cn/docs/leanstorage_guide-js.html#hash954895

新用户注册功能的实现

打开文件 src/views/account/pages/SignUp.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
<template>
<el-card shadow="always" class="login-module">
<el-form>
<div class="horiz-container form-title">
<span>注册新用户</span>
<div class="spacer"></div>
<span>
<router-link to="/">
<i class="fa fa-home fa-lg" aria-hidden="true"></i>
</router-link>
</span>
</div>
<el-form-item>
<el-input
type="text"
prefix-icon="fa fa-envelope-o fa-lg"
v-model="email"
placeholder="电子邮箱"
></el-input>
</el-form-item>

<el-form-item>
<el-input
type="text"
prefix-icon="fa fa-user-o fa-lg"
v-model="username"
placeholder="用户名"
></el-input>
</el-form-item>
<el-form-item>
<el-input
type="password"
prefix-icon="fa fa-lock fa-lg"
v-model="password"
placeholder="密码"
></el-input>
</el-form-item>
<el-form-item>
<!-- 按钮添加单击事件调用函数创建用户 -->
<el-button class="full-width" type="primary" @click="createUser">注册新用户</el-button>
</el-form-item>
<div class="horiz-container">
<div class="spacer"></div>
<router-link to="/login" class="link">
<el-link :underline="false" type="success">使用已有账号登录</el-link>
</router-link>
</div>
</el-form>
</el-card>
</template>

<script>
// 导入账号服务类
import AccountService from "../../../api/service/account_service";
export default {
name: "SignUp", // 组件名称
data: () => ({
username: "", // 用于输入用户名
password: "", // 用于输入密码
email: "", // 用于输入邮箱
}),
methods: {
// 调用 AccountService 中的异步方法时,函数前要加上 async
async createUser() {
// 注册函数返回值为 包含 状态码 和 用户 两个部分内容的对象
let response = await AccountService.signUp(this.username, this.password, this.email);
if (response.status_code == "ok") {
// 调用异步方法不能缺少 await,不然等不到正确结果
let token = await AccountService.getToken();
localStorage.setItem("token", token); // 保存当前用户的 session token
localStorage.setItem("currentUser", this.username); // 保存当前的用户名
this.$router.push("/dashboard"); // 跳转到后台管理)
}
else {
let errorMsg = {
"125": "无效的电子邮件地址",
"202": "此用户名已被注册",
"203": "此电子邮箱已经被占用",
"-1": "请求被终止,请检查网络连接状况",
};
// 使用 Notification 通知 显示注册失败的提示信息
this.$notify.error({
title: '注册失败', // 标题
message: errorMsg[response.status_code], // 取出对应的消息内容
// offset: 100, // 偏移量,消息距屏幕边缘偏移一段距离
showClose: false, // 不显示关闭按钮,只能等自动关闭
});
}
}
},
};
</script>

<style>
.login-module {
width: 380px;
}
</style>

在项目结果的网页中点击链接到注册新用户的界面, 输入新用户的电子邮箱用户名密码, 如下图所示(为方便看到可能出现的错误或异常信息,请按 F12 打开浏览器的Web开发者工具, 请切换到 控制台(Console)选项卡):

点击 注册新用户 按钮, 注册成功后,浏览器跳转到后台管理界面, 如下图所示:

注册失败时,或上角显示 注册失败 的提示,同时浏览器的控制台输出相应的异常信息, 如下图所示:

注册成功后,在 leancloud 的应用管理后台 刷新 后可以看到 _User 已经添多了一条记录

实现登录功能

打开文件 src/views/account/pages/Login.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
<template>
<el-card shadow="always" class="login-module">
<el-form>
<div class="horiz-container form-title">
<span>用户登录</span>
<div class="spacer"></div>
<span>
<router-link to="/">
<i class="fa fa-home fa-lg" aria-hidden="true"></i>
</router-link>
</span>
</div>
<el-form-item>
<el-input
type="text"
prefix-icon="fa fa-user-o fa-lg"
v-model="username"
placeholder="用户名"
></el-input>
</el-form-item>
<el-form-item>
<el-input
type="password"
prefix-icon="fa fa-lock fa-lg"
v-model="password"
placeholder="密码"
></el-input>
</el-form-item>
<el-form-item>
<!-- 登录按钮单击事件调用 login 函数 -->
<el-button class="full-width" type="primary" @click="login">登录</el-button>
</el-form-item>
<div class="horiz-container">
<div class="spacer"></div>
<router-link to="/password_reset" class="mx-2">
<el-link :underline="false" type="warning">忘记密码</el-link>
</router-link>
<router-link to="/signup" class="mx-2">
<el-link :underline="false" type="danger">没有账号</el-link>
</router-link>
</div>
</el-form>
</el-card>
</template>

<script>
// 导入账号服务类
import AccountService from "../../../api/service/account_service";
import { Loading } from "element-ui"; // 导入 Loading加载
export default {
name: "Login",
data: () => ({
username: "zhangsan", // 用于输入用户名
password: "123456", // 用于输入密码
loading: '', // 用于显示 Loading 加载 的变量
}),
methods: {
async login() {
this.showLoading('#login', '正在登录中...'); // 在选择器名为 # login 的元素上显示 loading

// 登录函数返回 包含 状态码 和 用户 两个信息的对象, 状态码为 字符类型 的 数字字符
let response = await AccountService.logIn(this.username, this.password);
// 为了进行登录失败的提示,只能通过 typeof currentUser 来 判断登录是否成功
if (response.status_code == "ok") {
// 读取进入登录页的前一页地址
const curr = localStorage.getItem("preRoute");
let token = await AccountService.getToken();
localStorage.setItem("token", token); // 保存当前用户的 session token
localStorage.setItem("currentUser", this.username); // 保存当前的用户名

console.log('response.user:', response.user);


if (curr === null || curr === '/' || curr === '/account' || curr === "/login") {
this.$router.push({ path: "/dashboard" });
} else {
// 跳转到进入登录页之前的页面
this.$router.push({ path: curr });
}
} else {
let errorMsg = {
"210": "用户名和密码不匹配",
"211": "找不到用户",
"219":'登录失败次数超过限制,请稍候再试,或者重设密码',
"-1": "请求被终止,请检查网络连接状况",
};
// 使用 Notification 通知 显示登录失败的的提示信息
this.$notify.error({
title: '登录失败', // 标题
message: errorMsg[response.status_code], // 消息内容
// offset: 100, // 偏移量,消息距屏幕边缘偏移一段距离
showClose: false, // 不显示关闭按钮,只能等自动关闭
});
}
// 结束显示 loading
this.endLoading();
},

/**
* 自定义用于局部区域显示 loading 的函数
* 局部区域显示 loading 的步骤:
* 1、import { Loading } from "element-ui"; // 导入 Loading加载
* 2、在 data 定义控制变量: loading:false,
* 3、定义函数:showLoading(targetNode, message) 和 endLoading()
* 4、调用第 3 步定义的两个函数
* @param {*} targetNode 目标区域的选择器,建设使用 id 选择器
* @returns
*/
showLoading(targetNode, message) {
this.loading = Loading.service({
// 锁定屏幕的滚动
lock: true,
// 显示的文本
text: message,
// document.querySelector 用于以获取到对应 DOM 节点
target: document.querySelector(targetNode),
});
},

// 停止显示 loading
endLoading() {
this.loading.close();
},

// 获取用户数据中的用户名和邮箱两个信息
getUser(user) {
return {
id: user.id,
username: user.get("username"),
email: user.get("email"),
};
},
}
};
</script>

<style>
.login-module {
width: 380px;
}
</style>

浏览器进入到登录页面,输入 用户名密码 登录, 登录不成功,显示相应的错误提示,效果如下图:

登录成功,则进入后台管理 或是自动跳转到登录界面的前一页。

实现通过邮箱找回密码

打开文件 src/views/account/pages/PasswordReset.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
<template>
<el-card shadow="always" class="login-module">
<el-form>
<div class="horiz-container form-title">
<span>通过邮箱找回密码</span>
<div class="spacer"></div>
<span>
<router-link to="/">
<i class="fa fa-home fa-lg" aria-hidden="true"></i>
</router-link>
</span>
</div>
<!-- 添加发送重置邮件成功后显示的内容 -->
<div v-if="sendEmailSuccess">
<el-alert
:title="`请登录 ${this.email} 邮箱进行密码重置`"
type="success"
effect="dark"
center
show-icon
:closable="false"
></el-alert>
</div>
<!-- 原来的表单项添加一个 <div v-else> </div> 包裹起来 -->
<div v-else>
<el-form-item>
<el-input
type="text"
prefix-icon="fa fa-envelope-o fa-lg"
v-model="email"
placeholder="电子邮箱"
></el-input>
</el-form-item>

<el-form-item>
<!-- 按钮单击事件添加调用 passwordReset 函数 -->
<el-button class="full-width" type="primary" @click="passwordReset">发送找回密码的邮件</el-button>
</el-form-item>
<div class="horiz-container">
<div class="spacer"></div>
<router-link to="/login" class="link">
<el-link :underline="false" type="success">使用已有账号登录</el-link>
</router-link>
<router-link to="/signup" class="mx-2">
<el-link :underline="false" type="danger">没有账号</el-link>
</router-link>
</div>
</div>
</el-form>
</el-card>
</template>

<script>
import AccountService from "../../../api/service/account_service";
export default {
name: "PasswordReset", // 组件名称
data: () => ({
email: "", // 用于输入邮箱
sendEmailSuccess: false,
}),
methods: {
async passwordReset() {
let email = this.email.trim();
// 发送重置密码邮件后,返回值为 字符类型的状态码
let response = await AccountService.passwordResetByEmail(email);
if (response.status_code == "ok") {
this.sendEmailSuccess = true;
// 显示发送成功的提示信息
this.$notify.success({
title: '发送重置密码邮件成功', // 标题
message: "请查看邮件,以便完成密码重置", // 消息内容
// offset: 100, // 偏移量,消息距屏幕边缘偏移一段距离
showClose: false, // 不显示关闭按钮,只能等自动关闭
});

} else {
let errorMsg = {
"1": "请不要往同一个邮件地址发送太多邮件",
"205": "此电子邮箱没有被注册使用",
"-1": "请求被终止,请检查网络连接状况"
};
// 使用 Notification 通知 显示操作失败的的提示信息
this.$notify.error({
title: '密码重置失败', // 标题
message: errorMsg[response.status_code], // 消息内容
// offset: 100, // 偏移量,消息距屏幕边缘偏移一段距离
showClose: false, // 不显示关闭按钮,只能等自动关闭
});
}
}
}
};
</script>

<style>
.login-module {
width: 380px;
}
</style>

浏览器进入到找回密码的界面,填写用户注册时的电子邮箱, 邮箱填写错误,显示相应的提示信息:

填写正确的电子邮箱,发送重置密码邮件,登录到自己的邮箱,即可重置密码:

在后台管理界面显示当前登录用户及注销

打开文件 src/views/dashboard/layout/Header.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
<template>
<div class="lay-header horiz-container">
<router-link to="/">
<el-button type="text">首页</el-button>
</router-link>
<div class="spacer"></div>
<el-link :underline="false" type="success" class="mx-2">
<i class="fa fa-bell fa-lg"></i> 消息
</el-link>
<el-link :underline="false" type="danger" class="mx-2">
<i class="fa fa-envelope fa-lg"></i> 邮件
</el-link>
<!-- 从状态栏读取当前已登录用户的信息,并显示用户名 -->
<el-link
:underline="false"
type="primary"
class="mx-2"
v-if="currentUser"
>当前用户: {{ currentUser }}</el-link>
<!-- 添加注销功能 -->
<el-link :underline="false" type="danger" class="mx-2" @click="logout">注销</el-link>
</div>
</template>

<script>
import AccountService from "../../../api/service/account_service.js"
export default {
name: 'LayoutHeader',
methods: {

// 用户注销
async logout() {
console.log("logout");
await AccountService.logOut();
if (this.$route.path != "/") {
localStorage.removeItem("preRoute"); // 删除已保存的前一页地址
localStorage.removeItem("token"); // 删除已保存的用户 token
this.$router.push("/");
}
},
},

computed: {
// 获取当前登录的用户名
currentUser() {
if (localStorage.getItem('token')) {
return localStorage.getItem('currentUser')
}
},
}
}
</script>

<style>
.lay-header {
height: 60px;
align-items: center;
}
</style>

登录成功后,后台管理页面的右上角显示 当前用户名,并实现用户的注销, 效果如下图所示:

后继内容:
使用 Element UI 和 Leancloud 的 Vue.js 项目开发 VII

===END===