|
| 1 | +## 目标 |
| 2 | +完成错误提示信息的封装 |
| 3 | +完成401错误跳转登录页的封装 |
| 4 | +完成restful请求组合式api的封装 |
| 5 | +## 错误提示信息 |
| 6 | +当我们的接口请求发生错误的时候,我们不可能直接在控制台进行打印,对于用户的体验是非常差的,所以我们需要在页面中弹出对应的提示信息。 |
| 7 | +首先我们需要让我们的提示信息支持多语言,在lang/global下面的zh-CN.ts和en-US.ts中分别导入: |
| 8 | +```typescript |
| 9 | +// 请求多语言配置 |
| 10 | + 'global.request.error.401': 'Login Expired', |
| 11 | + 'global.request.error.403': 'Resource Request Error', |
| 12 | + 'global.request.error.500': 'Server Error', |
| 13 | + 'global.request.error.other': 'System Error', |
| 14 | +``` |
| 15 | +```typescript |
| 16 | + // 请求多语言配置 |
| 17 | + 'global.request.error.401': '登录过期', |
| 18 | + 'global.request.error.403': '资源请求错误', |
| 19 | + 'global.request.error.500': '服务器错误', |
| 20 | + 'global.request.error.other': '系统错误', |
| 21 | +``` |
| 22 | + |
| 23 | +我们可以使用naive-ui给我们提供的notification组件来实现,接下来我们先来写一下这一部分的代码。 |
| 24 | +在request.ts中: |
| 25 | +```typescript |
| 26 | +const errorHandler = (error: AxiosError): Promise<any> => { |
| 27 | + const notification = useNotification() |
| 28 | + // 判断是否存在response |
| 29 | + if (error.response) { |
| 30 | + const { data, status, statusText } = error.response as AxiosResponse<any> |
| 31 | + if (status === 401) { |
| 32 | + // 重新登录 |
| 33 | + notification.error({ |
| 34 | + title: i18n.global.t('global.request.error.401'), |
| 35 | + content: data?.msg || statusText, |
| 36 | + duration: 3000, |
| 37 | + }) |
| 38 | + } |
| 39 | + else if (status === 403) { |
| 40 | + // |
| 41 | + notification.error({ |
| 42 | + title: i18n.global.t('global.request.error.403'), |
| 43 | + content: data?.msg || statusText, |
| 44 | + duration: 3000, |
| 45 | + }) |
| 46 | + } |
| 47 | + else if (status === 500) { |
| 48 | + notification.error({ |
| 49 | + title: i18n.global.t('global.request.error.500'), |
| 50 | + content: data?.msg || statusText, |
| 51 | + duration: 3000, |
| 52 | + }) |
| 53 | + } |
| 54 | + else { |
| 55 | + notification.error({ |
| 56 | + title: i18n.global.t('global.request.error.other'), |
| 57 | + content: data?.msg || statusText, |
| 58 | + duration: 3000, |
| 59 | + }) |
| 60 | + } |
| 61 | + } |
| 62 | + return Promise.reject(error) |
| 63 | +} |
| 64 | +``` |
| 65 | +我们测试一下会发现当我们点击401请求的时候,不仅没有弹出框,而且报错告诉我们inject只能在setup中使用或者函数式组件,所以我们在这里没办法直接使用的。 |
| 66 | + |
| 67 | +那么我们来改进一下让他能使用,我们在compsables创建一个全局配置的组合式api,global-config.ts的文件,然后实现如下: |
| 68 | +```typescript |
| 69 | +import { merge } from 'lodash-es' |
| 70 | + |
| 71 | +interface GlobalConfigType { |
| 72 | + notification?: ReturnType<typeof useNotification> |
| 73 | + message?: ReturnType<typeof useMessage> |
| 74 | + dialog?: ReturnType<typeof useDialog> |
| 75 | + loadingBar?: ReturnType<typeof useLoadingBar> |
| 76 | +} |
| 77 | +const globalConfig: GlobalConfigType = { |
| 78 | +} |
| 79 | + |
| 80 | +export const useGlobalConfig = (): GlobalConfigType => { |
| 81 | + return globalConfig |
| 82 | +} |
| 83 | + |
| 84 | +export const useGlobalConfigProvider = (config: GlobalConfigType) => { |
| 85 | + merge(globalConfig, config) |
| 86 | +} |
| 87 | + |
| 88 | +``` |
| 89 | +然后在components中的app-provider中增加一个naive-provider.vue组件 |
| 90 | +```vue |
| 91 | +<script lang="ts" setup> |
| 92 | +const notification = useNotification() |
| 93 | +const dialog = useDialog() |
| 94 | +const message = useMessage() |
| 95 | +const loadingBar = useLoadingBar() |
| 96 | +useGlobalConfigProvider({ |
| 97 | + notification, |
| 98 | + dialog, |
| 99 | + message, |
| 100 | + loadingBar, |
| 101 | +}) |
| 102 | +</script> |
| 103 | +
|
| 104 | +<template> |
| 105 | + <slot /> |
| 106 | +</template> |
| 107 | +
|
| 108 | +``` |
| 109 | +然后在app-provider/index.vue中导入并使用 |
| 110 | +```vue |
| 111 | +<template> |
| 112 | + <n-message-provider> |
| 113 | + <n-dialog-provider> |
| 114 | + <n-notification-provider> |
| 115 | + <n-loading-bar-provider> |
| 116 | ++ <naive-provider> |
| 117 | + <slot /> |
| 118 | ++ </naive-provider> |
| 119 | + </n-loading-bar-provider> |
| 120 | + </n-notification-provider> |
| 121 | + </n-dialog-provider> |
| 122 | + </n-message-provider> |
| 123 | +</template> |
| 124 | +
|
| 125 | +``` |
| 126 | +接下来我们调整request.ts中的引用: |
| 127 | +```typescript |
| 128 | +const errorHandler = (error: AxiosError): Promise<any> => { |
| 129 | + const { notification } = useGlobalConfig() |
| 130 | + // 判断是否存在response |
| 131 | + if (error.response) { |
| 132 | + const { data, status, statusText } = error.response as AxiosResponse<any> |
| 133 | + if (status === 401) { |
| 134 | + // 重新登录 |
| 135 | + notification?.error({ |
| 136 | + title: i18n.global.t('global.request.error.401'), |
| 137 | + content: data?.msg || statusText, |
| 138 | + duration: 3000, |
| 139 | + }) |
| 140 | + } |
| 141 | + else if (status === 403) { |
| 142 | + // |
| 143 | + notification?.error({ |
| 144 | + title: i18n.global.t('global.request.error.403'), |
| 145 | + content: data?.msg || statusText, |
| 146 | + duration: 3000, |
| 147 | + }) |
| 148 | + } |
| 149 | + else if (status === 500) { |
| 150 | + notification?.error({ |
| 151 | + title: i18n.global.t('global.request.error.500'), |
| 152 | + content: data?.msg || statusText, |
| 153 | + duration: 3000, |
| 154 | + }) |
| 155 | + } |
| 156 | + else { |
| 157 | + notification?.error({ |
| 158 | + title: i18n.global.t('global.request.error.other'), |
| 159 | + content: data?.msg || statusText, |
| 160 | + duration: 3000, |
| 161 | + }) |
| 162 | + } |
| 163 | + } |
| 164 | + return Promise.reject(error) |
| 165 | +} |
| 166 | + |
| 167 | +``` |
| 168 | +然后测试,发现可以正常使用了。 |
| 169 | +### 401跳转登录页面 |
| 170 | +接下来我们完成一下401跳转登录页面的功能。 |
| 171 | +首先我们项目中还没有登录页,我们先创建一个登录页面在pages中创建一个login文件夹,然后再创建一个index.vue的文件内容如下: |
| 172 | +```vue |
| 173 | +<script lang="ts" setup> |
| 174 | +
|
| 175 | +</script> |
| 176 | +
|
| 177 | +<template> |
| 178 | + <div> |
| 179 | + 登录页面 |
| 180 | + </div> |
| 181 | +</template> |
| 182 | +
|
| 183 | +``` |
| 184 | +然后再配置一下路由 |
| 185 | +```typescript |
| 186 | +import { createRouter, createWebHistory } from 'vue-router' |
| 187 | +import staticRoutes from '~/routes/static-routes' |
| 188 | +const router = createRouter({ |
| 189 | + routes: [ |
| 190 | + ...staticRoutes, |
| 191 | + { |
| 192 | + path: '/login', |
| 193 | + component: () => import('~/pages/login/index.vue'), |
| 194 | + name: 'Login', |
| 195 | + }, |
| 196 | + ], |
| 197 | + history: createWebHistory(import.meta.env.VITE_APP_BASE ?? '/'), |
| 198 | +}) |
| 199 | + |
| 200 | +export default router |
| 201 | + |
| 202 | +``` |
| 203 | +最后我们在request.ts中401的部分增加跳转逻辑: |
| 204 | +```typescript |
| 205 | +if (status === 401) { |
| 206 | + // 重新登录 |
| 207 | + notification?.error({ |
| 208 | + title: i18n.global.t('global.request.error.401'), |
| 209 | + content: data?.msg || statusText, |
| 210 | + duration: 3000, |
| 211 | + }) |
| 212 | + router.replace({ path: '/login' }).then(() => { |
| 213 | + /** |
| 214 | + * 这里处理清空用户信息和token的逻辑,后续扩展 |
| 215 | + */ |
| 216 | + token.value = null |
| 217 | + }) |
| 218 | +} |
| 219 | +``` |
| 220 | +刷新页面测试跳转 |
| 221 | + |
| 222 | +### 封装restfulAPI |
| 223 | +我们整个项目的增删改查我们会使用restfulAPI的方式进行请求接下来我们一起封装一下增删改查的请求。 |
| 224 | +在request.ts中: |
| 225 | +```typescript |
| 226 | + |
| 227 | +export const useGet = <P = any, R = any>(url: string, params?: P, config?: AxiosRequestConfig): Promise< ResponseBody<R>> => { |
| 228 | + return instance.request({ |
| 229 | + url, |
| 230 | + method: 'GET', |
| 231 | + params, |
| 232 | + ...config, |
| 233 | + }) |
| 234 | +} |
| 235 | + |
| 236 | +export const usePost = <P = any, R = any>(url: string, data?: P, config?: AxiosRequestConfig): Promise< ResponseBody<R>> => { |
| 237 | + return instance.request({ |
| 238 | + url, |
| 239 | + method: 'POST', |
| 240 | + data, |
| 241 | + ...config, |
| 242 | + }) |
| 243 | +} |
| 244 | + |
| 245 | +export const usePut = <P = any, R = any>(url: string, data?: P, config?: AxiosRequestConfig): Promise< ResponseBody<R>> => { |
| 246 | + return instance.request({ |
| 247 | + url, |
| 248 | + method: 'PUT', |
| 249 | + data, |
| 250 | + ...config, |
| 251 | + }) |
| 252 | +} |
| 253 | + |
| 254 | +export const useDelete = <P = any, R = any>(url: string, data?: P, config?: AxiosRequestConfig): Promise< ResponseBody<R>> => { |
| 255 | + return instance.request({ |
| 256 | + url, |
| 257 | + method: 'DELETE', |
| 258 | + data, |
| 259 | + ...config, |
| 260 | + }) |
| 261 | +} |
| 262 | +``` |
| 263 | +然后我们在compsables中创建一个axios-fetch.ts的文件: |
| 264 | +```typescript |
| 265 | +import { useDelete, useGet, usePost, usePut } from '~/utils/request' |
| 266 | + |
| 267 | +export { |
| 268 | + useGet, |
| 269 | + usePost, |
| 270 | + usePut, |
| 271 | + useDelete, |
| 272 | +} |
| 273 | +``` |
| 274 | +实现自动导入的功能。 |
| 275 | + |
| 276 | +接下来我们进行测试,我们准备了四个请求如下: |
| 277 | +```shell |
| 278 | +# get |
| 279 | +https://mock.28yanyu.cn/mock/637af0d4080d2f1284a9e77b/test/200 |
| 280 | +# post |
| 281 | +https://mock.28yanyu.cn/mock/637af0d4080d2f1284a9e77b/test/post |
| 282 | +# put |
| 283 | +https://mock.28yanyu.cn/mock/637af0d4080d2f1284a9e77b/test/put |
| 284 | +# delete |
| 285 | +https://mock.28yanyu.cn/mock/637af0d4080d2f1284a9e77b/test/delete |
| 286 | +``` |
| 287 | +接下来我们在pages/index.vue中进行测试 |
| 288 | +```vue |
| 289 | +<script lang="ts" setup> |
| 290 | +const onRequest1 = async () => { |
| 291 | + await useGet('https://mock.28yanyu.cn/mock/637af0d4080d2f1284a9e77b/test/200') |
| 292 | +} |
| 293 | +const onRequest2 = async () => { |
| 294 | + await usePost('https://mock.28yanyu.cn/mock/637af0d4080d2f1284a9e77b/test/post') |
| 295 | +} |
| 296 | +const onRequest3 = async () => { |
| 297 | + await usePut('https://mock.28yanyu.cn/mock/637af0d4080d2f1284a9e77b/test/put') |
| 298 | +} |
| 299 | +
|
| 300 | +const onRequest4 = async () => { |
| 301 | + await useDelete('https://mock.28yanyu.cn/mock/637af0d4080d2f1284a9e77b/test/delete') |
| 302 | +} |
| 303 | +</script> |
| 304 | +
|
| 305 | +<template> |
| 306 | + <div> |
| 307 | + <n-space> |
| 308 | + <n-button @click="onRequest1"> |
| 309 | + get |
| 310 | + </n-button> |
| 311 | + <n-button @click="onRequest2"> |
| 312 | + post |
| 313 | + </n-button> |
| 314 | + <n-button @click="onRequest3"> |
| 315 | + put |
| 316 | + </n-button> |
| 317 | + <n-button @click="onRequest4"> |
| 318 | + delete |
| 319 | + </n-button> |
| 320 | + </n-space> |
| 321 | + </div> |
| 322 | +</template> |
| 323 | +
|
| 324 | +<style scoped> |
| 325 | +
|
| 326 | +</style> |
| 327 | +
|
| 328 | +``` |
| 329 | +测试是否能够请求成功 |
0 commit comments