Skip to content

Commit

Permalink
feat: 🚀 新增请求封装,全局消息提示
Browse files Browse the repository at this point in the history
  • Loading branch information
gaojianghua committed Aug 4, 2023
1 parent 9844884 commit dc94853
Show file tree
Hide file tree
Showing 26 changed files with 352 additions and 24 deletions.
1 change: 1 addition & 0 deletions .env.prod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NUXT_PUBLIC_API_BASE_URL='https://gaojianghua.cn'
15 changes: 13 additions & 2 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,19 @@ export default defineNuxtConfig({
build: {
transpile: ['dayjs'],
},
modules: ['@pinia/nuxt', '@vueuse/nuxt', '@nuxtjs/tailwindcss'],
css: ['~/styles/init.css'],
components: [
{
path: '@/components/public',
extensions: ['.vue'],
},
],
runtimeConfig: {
public: {
apiBaseUrl: process.env.NUXT_PUBLIC_API_BASE_URL
},
},
modules: ['@pinia/nuxt', '@vueuse/nuxt', '@nuxtjs/tailwindcss', '@pinia-plugin-persistedstate/nuxt'],
css: ['~/assets/styles/init.css'],
app: {
head: {
charset: 'utf-8',
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"typescript": "^4.9.4"
},
"dependencies": {
"@pinia-plugin-persistedstate/nuxt": "^1.1.1",
"@pinia/nuxt": "^0.4.6",
"@vueuse/core": "^10.3.0",
"@vueuse/nuxt": "^10.3.0",
Expand Down
4 changes: 4 additions & 0 deletions src/api/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* @description: 获取用户信息
*/
export const getUserinfo = (params?: any) => useRequest.get("/user/userinfo", params);
File renamed without changes.
Empty file added src/api/put.ts
Empty file.
Empty file added src/api/remove.ts
Empty file.
10 changes: 4 additions & 6 deletions src/app.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
<template>
<div>
<NuxtLayout>
<NuxtPage></NuxtPage>
</NuxtLayout>
</div>
</template>
<NuxtLayout>
<NuxtPage></NuxtPage>
</NuxtLayout>
</template>
Binary file added src/assets/image/error.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
Binary file added src/assets/image/success.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/image/warning.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/styles/init.css → src/assets/styles/init.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ a{
/* 机械字体包 */
@font-face {
font-family: NumFont;
src: url('./integer.ttf');
src: url('integer.ttf');
unicode-range: U+30-39;
}

Expand Down
File renamed without changes.
45 changes: 45 additions & 0 deletions src/components/message/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Message from './index.vue'

interface MessageProps {
tips?: string,
type?: any,
visible?: boolean,
duration?: number,
}

// 插件注册
import { createVNode, render } from 'vue'

const Ele = function(props: MessageProps) {
if (process.client) {
const id = 'message'
const div = document.querySelector('#' + id)
if (!props.visible) {
return
}
if (div) {
document.body.removeChild(div)
return
}
const container = document.createElement('div')
container.id = id
const vm = createVNode(Message, { id, ...props })
render(vm, container)
document.body.appendChild(container)
}
}

export default {
success(tips: string) {
Ele({ type: 'success', visible: true, tips })
},
warning(tips: string) {
Ele({ type: 'warning', visible: true, tips })
},
error(tips: string) {
Ele({ type: 'error', visible: true, tips })
},
default(tips: string) {
Ele({ type: 'default', visible: true, tips })
}
}
94 changes: 94 additions & 0 deletions src/components/message/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<template>
<div v-if="state" :class="{ zoomOut: !lock }" class="fixed m-auto left-0 right-0 top-[-50%] bottom-0 z-50 flex items-center justify-center zoomIn">
<div class="rounded-[.5rem] flex items-center px-[1rem] py-[.3rem] border" :class="{'bg-warning': status === 'warning','bg-success': status === 'success','bg-error': status === 'error', 'bg-info': status === 'default'}">
<img v-if="status === 'success'" class="w-[2rem] h-[2rem]" src="../../assets/image/success.png" />
<img v-if="status === 'error'" class="w-[2rem] h-[2rem]" src="../../assets/image/error.png" />
<img v-if="status === 'warning'" class="w-[2rem] h-[2rem]" src="../../assets/image/warning.png" />
<span class="text-[1.4rem]" :class="{'ml-2': status !== 'default'}">{{ title }}</span>
</div>
</div>
</template>

<script setup lang="ts">
type MessageType = 'default' | 'success' | 'warning' | 'error'
interface MessageProps {
tips?: string,
type?: MessageType,
visible?: boolean,
duration?: number,
id?: string,
}
const props = withDefaults(defineProps<MessageProps>(), {
tips: '',
type: 'default',
visible: false,
duration: 3000,
id: 'message'
})
const state = ref(false)
const title = ref(props.tips)
const status = ref(props.type)
const lock = ref(false)
const timer1 = ref()
const timer2 = ref()
watch(() => props.visible, (val: boolean) => {
state.value = val
show()
})
const hide = () =>{
timer1.value = setTimeout(() => {
lock.value = false
}, props.duration - 300)
timer2.value = setTimeout(() => {
state.value = false
document.body.removeChild(document.querySelector('#' + props.id)!)
}, props.duration)
}
const show = () => {
if (state.value) {
return
}
clearTimeout(timer1.value)
clearTimeout(timer2.value)
state.value = true
lock.value = true
hide()
}
onMounted(() => show())
</script>

<style scoped lang="scss">
.zoomIn {
animation: zoomIn 0.3s linear;
}
.zoomOut {
animation: zoomOut 0.3s linear;
}
@keyframes zoomIn {
0%,30% {
opacity: 0;
transform: scale(0.5);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes zoomOut {
0% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(0);
}
}
</style>
4 changes: 4 additions & 0 deletions src/composables/useRef.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const useRef = (value:any) => {
const status = ref(value)
return status.value
};
126 changes: 126 additions & 0 deletions src/composables/useRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {NuxtApp, UseFetchOptions} from "nuxt/app";

export enum ResultEnum {
SUCCESS = 200,
TOKEN_OVERDUE = 20001, // 用户登录失败
INTERNAL_SERVER_ERROR = 500, // 服务异常
}

interface DefaultResult<T = any> {
code: number;
data: T;
msg: string;
success: boolean;
}

type UrlType = string | Request | Ref<string | Request> | (() => string | Request);

type HttpOption<T> = UseFetchOptions<DefaultResult<T>>;

interface RequestConfig<T = any> extends HttpOption<T> {
// 忽略拦截,不走拦截,拥有 responseData,且 code !== 0 的情况下,直接返回 responseData,
// 但是若无 responseData, 不设置 ignoreGlobalErrorMessage 也会报错
ignoreCatch?: boolean;

// 忽略全局错误提示,走拦截,但是任何情况下都不会提示错误信息
ignoreGlobalErrorMessage?: boolean;
}

const request = async <T>(
url: UrlType,
params: any,
options: RequestConfig<T>
): Promise<DefaultResult<T> | T> => {
const headers = useRequestHeaders(["cookie"]);
const method = ((options?.method || "GET") as string).toUpperCase();
const runtimeConfig = useRuntimeConfig();
const nuxtApp = useNuxtApp();
const { $message, $login } = nuxtApp;
const { apiBaseUrl } = runtimeConfig.public;
const baseURL = `${apiBaseUrl}/api`;

// 处理用户信息过期
const handlerTokenOverdue = async () => {
const { _route } = nuxtApp;
// await $login(_route?.fullPath);
};

// 处理报错异常
const handlerError = (msg = "服务异常") => {
if (process.server) {
showError({ message: msg, statusCode: 500 });
} else {
$message.error(msg);
}
};

const { data, error } = await useFetch(url, {
baseURL,
headers,
credentials: "include",
params: method === "GET" ? params : undefined,
body: method === "POST" ? JSON.stringify(params) : undefined,
...options,
onRequest({ request, options }) {
// 设置请求报头
options.headers = options.headers || {}
/**如果接口需求携带token请求,则可先自行使用官方的useCookie()方法设置Cookie存储后,再使用useCookie()方法,取出token使用。如下例子:*/
//const token = useCookie('token')
//@ts-ignore
//options.headers.Authorization = token.value||null
},
onRequestError({ request, options, error }) {
// 处理请求错误
console.log("服务器链接失败!")
},
onResponse({ request, response, options }) {
// 处理响应数据
},
onResponseError({ request, response, options }) {
// 处理响应错误
}
});

const responseData = data.value as DefaultResult<T>;
const { ignoreCatch, ignoreGlobalErrorMessage } = options; // 忽略全局

if (error.value || !responseData) {
if (!ignoreGlobalErrorMessage) handlerError();
return Promise.reject(error.value || "服务响应失败,请稍后重试");
} else {
const { code, data: result, msg } = responseData;
// 接口请求成功,直接返回结果
if (code === ResultEnum.SUCCESS || !code) {
return result;
}
if (!ignoreCatch) {
// 接口请求错误,统一处理
switch (code) {
case ResultEnum.TOKEN_OVERDUE: // 登录信息过期,去登录
// 用户信息过期
await handlerTokenOverdue();
break;
default:
if (!ignoreGlobalErrorMessage) handlerError(msg);
return Promise.reject(msg || "服务响应失败,请稍后重试");
}
}
}
return responseData;
};

// 自动导出
export const useRequest = {
get: <T>(url: UrlType, params?: any, option?: RequestConfig<T>) => {
return request<T>(url, params, { method: "get", ...option });
},
post: <T>(url: UrlType, params?: any, option?: RequestConfig<T>) => {
return request<T>(url, params, { method: "post", ...option });
},
put: <T>(url: UrlType, params?: any, option?: RequestConfig<T>) => {
return request<T>(url, params, { method: "put", ...option });
},
remove: <T>(url: UrlType, params?: any, option?: RequestConfig<T>) => {
return request<T>(url, params, { method: "delete", ...option });
},
};
9 changes: 9 additions & 0 deletions src/middleware/auth.global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useUserStore } from '~/store';
export default defineNuxtRouteMiddleware(async (to) => {
const userStore = useUserStore();
// 获取用户信息
let { id } = userStore.$state.state.userInfo;
if (!id) {
// 用户不存在之后的操作
}
});
2 changes: 1 addition & 1 deletion src/pages/article.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</div>
<div ref="el" class="box flex-1 h-full p-3 overflow-y-scroll">
<div v-if="list.length === 0" class="flex flex-col items-center h-full w-full justify-center">
<img class="w-[30rem] h-[30rem]" src="@/static/image/list-empty.png" alt="列表为空">
<img class="w-[30rem] h-[30rem]" src="@/assets/image/list-empty.png" alt="列表为空">
<span class="mt-5 text-[#ccc] text-[1.2rem]">作者很懒,未发布新的内容!</span>
</div>
<div v-else class="box box-cd flex cursor-pointer items-center justify-between mb-3 p-3" v-for="(item, j) in list" :key="j" @click="openDetails(j)">
Expand Down
2 changes: 0 additions & 2 deletions src/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@


<script setup lang="ts">
import {ref} from 'vue'
let year = ref(new Date().getFullYear() - 2018)
let project = ref([
{
Expand Down
9 changes: 9 additions & 0 deletions src/plugins/message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import message from '~/components/message/index'

export default defineNuxtPlugin(() => {
return {
provide: {
message
}
}
})
10 changes: 5 additions & 5 deletions src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
* @Date : 2023-01-15 09:39:07
* @LastEditors : 15257184434 [email protected]
* @LastEditTime : 2023-01-15 09:46:10
* @FilePath : \varlet-nuxt3-example\src\store\index.ts
* @Description :
*
* Copyright (c) 2023 by 15257184434 [email protected], All Rights Reserved.
* @FilePath : \varlet-nuxt3-example\src\store\useRef.ts
* @Description :
*
* Copyright (c) 2023 by 15257184434 [email protected], All Rights Reserved.
*/
export * from './useUserStore'
export * from './useUserStore'
Loading

0 comments on commit dc94853

Please sign in to comment.