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 = '验证码发送失败,请稍后重试'
  }
}
相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅6 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax