本地调试的时候加上代理,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 = '验证码发送失败,请稍后重试'
}
}