chore: 重写初始提交(清空历史,整理后全量提交)

This commit is contained in:
ywxapp
2026-08-16 16:54:14 +08:00
commit 6c1a106bc1
1808 changed files with 238144 additions and 0 deletions
+422
View File
@@ -0,0 +1,422 @@
// Layui 轻量级请求模块(基于 Fetch API + layui layer
// 支持文件上传
//
// 使用示例:
// layui.use(['http'], function(){ var req = layui.request; req.post('/api/upload', formData).then(...).catch(...) });
layui.define(["layer", "setter"], function (exports) {
"use strict";
var setter = layui.setter,
layer = layui.layer;
let isRefreshing = false;
let refreshQueue = [];
let loadIndex;
// 默认配置
const defaultConfig = {
baseURL: '',
timeout: 30000,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
withCredentials: true,
};
/**
* 刷新 Tokens
* @returns {Promise<string>}
*/
async function refreshToken() {
const { refresh_token } = setter.getTokens();
if (!refresh_token) {
throw new Error("No refresh token");
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), defaultConfig.timeout);
try {
var Accept = defaultConfig.headers['Accept'] || 'application/json';
const response = await fetch(defaultConfig.baseURL + "/api/refresh", {
method: "POST",
headers: {
'Content-Type': 'application/json',
'Accept': Accept,
...defaultConfig.headers,
},
credentials: defaultConfig.withCredentials ? 'include' : 'same-origin',
signal: controller.signal,
body: JSON.stringify({ refresh_token }),
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const res = await response.json();
if (res.code === 200) {
setter.saveTokens({
access_token: res.access || res.access_token,
refresh_token: res.refresh || res.refresh_token || refresh_token,
});
return res.access || res.access_token;
} else {
throw new Error(res.msg || "Refresh failed");
}
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
}
}
/**
* 重试请求
* @param {Object} options - fetch 配置选项
* @returns {Promise}
*/
async function retryRequest(options) {
const { access_token } = setter.getTokens();
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), options.timeout || defaultConfig.timeout);
try {
// 构建请求头
const headers = { ...defaultConfig.headers, ...options.headers };
const { access_token: newToken } = setter.getTokens();
if (newToken) {
headers['Authorization'] = `Bearer ${newToken}`;
}
// 如果是 FormData,删除 Content-Type 让浏览器自动设置
if (options.data instanceof FormData) {
delete headers['Content-Type'];
}
const fetchOptions = {
method: options.method || 'GET',
headers,
credentials: defaultConfig.withCredentials ? 'include' : 'same-origin',
signal: controller.signal,
};
// 添加 body(如果不是 GET/HEAD 请求)
if (options.method !== 'GET' && options.method !== 'HEAD') {
fetchOptions.body = options.data instanceof FormData
? options.data
: JSON.stringify(options.data);
}
const response = await fetch(options.url, fetchOptions);
clearTimeout(timeoutId);
// 处理响应头中的 token
const authHeader = response.headers.get('Authorization');
if (authHeader && authHeader.startsWith('Bearer ')) {
const newToken = authHeader.substring(7);
setter.saveTokens({ access_token: newToken });
}
let data;
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
data = await response.json();
} else if (contentType && contentType.includes('text/')) {
data = await response.text();
} else {
data = await response.blob();
}
if (!response.ok) {
const error = new Error(response.statusText || 'Request failed');
error.status = response.status;
error.data = data;
throw error;
}
return data;
} catch (error) {
if (error.name === 'AbortError') {
error.message = 'Request timeout';
}
throw error;
}
}
/**
* Fetch 请求核心方法
* @param {Object} options - fetch 配置选项
* @returns {Promise}
*/
async function _Request(options) {
const { access_token } = setter.getTokens();
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), options.timeout || defaultConfig.timeout);
try {
// 构建请求头
const headers = { ...defaultConfig.headers, ...options.headers };
if (access_token) {
headers['Authorization'] = `Bearer ${access_token}`;
}
// 如果是 FormData,删除 Content-Type 让浏览器自动设置
if (options.data instanceof FormData) {
delete headers['Content-Type'];
}
const fetchOptions = {
method: options.method || 'GET',
headers,
credentials: defaultConfig.withCredentials ? 'include' : 'same-origin',
signal: controller.signal,
};
// 添加 body(如果不是 GET/HEAD 请求)
if (options.method !== 'GET' && options.method !== 'HEAD') {
fetchOptions.body = options.data instanceof FormData
? options.data
: JSON.stringify(options.data);
}
const response = await fetch(options.url, fetchOptions);
clearTimeout(timeoutId);
// 处理响应头中的 token
const authHeader = response.headers.get('Authorization');
if (authHeader && authHeader.startsWith('Bearer ')) {
const newToken = authHeader.substring(7);
setter.saveTokens({ access_token: newToken });
}
let data;
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
data = await response.json();
} else if (contentType && contentType.includes('text/')) {
data = await response.text();
} else {
data = await response.blob();
}
// 检查响应数据中的 token
if (data && (data.access_token || data.access)) {
setter.saveTokens({
access_token: data.access_token || data.access,
refresh_token: data.refresh_token || data.refresh,
});
}
if (!response.ok) {
if (response.status === 401 && !options._retry) {
// 401 错误,尝试刷新 token
return new Promise((resolve, reject) => {
refreshQueue.push({ resolve, reject, options });
if (!isRefreshing) {
isRefreshing = true;
refreshToken()
.then(() => {
const queue = [...refreshQueue];
refreshQueue = [];
queue.forEach(({ resolve, reject, options }) => {
options._retry = true;
retryRequest(options)
.then(resolve)
.catch(reject);
});
isRefreshing = false;
})
.catch((refreshError) => {
refreshQueue.forEach(({ reject }) => reject(refreshError));
refreshQueue = [];
isRefreshing = false;
// 清除本地存储并跳转登录
localStorage.clear();
layer.msg("登录已过期,请重新登录", { icon: 2, time: 2000 });
setTimeout(() => {
window.top.location.href = "./login.html";
// if (window === window.parent) {
// window.location.href = "./login.html";
// } else {
// window.parent.location.href = "./login.html";
// }
}, 2000);
reject(refreshError);
});
}
});
} else {
const error = new Error(response.statusText || 'Request failed');
error.status = response.status;
error.data = data;
throw error;
}
}
return data;
} catch (error) {
if (error.name === 'AbortError') {
error.message = 'Request timeout';
}
throw error;
}
};
async function setAccept(accept = "json") {
switch (accept) {
case 'xml':
defaultConfig.headers['Accept'] = `application/xml`;
break;
case 'html':
defaultConfig.headers['Accept'] = `text/html`;
break;
default:
defaultConfig.headers['Accept'] = `application/json`;
break;
}
}
// 暴露接口
var http = {
/**
* 配置请求默认参数
* @param {Object} cfg - 配置对象
* @returns {Object} 当前配置
*/
config: function (cfg) {
Object.assign(defaultConfig, cfg || {});
return defaultConfig;
},
/**
* 通用请求方法
* @param {Object} options - fetch 配置选项
* @returns {Promise}
*/
request: _Request,
/**
* GET 请求
* @param {string} url - 请求地址
* @param {Object} params - URL 参数
* @param {Object} options - fetch 配置选项
* @returns {Promise}
*/
get: function (url, params, options = {}) {
// 处理 URL 参数
if (params && Object.keys(params).length > 0) {
const queryString = new URLSearchParams(params).toString();
url += (url.includes('?') ? '&' : '?') + queryString;
}
options.method = "GET";
options.url = defaultConfig.baseURL + url;
return _Request(options);
},
/**
* POST 请求
* @param {string} url - 请求地址
* @param {Object|FormData} data - 请求数据或 FormData 对象
* @param {Object} options - fetch 配置选项
* @returns {Promise}
*/
post: function (url, data, options = {}) {
options.method = "POST";
options.url = defaultConfig.baseURL + url;
options.data = data;
return _Request(options);
},
/**
* PUT 请求
* @param {string} url - 请求地址
* @param {Object|FormData} data - 请求数据或 FormData 对象
* @param {Object} options - fetch 配置选项
* @returns {Promise}
*/
put: function (url, data, options = {}) {
options.method = "PUT";
options.url = defaultConfig.baseURL + url;
options.data = data;
return _Request(options);
},
/**
* DELETE 请求
* @param {string} url - 请求地址
* @param {Object} params - URL 参数
* @param {Object} options - fetch 配置选项
* @returns {Promise}
*/
delete: function (url, params, options = {}) {
// 处理 URL 参数
if (params && Object.keys(params).length > 0) {
const queryString = new URLSearchParams(params).toString();
url += (url.includes('?') ? '&' : '?') + queryString;
}
options.method = "DELETE";
options.url = defaultConfig.baseURL + url;
return _Request(options);
},
/**
* 上传文件(简化版)
* @param {string} url - 上传地址
* @param {File|File[]} files - 文件对象或文件数组
* @param {Object} params - 其他参数
* @param {Object} options - fetch 配置选项
* @returns {Promise}
*/
upload: function (url, files, params = {}, options = {}) {
const formData = new FormData();
// 添加文件
if (Array.isArray(files)) {
files.forEach((file, index) => {
formData.append(`file${index}`, file);
});
} else if (files) {
formData.append('file', files);
}
// 添加其他参数
Object.keys(params).forEach(key => {
formData.append(key, params[key]);
});
options.method = "POST";
options.url = defaultConfig.baseURL + url;
options.data = formData;
return _Request(options);
},
/**
* 刷新 token
* @returns {Promise<string>}
*/
refreshToken: refreshToken,
accept: setAccept,
};
exports("http", http);
});