Nuxt 的 网络请求 添加代理

本地调试的时候加上代理,ssr网络请求就不会出现奇奇怪怪的问题

添加代理

csharp 复制代码
// server/api/proxy.ts

export default defineEventHandler(async (event) => {
    const query = getQuery(event)
    const targetUrl = query.url as string
    const method = getMethod(event)
    const body = method === 'POST' ? await readBody(event) : undefined
    const headers = {
        ...getHeaders(event),
        "x-forwarded-host": getRequestURL(event).host,
    }
    return await $fetch(targetUrl, {
        method,
        headers,
        params: query,
        body,
        credentials: "include",
    })
})

网络请求类 useFetchSSRApi.ts

  • useFetchSSRApi.ts
typescript 复制代码
import {useRequestHeaders, useRuntimeConfig} from "#app";
import {useLangStore} from "~/stores/useLangStore"; // 本地缓存

/**
 * SSR 请求
 */

export type HttpMethod = "GET" | "POST";


export interface ApiOptions {
    method?: HttpMethod
    params?: Record<string, any>
    body?: any
    headers?: HeadersInit
    lazy?: boolean
    immediate?: boolean
    key?: string
    baseUri?: string
    server?: boolean
}

export function useFetchSSRApi(
    url: string,
    options: ApiOptions = {}
) {
    // const nuxtApp = useNuxtApp()

    const {locale} = useI18n()
    const langStore = useLangStore();
    const route = useRoute()

    // 公共参数
    let lang: string = Array.isArray(route.query.lang) ? route.query.lang[0] : route.query.lang;
    if (!lang || lang.length <= 0) {
        lang = locale.value
    }
    if (!lang || lang.length <= 0) {
        lang = langStore.getLang()
    }
    lang = lang || useCookie('Lang').value || ''

    let currency: string = Array.isArray(route.query.currency) ? route.query.currency[0] : route.query.currency;
    currency = useCookie('currency').value || currency || ''

    let token: string = Array.isArray(route.query.token) ? route.query.token[0] : route.query.token;
    token = useCookie('token').value || token || '';

    // 合并参数
    const defaultParams = {lang, currency, token}
    options.params = Object.assign({}, options.params, defaultParams)

    // 方法
    const method = options.method || 'POST';

    const key = options.key || url

    let bURL = options.baseUri;
    if (bURL == undefined || bURL.length <= 0) {
        const {public: {API_BASE_DEV, API_BASE_PROD}} = useRuntimeConfig();
        bURL = import.meta.env.MODE === "production" ? API_BASE_PROD : API_BASE_DEV;
    }

    let finalUrl = new URL(bURL).toString() + url

    // 是否走代理(开发用)
    const useProxy = import.meta.dev && bURL.includes('http')
    finalUrl = useProxy ? `/api/proxy?url=${encodeURIComponent(finalUrl)}` : finalUrl;

    const headers: HeadersInit = {
        "Accept": 'application/json',
        "Content-Type": 'application/json',
        "Authorization": token ? `Bearer ${token}` : '',
        "Lang": lang,
        // "currency": currency,
        ...options.headers,
    }

    // SSR 兼容请求头(如 Cookie)
    if (import.meta.server) {
        const reqHeaders = useRequestHeaders(['cookie'])
        if (reqHeaders.cookie) headers['cookie'] = reqHeaders.cookie
    }

    // useFetch 自动处理 SSR/CSR
    return useFetch(finalUrl, {
        ...options,
        key,
        method,
        headers,
        params: method === 'GET' ? {...options.params, lang, currency, token} : undefined,
        body: method !== 'GET' ? options.body : undefined,
        credentials: 'include',
        lazy: options.lazy ?? false,
        immediate: options.immediate ?? true,
        onRequest: interceptorRequest,
        onResponse: interceptorResponse,
        onResponseError({response}) {
            console.log('Server or network error >>> ', response._data)
            throw response._data;
        },
    });


    // useAsyncData 自动处理 SSR/CSR
    //


}


let MessageBox_show = false;

function showMessage(msg: string) {
    if (import.meta.client && !MessageBox_show) {
        MessageBox_show = true;
    }
}

function jumpToLogin() {
    // 需要强制登录,直接跳转
    useNuxtApp().runWithContext(() => navigateTo("/login"));
}

// 请求拦截器
function interceptorRequest({options}) {
    // token
}


// 响应拦截器
async function interceptorResponse({response}) {

    if (response.status >= 200 || response.status < 300) {
        if (response._data.status == 222) {
            // 需要登录,提示
            showMessage("Please log in !");
        } else if (response._data.status == 223) {
            jumpToLogin();
        }
    }
}

非ssr请求, 其实也可以用 useFetchSSRApi.ts 但是又封装了一个请求类 useFetchRequest.ts

typescript 复制代码
import {useRuntimeConfig} from "#app";

interface RequestOptions {
    baseUrl?: string;

    [key: string]: any;
}

type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "JSONP";

// 请求拦截器
function interceptorRequest({options}) {
    // token
    console.log('interceptorRequest >>> ', options)

    let token = useTokenStore().token
    options.headers = {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
        ...options.headers,
    };
}

let MessageBox_show = false;

function showMessage(msg: string) {
    if (import.meta.client && !MessageBox_show) {
        MessageBox_show = true;
    }
}

function jumpToLogin() {
    // 需要强制登录,直接跳转
    useNuxtApp().runWithContext(() => navigateTo("/login"));
}

// 响应拦截器
async function interceptorResponse({response}) {

    console.log('interceptorResponse >>> ', response)

    if (response.status >= 200 || response.status < 300) {
        if (response._data.status == 222) {
            // 需要登录,提示
            showMessage("Please log in !");
        } else if (response._data.status == 223) {
            jumpToLogin();
        }
    }
}

async function handleUseFetch(url: string, params: any, options: {}) {
    const {data, error} = await useFetch(url, {
        body: params,
        ...options,
        server: false,
        onRequest: interceptorRequest,
        onResponse: interceptorResponse,
        onResponseError({response}) {
            console.log('Server or network error >>> ', response._data)
            throw response._data;
        },
    });
    return data.value;
}


/**
 * 创建请求方法
 * @param method
 */
function createFetchRequest(method: HttpMethod) {
    return function (url: string, data?: any, options: RequestOptions = {}) {
        const {
            public: {API_BASE_DEV, API_BASE_PROD},
        } = useRuntimeConfig();

        // method
        options["method"] = method;
        if (options && options.baseURL && options.baseURL.length > 0) {
            url = new URL(options.baseURL + url).toString();
        } else {
            const envBaseURL = import.meta.env.MODE === "production" ? API_BASE_PROD : API_BASE_DEV;
            url = new URL(envBaseURL + url).toString();
        }

        // ✅ 如果是 GET 请求,自动拼接参数到 URL 上
        if (method === "GET" && data && typeof data === "object") {
            const queryString = new URLSearchParams(data).toString();
            url += url.includes("?") ? `&${queryString}` : `?${queryString}`;
            // ✅ GET 请求不需要 body 参数
            data = null;
        }
        return handleUseFetch(url, data, options);
    };
}

// 提供 $fetch & HTTP 方法 - 统一管理请求 - 再到组件中使用
export const useApiGet = createFetchRequest("GET");
export const useApiPost = createFetchRequest("POST");
export const useApiPut = createFetchRequest("PUT");
export const useApiDelete = createFetchRequest("DELETE");


// // 发起请求
// function handleUseFetch(url: string, params: any, options: {}): Promise<any> {
//    return new Promise(async (resolve, reject) => {
//       useFetch(url, {
//          body: params,
//          ...options,
//          server: true,
//          onRequest: interceptorRequest,
//          onResponse: interceptorResponse,
//          onResponseError({ response }) {
//             showMessage(response._data ?? "Server or network error");
//             reject(response._data);
//          },
//       })
//          .then(({ data, error }: any) => {
//             resolve(data.value);
//          })
//          .catch((err: any) => {
//             reject(err);
//          });
//    });
// }

配合上面的请求了,统一管理接口,写在 api.ts

javascript 复制代码
import {useApiPost} from "./useFetchRequest";

export const useApiHome = {
    detail: {
        tips: "首页详情",
        url: `?s=/index/index/home`,
        get: async function (params: any = {}, options: {} = {}) {
            return await useApiPost(this.url, params, options);
        },
    },
    categories: {
        tips: "分类tree列表",
        url: `?s=/index/category/treeData`,
        get: async function (params: any = {}, options: {} = {}) {
            return await useApiPost(this.url, params, options);
        },
    },
};

调用

typescript 复制代码
// 获取验证码
const handleGetCode = async () => {
  // 模拟发送验证码接口调用
  try {
    useApiStormFrom.sendSmsCaptcha
    .get({phone: form.phone})
    .then((res: any) => {
      if (res.status === 200) {
        // 验证码发送成功
        showNotify(res.message)
      } else {
        showNotify(res.message, 'warning')
        errors.code = res.message;
      }
    })
    .catch((error) => {
      errors.code = error;
    });

    // 启动倒计时
    
  } catch (error) {
    console.error('发送验证码失败:', error)
    errors.code = '验证码发送失败,请稍后重试'
  }
}
相关推荐
天才熊猫君1 小时前
npm 和 pnpm 的一些理解
前端
飞飞飞仔1 小时前
从 Cursor AI 到 Claude Code AI:我的辅助编程转型之路
前端
qb1 小时前
vue3.5.18源码:调试方式
前端·vue.js·架构
Spider_Man1 小时前
缓存策略大乱斗:让你的页面快到飞起!
前端·http·node.js
前端老鹰1 小时前
CSS overscroll-behavior:解决滚动穿透的 “边界控制” 专家
前端·css·html
一叶怎知秋2 小时前
【openlayers框架学习】九:openlayers中的交互类(select和draw)
前端·javascript·笔记·学习·交互
allenlluo2 小时前
浅谈Web Components
前端·javascript
Mintopia2 小时前
把猫咪装进 public/ 文件夹:Next.js 静态资源管理的魔幻漂流
前端·javascript·next.js
Spider_Man2 小时前
预览一开,灵魂出窍!低代码平台的魔法剧场大揭秘🎩✨
前端·低代码·typescript
xianxin_2 小时前
HTML 代码编写规范
前端