# 错误处理 [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)