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 = '验证码发送失败,请稍后重试'
  }
}
相关推荐
FSHOW2 分钟前
【独立开发日记】MQ端到端类型安全
前端·javascript·后端
老华带你飞15 分钟前
社区互助|基于SSM+vue的社区互助平台的设计与实现(源码+数据库+文档)
java·前端·数据库·vue.js·小程序·毕设·社区互助平台
一支鱼27 分钟前
前端使用次数最多的工具封装
前端·typescript·编程语言
GIS之路30 分钟前
GDAL 简介
前端
前端工作日常44 分钟前
单元测试与E2E测试中使用浏览器的原因及区别
前端·单元测试
Js_cold1 小时前
Notepad++使用技巧1
前端·javascript·fpga开发·notepad++
接着奏乐接着舞。2 小时前
前端RSA加密遇到Java后端解密失败的问题解决
java·开发语言·前端
dreams_dream2 小时前
vue中的与,或,非
前端·javascript·vue.js
柳杉3 小时前
使用three.js搭建3d隧道监测-3
前端·javascript·three.js
携欢3 小时前
PortSwigger靶场之Reflected XSS into HTML context with nothing encoded通关秘籍
前端·xss