# 錯誤處理
[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);
}
}
}
```
現在再進入瀏覽器打開頁面,可以看到一切都按照預期顯示了。


- 第一章. 基礎信息
- 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 小結