# 错误处理
[TOC]
在上一节的代码中,我们只是在控制台打印出了错误信息,并未告诉用户为什么出错。
## 封装消息条
Vuetify 的消息条是不支持 JS 调用的,所以我们需要通过其他方式来让它支持这个功能。
原理不复杂,我们只需要:
1. 创建消息条模板
2. 利用 Vuex 控制是否显示
3. 全局注册模板
### 创建消息条模板
src\components\_partial\Snackbar.vue
```html title="src\components\_partial\Snackbar.vue"
<template>
<div>
<v-snackbar light top centered v-model="visible">
{{ $store.state.snackbar.msg }}
<template v-slot:action="{ attrs }">
<v-btn
text
small
v-bind="attrs"
v-if="showClose"
color="primary"
@click="close"
>关闭</v-btn
>
</template>
</v-snackbar>
</div>
</template>
<script>
export default {
computed: {
visible() {
return this.$store.state.snackbar.visible;
},
showClose() {
return this.$store.state.snackbar.showClose;
}
},
methods: {
close() {
this.$store.commit("snackbar/close_snackbar");
}
}
};
</script>
```
### 利用 Vuex 控制是否显示
可以看到,我们预留了 Vuex 的数据,现在再来创建 Vuex 文件。
src\store\modules\snackbar.js
```javascript title="src\store\modules\snackbar.js"
const snackbar = {
namespaced: true,
state: {
msg: "", // snackbar 的信息
visible: false, // 是否显示 snackbar
showClose: true, // 是否显示关闭按钮
timeout: 6000 // 自动关闭时间
},
mutations: {
open_snackbar(state, options) {
state.visible = true;
state.msg = options.msg;
},
close_snackbar(state) {
state.visible = false;
},
set_show_close(state, isShow) {
state.showClose = isShow;
},
set_timeout(state, timeout) {
state.timeout = timeout;
}
},
actions: {
openSnackbar(content, options) {
const timeout = content.state.timeout;
content.commit("open_snackbar", {
msg: options.msg
});
setTimeout(() => {
content.commit("close_snackbar");
}, timeout);
}
}
};
export default snackbar;
```
我们在 modules 文件夹内创建了一个新的 Vuex,而不是在之前的 index.js 继续编写,这是为了更好的维护性。
另外,在 Vuex 中我们无法直接修改 state 的数据,则需要通过 mutations 方法该更改 state。
可是 actions 的功能和 mutations 看起来也差不多,那为什么还要使用 actions 呢?这是因为:
在 actions 中提交 mutation,并且可以包含任何的异步操作。actions 可以理解为通过将 mutations 里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据(但是还是通过 mutation 来操作,因为只有它能操作)
> 简单来说,actions 可以使用异步来调用。
这个文件创建好之后,我们还需要注册该模块,所以打开 src\store\index.js:
```javascript title="src\store\index.js'
import Vue from "vue";
import Vuex from "vuex";
import snackbar from "./modules/snackbar";
Vue.use(Vuex);
export default new Vuex.Store({
...
modules: {
snackbar: snackbar
}
});
```
在 modules 中直接注册,之后我们就可以通过 `$store.state.snackbar` 的方式进行调用了。
### 全局注册模板
src\App.vue
```html title="src\App.vue"
<template>
<v-app>
...
<Snackbar />
</v-app>
</template>
<script>
import Snackbar from "./components/_partial/Snackbar";
export default {
components: {
Snackbar
},
...
}
```
### 调用消息条
注册完成之后,我们很简单的控制 Vuex 就能进行全局调用了:
model\http.js
```javascript title="model\http.js"
...
/**
* 请求失败后的错误统一处理
* @param {Number} status 请求失败的状态码
*/
const errorHandle = (status, res) => {
// 状态码判断
switch (status) {
case 403:
case 401:
store.commit("logout");
toLogin();
store.dispatch("snackbar/openSnackbar", {
msg: res
});
break;
// 404请求不存在
case 404:
store.dispatch("snackbar/openSnackbar", {
msg: "请求的资源不存在"
});
break;
default:
store.dispatch("snackbar/openSnackbar", {
msg: res
});
}
};
...
```
## 统一数据格式
在刚刚的代码中,我们统一拦截 403、401 状态码来使用户进行退出,所以在后端中也要统一状态码。
请注意,我们在该文件中写了这么一行 `response.data.message` 来调用消息数据的显示:
model\http.js
```javascript title="model\http.js"
if (response) {
// 请求已发出,但是不在2xx的范围
errorHandle(response.status, response.data.message);
return Promise.reject(response);
}
```
所以我们还需要统一后端返回的数据格式:
app\controller\Auth.php
```php title="app\controller\Auth.php"
class Auth
{
public function me()
{
try {
$id = JWTAuth::auth()['id'];
$data = User::find($id);
return json($data);
} catch (Exception $e) {
return json([
'message' => '请先登录'
], 401);
}
}
public function login()
{
$requestData = Request::post();
$user = User::where('email', $requestData['email'])->find();
if ($user !== null && password_verify($requestData['password'], $user->password)) {
return json([
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'token' => JWTAuth::builder(['id' => $user->id]),
'ttl' => env('JWT_TTL')
]);
} else {
return json(
[
'message' => '授权错误,请检查邮件地址或密码'
],
401
);
}
}
public function sign()
{
$requestData = Request::post();
try {
validate(validateAuth::class)->batch(true)->check($requestData);
$create = User::create($requestData);
$data = User::find($create->id);
return json([
'id' => $data->id,
'name' => $data->name,
'email' => $data->email,
'token' => JWTAuth::builder(['id' => $data->id]),
'ttl' => env('JWT_TTL')
]);
return json($data);
} catch (ValidateException $e) {
return json(
[
'message' => $e->getError()
],
400
);
}
}
public function logout()
{
$authorization = Request::header('Authorization');
$token = explode('Bearer ', $authorization)[1];
try {
JWTAuth::invalidate($token);
JWTAuth::validate($token);
return json([
'message' => '登出成功'
]);
} catch (Exception $e) {
return json([
'message' => '登出失败,请检查 token 有效情况'
], 403);
}
}
}
```
现在再进入浏览器打开页面,可以看到一切都按照预期显示了。
![](https://img.kancloud.cn/c4/53/c4535cdb6a74e4c409aa5f0679b38e43_1439x488.png)
![](https://img.kancloud.cn/45/fa/45fa7a2c077e5b0bc915d7b36c2ce706_1512x421.png)
- 第一章. 基础信息
- 1.1 序言
- 1.2 关于作者
- 1.3 本书源码
- 1.4 问题反馈
- 第二章. 舞台布置
- 2.1 开发环境搭建
- 2.2 产品分析
- 2.3 创建后端应用
- 2.4 创建前端应用
- 第三章. 构建页面
- 3.1 章节说明
- 3.2 第一个 API
- 3.3 静态页面
- 3.4 Think 命令
- 3.5 小结
- 第四章. 优化页面
- 4.1 章节说明
- 4.2 使用路由
- 4.3 注册页面
- 4.4 样式美化
- 4.5 小结
- 第五章. 用户模型
- 5.1 章节说明
- 5.2 数据库迁移
- 5.3 模型
- 5.4 小结
- 第六章. 用户注册
- 6.1 章节说明
- 6.2 接收数据
- 6.3 数据验证
- 6.4 写入数据
- 6.5 前端页面
- 6.6 小结
- 第七章. 会话管理
- 7.1 章节说明
- 7.2 会话控制
- 7.3 前端拦截
- 7.4 使用 Vuex
- 7.5 用户登入
- 7.6 用户登出
- 7.7 小结
- 第八章. 用户数据
- 8.1 章节说明
- 8.2 查找用户
- 8.3 重构代码
- 8.4 错误处理
- 8.5 个人资料
- 8.6 更新资料
- 8.7 小结
- 第九章. 推文数据
- 9.1 章节说明
- 9.2 推文模型
- 9.3 发送推文
- 9.4 发送推文前端页面
- 9.5 推文流
- 9.6 用户的所有推文
- 9.7 小结
- 第十章. 用户关系
- 10.1 章节说明
- 10.2 粉丝模型
- 10.3 关注与取消关注
- 10.4 已关注用户的推文
- 10.5 小结