Prefer server quote status, avoid false no-quote before ready, toast on 401 re-login, and keep product/banner image URLs absolute. Co-authored-by: Cursor <cursoragent@cursor.com>
255 lines
7.7 KiB
TypeScript
255 lines
7.7 KiB
TypeScript
import { getConfigApp } from '@/utils/config'
|
|
import { baseURL as fallbackBaseURL } from '@/themeConfig'
|
|
import type {AxiosRequestConfig, AxiosResponse} from 'axios'
|
|
import axios from 'axios'
|
|
import {getToken, removeToken} from "@/utils";
|
|
import router from "@/router";
|
|
import {get} from 'lodash'
|
|
import {message} from "ant-design-vue";
|
|
|
|
interface Meta {
|
|
errorMessage?: 'message' | 'notification' | 'toast' | 'none'
|
|
}
|
|
|
|
interface MyAxiosRequestConfig<D = any> extends AxiosRequestConfig<D> {
|
|
meta?: Meta
|
|
}
|
|
|
|
const headers = {
|
|
'Content-Type': 'application/json',
|
|
Accept: 'application/json',
|
|
}
|
|
|
|
const _axios = axios.create({
|
|
baseURL: fallbackBaseURL,
|
|
headers,
|
|
})
|
|
|
|
_axios.interceptors.request.use(
|
|
(config) => {
|
|
const runtimeBaseURL = getConfigApp('node_server')
|
|
if (runtimeBaseURL) {
|
|
config.baseURL = runtimeBaseURL
|
|
}
|
|
//console.log('request config', config)
|
|
if (config.headers) {
|
|
const token = getToken()
|
|
if (token) {
|
|
//@ts-ignore
|
|
config.headers['Authorization'] = 'Bearer ' + token
|
|
}
|
|
}
|
|
return config
|
|
},
|
|
(error) => {
|
|
//console.log('this is error on interceptors.request', error)
|
|
Promise.reject(error)
|
|
},
|
|
)
|
|
_axios.interceptors.response.use(
|
|
(response) => {
|
|
//console.log('this is response')
|
|
// Any status code that lie within the range of 2xx cause this function to trigger
|
|
// Do something with response data
|
|
//console.log('this is fulfilled on interceptors.response', response.data)
|
|
return response.data
|
|
},
|
|
async function (error) {
|
|
const response = error.response
|
|
const config = error.config as MyAxiosRequestConfig | undefined
|
|
|
|
if (response?.status === 401) {
|
|
removeToken()
|
|
showPleaseLoginAgain()
|
|
const routeName = router.currentRoute.value.name
|
|
const isPublicRoute =
|
|
router.currentRoute.value.meta?.public === true ||
|
|
['buyProduct', 'productDetail', 'about', 'cart', 'tvMonitor', 'login', '404'].includes(
|
|
routeName as string,
|
|
)
|
|
if (!isPublicRoute) {
|
|
await router.push({name: 'login'})
|
|
}
|
|
return Promise.reject(error)
|
|
}
|
|
|
|
if (!shouldShowApiError(config)) {
|
|
return Promise.reject(error)
|
|
}
|
|
|
|
const apiMessage = extractApiErrorMessage(response?.data)
|
|
const status = Number(response?.status ?? 0)
|
|
|
|
if (response?.status === 403) {
|
|
showApiError(
|
|
withHttpStatus(apiMessage || 'شما دسترسی لازم برای انجام این عملیات را ندارید', 403),
|
|
config,
|
|
)
|
|
return Promise.reject(error)
|
|
}
|
|
|
|
if (response?.status === 422) {
|
|
if (apiMessage) {
|
|
showApiError(withHttpStatus(apiMessage, 422), config)
|
|
} else {
|
|
showApiError(withHttpStatus('خطا در اعتبارسنجی', 422), config)
|
|
}
|
|
return Promise.reject(error)
|
|
}
|
|
|
|
if (response) {
|
|
if (status >= 500) {
|
|
showApiError(withHttpStatus(SERVER_ERROR, status), config)
|
|
} else {
|
|
showApiError(withHttpStatus(apiMessage || DEFAULT_LOAD_ERROR, status), config)
|
|
}
|
|
return Promise.reject(error)
|
|
}
|
|
|
|
// بدون status (نه 4xx نه 5xx): قطع شبکه، timeout، DNS، …
|
|
showApiError(NETWORK_ERROR, config)
|
|
return Promise.reject(error)
|
|
})
|
|
|
|
const DEFAULT_LOAD_ERROR = 'خطا در بارگذاری اطلاعات'
|
|
const SERVER_ERROR = 'خطا در برقراری ارتباط با سرور'
|
|
const NETWORK_ERROR = 'لطفا اینترنت خود را بررسی کنید و مجددا تلاش کنید'
|
|
const LOGIN_AGAIN = 'لطفا مجددا وارد شوید'
|
|
let lastAuthToastAt = 0
|
|
|
|
function showPleaseLoginAgain() {
|
|
const now = Date.now()
|
|
if (now - lastAuthToastAt < 2500) return
|
|
lastAuthToastAt = now
|
|
message.error({
|
|
content: LOGIN_AGAIN,
|
|
key: 'please-login-again',
|
|
duration: 3,
|
|
})
|
|
}
|
|
|
|
function withHttpStatus(message: string, status: number): string {
|
|
return `${message} (${status})`
|
|
}
|
|
|
|
function extractApiErrorMessage(data: unknown): string {
|
|
if (!data || typeof data !== 'object') {
|
|
return ''
|
|
}
|
|
|
|
const record = data as Record<string, unknown>
|
|
const rawMessage = record.message
|
|
|
|
if (typeof rawMessage === 'string' && rawMessage.trim()) {
|
|
return rawMessage.trim()
|
|
}
|
|
|
|
if (typeof rawMessage === 'number') {
|
|
return String(rawMessage)
|
|
}
|
|
|
|
const rawError = record.error
|
|
if (typeof rawError === 'string' && rawError.trim()) {
|
|
return rawError.trim()
|
|
}
|
|
|
|
const errors = record.errors
|
|
if (errors && typeof errors === 'object') {
|
|
for (const value of Object.values(errors as Record<string, unknown>)) {
|
|
if (Array.isArray(value) && typeof value[0] === 'string' && value[0].trim()) {
|
|
return value[0].trim()
|
|
}
|
|
if (typeof value === 'string' && value.trim()) {
|
|
return value.trim()
|
|
}
|
|
}
|
|
}
|
|
|
|
return ''
|
|
}
|
|
|
|
function shouldShowApiError(config: MyAxiosRequestConfig | undefined): boolean {
|
|
return get(config, 'meta.errorMessage', 'message') !== 'none'
|
|
}
|
|
|
|
function showApiError(msg: string, config: MyAxiosRequestConfig | undefined) {
|
|
if (!shouldShowApiError(config)) {
|
|
return
|
|
}
|
|
|
|
message.error(msg)
|
|
}
|
|
|
|
const myAxios = {
|
|
//AxiosResponse<T>
|
|
post<T = any, R = T, D = any>(
|
|
url: string,
|
|
data?: D,
|
|
config?: MyAxiosRequestConfig<D>,
|
|
): Promise<R> {
|
|
return _axios.post(url, data, config)
|
|
},
|
|
request<T = any, R = AxiosResponse<T>, D = any>(config: MyAxiosRequestConfig<D>): Promise<R> {
|
|
return _axios.request(config)
|
|
},
|
|
//AxiosResponse<T>
|
|
get<T = any, R = T, D = any>(url: string, config?: MyAxiosRequestConfig<D>): Promise<R> {
|
|
return _axios.get(url, config)
|
|
},
|
|
//AxiosResponse<T>
|
|
delete<T = any, R = T, D = any>(url: string, config?: MyAxiosRequestConfig<D>): Promise<R> {
|
|
return _axios.delete(url, config)
|
|
},
|
|
//AxiosResponse<T>
|
|
head<T = any, R = T, D = any>(url: string, config?: MyAxiosRequestConfig<D>): Promise<R> {
|
|
return _axios.head(url, config)
|
|
},
|
|
//AxiosResponse<T>
|
|
options<T = any, R = T, D = any>(url: string, config?: MyAxiosRequestConfig<D>): Promise<R> {
|
|
return _axios.options(url, config)
|
|
},
|
|
//AxiosResponse<T>
|
|
put<T = any, R = T, D = any>(
|
|
url: string,
|
|
data?: D,
|
|
config?: MyAxiosRequestConfig<D>,
|
|
): Promise<R> {
|
|
return _axios.put(url, data, config)
|
|
},
|
|
//AxiosResponse<T>
|
|
patch<T = any, R = T, D = any>(
|
|
url: string,
|
|
data?: D,
|
|
config?: MyAxiosRequestConfig<D>,
|
|
): Promise<R> {
|
|
return _axios.patch(url, data, config)
|
|
},
|
|
//AxiosResponse<T>
|
|
postForm<T = any, R = T, D = any>(
|
|
url: string,
|
|
data?: D,
|
|
config?: MyAxiosRequestConfig<D>,
|
|
): Promise<R> {
|
|
return _axios.postForm(url, data, config)
|
|
},
|
|
//AxiosResponse<T>
|
|
putForm<T = any, R = T, D = any>(
|
|
url: string,
|
|
data?: D,
|
|
config?: MyAxiosRequestConfig<D>,
|
|
): Promise<R> {
|
|
return _axios.putForm(url, data, config)
|
|
},
|
|
//AxiosResponse<T>
|
|
patchForm<T = any, R = T, D = any>(
|
|
url: string,
|
|
data?: D,
|
|
config?: MyAxiosRequestConfig<D>,
|
|
): Promise<R> {
|
|
return _axios.patchForm(url, data, config)
|
|
},
|
|
}
|
|
|
|
export default myAxios
|
|
export {_axios}
|