胶囊型按钮UI美化
3个月前 <br />
debug: true, // 开启调试日志<br />
debounceTime: 200, // 全局防抖延迟(ms)<br />
poolMax: 8, // 请求池最大实例数<br />
poolIdleTimeout: 20000, // 实例闲置超时销毁时间(ms)<br />
headers: { // 全局请求头<br />
'Content-Type': 'application/json',<br />
'Authorization': 'Bearer your-token'<br />
}<br />
});<br />
```<br />
<br />
### 2. 发送 GET 请求<br />
<br />
```javascript<br />
// 获取用户列表<br />
const { promise, cancel } = http.get('/user/list', {<br />
data: { page: 1, size: 10 }, // 请求参数<br />
cancelRepeat: true, // 取消重复请求<br />
debounceTime: 300 // 单次请求防抖时间(覆盖全局)<br />
});<br />
<br />
promise.then(res => {<br />
console.log('请求成功:', res.data);<br />
}).catch(err => {<br />
console.error('请求失败:', err.message);<br />
});<br />
<br />
// 手动取消请求(如组件卸载时)<br />
// cancel();<br />
```<br />
<br />
### 3. 发送 POST 请求(JSON 格式)<br />
<br />
```javascript<br />
// 添加用户<br />
http.post('/user/add', {<br />
data: { username: 'test', password: '123456' },<br />
headers: { 'X-Request-ID': 'xxx' } // 单次请求头<br />
}).promise.then(res => {<br />
console.log('添加成功:', res);<br />
});<br />
```<br />
<br />
### 4. 文件上传(FormData 格式)<br />
<br />
```javascript<br />
const uploadFile = (file) => {<br />
const formData = new FormData();<br />
formData.append('file', file);<br />
<br />
http.post('/upload', {<br />
data: formData,<br />
headers: { 'Content-Type': 'multipart/form-data' },<br />
onUploadProgress: (e) => {<br />
console.log(`上传进度: ${(e.loaded / e.total) * 100}%`);<br />
}<br />
}).promise.then(res => {<br />
console.log('上传成功:', res.data);<br />
});<br />
};<br />
<br />
// 调用示例:绑定文件选择框<br />
document.querySelector('#file-input').addEventListener('change', (e) => {<br />
uploadFile(e.target.files[0]);<br />
});<br />
```<br />
<br />
### 5. 文件下载(Blob 格式)<br />
<br />
```javascript<br />
const downloadFile = () => {<br />
http.get('/file/export', {<br />
responseType: 'blob', // 响应类型为 Blob<br />
onDownloadProgress: (e) => {<br />
console.log(`下载进度: ${(e.loaded / e.total) * 100}%`);<br />
}<br />
}).promise.then(res => {<br />
// 生成下载链接<br />
const blobUrl = URL.createObjectURL(res.data);<br />
const a = document.createElement('a');<br />
a.href = blobUrl;<br />
a.download = 'export.xlsx'; // 文件名<br />
a.click();<br />
URL.revokeObjectURL(blobUrl); // 释放资源<br />
});<br />
};<br />
```<br />
<br />
## 三、核心功能<br />
<br />
### 1. 拦截器<br />
<br />
支持注册**请求拦截器**(请求发送前修改配置)和**响应拦截器**(响应返回后统一处理)。<br />
<br />
```javascript<br />
// 1. 请求拦截器:添加 Token<br />
http.useRequestInterceptor(config => {<br />
config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`;<br />
return config;<br />
});<br />
<br />
// 2. 响应拦截器:统一错误处理<br />
http.useResponseInterceptor(res => {<br />
// 登录失效处理<br />
if (res.data?.code === 401) {<br />
localStorage.removeItem('token');<br />
location.href = '/login';<br />
}<br />
return res;<br />
});<br />
```<br />
<br />
### 2. 防抖功能<br />
<br />
适用于高频请求场景(如输入框实时搜索),防抖窗口期内重复请求会重置计时。<br />
<br />
```javascript<br />
// 输入框搜索防抖<br />
document.querySelector('#search-input').addEventListener('input', (e) => {<br />
const keyword = e.target.value.trim();<br />
if (!keyword) return;<br />
<br />
http.get('/search', {<br />
data: { keyword },<br />
debounceTime: 300 // 300ms 内输入停止后再请求<br />
}).promise.then(res => {<br />
console.log('搜索结果:', res.data);<br />
});<br />
});<br />
<br />
// 手动取消防抖<br />
// http.cancelDebounce('GET-/search-keyword=xxx');<br />
```<br />
<br />
### 3. 请求池化<br />
<br />
复用 `XMLHttpRequest` 实例,减少高频请求下的实例创建/销毁开销,默认开启。<br />
<br />
```javascript<br />
// 高频轮询场景(池化效果显著)<br />
setInterval(() => {<br />
http.get('/status', {<br />
usePool: true // 启用池化(默认开启)<br />
}).promise.then(res => {<br />
console.log('服务状态:', res.data);<br />
});<br />
}, 1000);<br />
```<br />
<br />
### 4. 重试机制<br />
<br />
针对网络错误、超时等场景自动重试,可配置重试次数和间隔。<br />
<br />
```javascript<br />
// 初始化时配置全局重试<br />
const http = new HttpRequest({ retryCount: 2, retryDelay: 1000 });<br />
<br />
// 单次请求覆盖全局配置<br />
http.get('/data', { retryCount: 3 }).promise.then(res => {<br />
console.log(res);<br />
});<br />
```<br />
<br />
## 四、配置项说明<br />
<br />
### 1. 构造函数配置项<br />
<br />
| 配置项 | 类型 | 默认值 | 说明 |<br />
|--------|------|--------|------|<br />
| `baseUrl` | String | `''` | 接口基础路径 |<br />
| `timeout` | Number | `10000` | 请求超时时间(ms) |<br />
| `headers` | Object | `{ 'Content-Type': 'application/json' }` | 全局请求头 |<br />
| `retryCount` | Number | `0` | 请求失败重试次数 |<br />
| `retryDelay` | Number | `1000` | 重试间隔时间(ms) |<br />
| `debug` | Boolean | `false` | 是否开启调试日志 |<br />
| `debounceTime` | Number | `0` | 全局防抖延迟(ms),0 为关闭 |<br />
| `serializeCacheMax` | Number | `100` | 参数序列化缓存最大数量 |<br />
| `poolMax` | Number | `5` | 请求池最大实例数 |<br />
| `poolIdleTimeout` | Number | `30000` | 池实例闲置超时销毁时间(ms) |<br />
<br />
### 2. `request` 方法参数<br />
<br />
| 参数 | 类型 | 默认值 | 说明 |<br />
|------|------|--------|------|<br />
| `method` | String | - | 请求方法(GET/POST/PUT/DELETE) |<br />
| `url` | String | - | 接口路径(拼接 baseUrl) |<br />
| `options.data` | Object/FormData | `{}` | 请求参数 |<br />
| `options.headers` | Object | `{}` | 单次请求头(优先级高于全局) |<br />
| `options.timeout` | Number | 全局 `timeout` | 单次请求超时时间 |<br />
| `options.retryCount` | Number | 全局 `retryCount` | 单次请求重试次数 |<br />
| `options.retryDelay` | Number | 全局 `retryDelay` | 单次请求重试间隔 |<br />
| `options.cancelRepeat` | Boolean | `true` | 是否取消重复请求 |<br />
| `options.responseType` | String | `''` | 响应类型(如 blob/json) |<br />
| `options.debounceTime` | Number | 全局 `debounceTime` | 单次请求防抖延迟 |<br />
| `options.onUploadProgress` | Function | - | 上传进度回调 |<br />
| `options.onDownloadProgress` | Function | - | 下载进度回调 |<br />
| `options.usePool` | Boolean | `true` | 是否启用请求池化 |<br />
<br />
## 五、方法说明<br />
<br />
| 方法 | 说明 | 返回值 |<br />
|------|------|--------|<br />
| `get(url, options)` | 发送 GET 请求 | `{ promise, cancel, cancelDebounce }` |<br />
| `post(url, options)` | 发送 POST 请求 | `{ promise, cancel, cancelDebounce }` |<br />
| `put(url, options)` | 发送 PUT 请求 | `{ promise, cancel, cancelDebounce }` |<br />
| `delete(url, options)` | 发送 DELETE 请求 | `{ promise, cancel, cancelDebounce }` |<br />
| `useRequestInterceptor(fn)` | 注册请求拦截器 | - |<br />
| `useResponseInterceptor(fn)` | 注册响应拦截器 | - |<br />
| `cancelDebounce(reqKey)` | 手动取消防抖 | - |<br />
<br />
## 六、注意事项<br />
<br />
1. 当请求参数为 `FormData` 时,需手动设置请求头为 `multipart/form-data`,且框架会自动跳过 `Content-Type` 的手动设置(由浏览器自动添加边界符)。<br />
2. 防抖功能仅对**相同请求标识**的请求生效,请求标识由 `method + url + 参数` 生成。<br />
3. 请求池化功能在高频请求场景下性能提升明显,低频请求可关闭以节省内存。<br />
4. 调试日志(`debug: true`)仅建议在开发环境开启,生产环境关闭以减少性能开销。<br />
<br />
<br />
```javascript<br />
class HttpRequest {<br />
static METHODS = Object.freeze({<br />
GET: 'GET',<br />
POST: 'POST',<br />
PUT: 'PUT',<br />
DELETE: 'DELETE'<br />
});<br />
<br />
constructor(baseConfig = {}) {<br />
this.config = Object.freeze({<br />
baseUrl: baseConfig.baseUrl || '',<br />
timeout: baseConfig.timeout || 10000,<br />
headers: Object.freeze(baseConfig.headers || { 'Content-Type': 'application/json' }),<br />
retryCount: baseConfig.retryCount || 0,<br />
retryDelay: baseConfig.retryDelay || 1000,<br />
debug: baseConfig.debug || false,<br />
debounceTime: baseConfig.debounceTime || 0,<br />
serializeCacheMax: baseConfig.serializeCacheMax || 100,<br />
// 池化配置<br />
poolMax: baseConfig.poolMax || 5, // 最大实例数<br />
poolIdleTimeout: baseConfig.poolIdleTimeout || 30000 // 实例最大闲置时间(ms)<br />
});<br />
<br />
this.requestInterceptors = [];<br />
this.responseInterceptors = [];<br />
this.requestCache = new WeakMap();<br />
this.debounceCache = new Map();<br />
this.serializeCache = new Map();<br />
// 请求池:key为请求方法,value为闲置实例队列<br />
this.xhrPool = new Map();<br />
// 初始化池队列<br />
Object.values(HttpRequest.METHODS).forEach(method => {<br />
this.xhrPool.set(method, []);<br />
});<br />
}<br />
<br />
/**<br />
* 从池内获取XHR实例<br />
* @param {String} method 请求方法<br />
* @returns {XMLHttpRequest}<br />
*/<br />
getXhrFromPool(method) {<br />
const queue = this.xhrPool.get(method) || [];<br />
while (queue.length > 0) {<br />
const xhr = queue.shift();<br />
// 清除闲置定时器<br />
clearTimeout(xhr.idleTimer);<br />
// 重置实例状态<br />
xhr.onload = null;<br />
xhr.onerror = null;<br />
xhr.ontimeout = null;<br />
xhr.onabort = null;<br />
if (xhr.upload) xhr.upload.onprogress = null;<br />
return xhr;<br />
}<br />
// 池内无闲置实例,且未达最大容量则创建新实例<br />
if (queue.length < this.config.poolMax) {<br />
return new XMLHttpRequest();<br />
}<br />
// 超过最大容量,等待50ms后重试(避免阻塞)<br />
return new Promise(resolve => {<br />
setTimeout(() => resolve(this.getXhrFromPool(method)), 50);<br />
});<br />
}<br />
<br />
/**<br />
* 将XHR实例放回池内<br />
* @param {String} method 请求方法<br />
* @param {XMLHttpRequest} xhr 实例<br />
*/<br />
putXhrToPool(method, xhr) {<br />
const queue = this.xhrPool.get(method) || [];<br />
if (queue.length >= this.config.poolMax) {<br />
// 超过最大容量,直接销毁<br />
return;<br />
}<br />
// 设置闲置超时定时器<br />
xhr.idleTimer = setTimeout(() => {<br />
const index = queue.indexOf(xhr);<br />
index > -1 && queue.splice(index, 1);<br />
}, this.config.poolIdleTimeout);<br />
queue.push(xhr);<br />
this.xhrPool.set(method, queue);<br />
}<br />
<br />
static serializeParams(data, maxCache = 100, cache = new Map()) {<br />
if (typeof data !== 'object' || data === null) return '';<br />
const cacheKey = JSON.stringify(data);<br />
if (cache.has(cacheKey)) {<br />
const value = cache.get(cacheKey);<br />
cache.delete(cacheKey);<br />
cache.set(cacheKey, value);<br />
return value;<br />
}<br />
<br />
let paramsArr = [];<br />
const keys = Object.keys(data);<br />
for (let i = 0, len = keys.length; i < len; i++) {<br />
const key = keys[i];<br />
const value = data[key];<br />
if (value === undefined || value === null || value === '') continue;<br />
paramsArr.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);<br />
}<br />
const paramsStr = paramsArr.join('&');<br />
<br />
if (cache.size >= maxCache) {<br />
const firstKey = cache.keys().next().value;<br />
cache.delete(firstKey);<br />
}<br />
cache.set(cacheKey, paramsStr);<br />
return paramsStr;<br />
}<br />
<br />
generateReqKey(method, url, data) {<br />
return [<br />
method,<br />
url,<br />
HttpRequest.serializeParams(data, this.config.serializeCacheMax, this.serializeCache)<br />
].join('-');<br />
}<br />
<br />
useRequestInterceptor(interceptor) {<br />
typeof interceptor === 'function' && this.requestInterceptors.push(interceptor);<br />
}<br />
<br />
useResponseInterceptor(interceptor) {<br />
typeof interceptor === 'function' && this.responseInterceptors.push(interceptor);<br />
}<br />
<br />
cancelDebounce(reqKey) {<br />
if (this.debounceCache.has(reqKey)) {<br />
clearTimeout(this.debounceCache.get(reqKey));<br />
this.debounceCache.delete(reqKey);<br />
this.config.debug && console.log(`[Debounce Canceled] ${reqKey}`);<br />
}<br />
}<br />
<br />
request(method, url, options = {}) {<br />
const config = this.config;<br />
const {<br />
data = {},<br />
headers = {},<br />
timeout = config.timeout,<br />
retryCount = config.retryCount,<br />
retryDelay = config.retryDelay,<br />
cancelRepeat = true,<br />
responseType = '',<br />
debounceTime = config.debounceTime,<br />
onUploadProgress,<br />
onDownloadProgress,<br />
// 新增:是否启用池化<br />
usePool = true<br />
} = options;<br />
<br />
let requestConfig = { method, url, data, headers, timeout, responseType };<br />
const reqInterceptorLen = this.requestInterceptors.length;<br />
for (let i = 0; i < reqInterceptorLen; i++) {<br />
requestConfig = this.requestInterceptors[i](requestConfig) || requestConfig;<br />
}<br />
const { method: finalMethod, url: finalUrl, data: finalData, headers: finalHeaders, responseType: finalResponseType } = requestConfig;<br />
<br />
const methodUpper = HttpRequest.METHODS[finalMethod.toUpperCase()] || finalMethod.toUpperCase();<br />
const reqKey = this.generateReqKey(methodUpper, finalUrl, finalData);<br />
<br />
let cancel;<br />
let controller;<br />
let promise;<br />
<br />
const handleDebounce = () => {<br />
this.cancelDebounce(reqKey);<br />
if (debounceTime <= 0) return null;<br />
return new Promise((resolve, reject) => {<br />
const timer = setTimeout(() => {<br />
this.debounceCache.delete(reqKey);<br />
sendRealRequest().then(resolve).catch(reject);<br />
}, debounceTime);<br />
this.debounceCache.set(reqKey, timer);<br />
config.debug && console.log(`[Debounce Start] ${reqKey} (delay: ${debounceTime}ms)`);<br />
});<br />
};<br />
<br />
const sendRealRequest = async () => {<br />
if (cancelRepeat) {<br />
this.requestCache.has(reqKey) && this.requestCache.get(reqKey).abort();<br />
controller = new AbortController();<br />
cancel = () => {<br />
controller.abort();<br />
this.requestCache.delete(reqKey);<br />
};<br />
this.requestCache.set(reqKey, controller);<br />
}<br />
<br />
const fullUrl = config.baseUrl + finalUrl;<br />
const isGet = methodUpper === HttpRequest.METHODS.GET;<br />
const paramsStr = HttpRequest.serializeParams(finalData, config.serializeCacheMax, this.serializeCache);<br />
const requestUrl = isGet && paramsStr ? `${fullUrl}?${paramsStr}` : fullUrl;<br />
const allHeaders = Object.assign({}, config.headers, finalHeaders);<br />
const startTime = config.debug ? Date.now() : 0;<br />
<br />
const hasUploadProgress = typeof onUploadProgress === 'function';<br />
const hasDownloadProgress = typeof onDownloadProgress === 'function';<br />
<br />
const sendRequest = async (count) => {<br />
// 获取XHR实例<br />
let xhr = usePool ? await this.getXhrFromPool(methodUpper) : new XMLHttpRequest();<br />
return new Promise((resolve, reject) => {<br />
xhr.open(methodUpper, requestUrl, true);<br />
xhr.timeout = timeout;<br />
xhr.responseType = finalResponseType;<br />
controller && (xhr.signal = controller.signal);<br />
<br />
const headerKeys = Object.keys(allHeaders);<br />
const headerLen = headerKeys.length;<br />
for (let i = 0; i < headerLen; i++) {<br />
const key = headerKeys[i];<br />
if (!(finalData instanceof FormData) || key !== 'Content-Type') {<br />
xhr.setRequestHeader(key, allHeaders[key]);<br />
}<br />
}<br />
<br />
xhr.onload = () => {<br />
cancelRepeat && this.requestCache.delete(reqKey);<br />
// 实例放回池内<br />
usePool && this.putXhrToPool(methodUpper, xhr);<br />
<br />
if (config.debug) {<br />
console.log(`[Request End] ${reqKey}`, {<br />
status: xhr.status,<br />
duration: `${Date.now() - startTime}ms`<br />
});<br />
}<br />
<br />
if (xhr.status >= 200 && xhr.status < 300) {<br />
let response = { data: xhr.response, status: xhr.status, statusText: xhr.statusText };<br />
const resInterceptorLen = this.responseInterceptors.length;<br />
for (let i = 0; i < resInterceptorLen; i++) {<br />
response = this.responseInterceptors[i](response) || response;<br />
}<br />
resolve(response);<br />
} else {<br />
reject(new Error(`Request failed with status ${xhr.status}`));<br />
}<br />
};<br />
<br />
const handleError = (errMsg) => {<br />
cancelRepeat && this.requestCache.delete(reqKey);<br />
// 实例放回池内<br />
usePool && this.putXhrToPool(methodUpper, xhr);<br />
reject(new Error(errMsg));<br />
};<br />
xhr.onerror = () => handleError('Network request failed');<br />
xhr.ontimeout = () => handleError(`Request timeout (${timeout}ms)`);<br />
xhr.onabort = () => handleError('Request aborted');<br />
<br />
hasUploadProgress && xhr.upload && (xhr.upload.onprogress = onUploadProgress);<br />
hasDownloadProgress && (xhr.onprogress = onDownloadProgress);<br />
<br />
let sendData = finalData;<br />
if (!isGet && !(sendData instanceof FormData)) {<br />
sendData = allHeaders['Content-Type'] === 'application/json' ? JSON.stringify(sendData) : paramsStr;<br />
}<br />
xhr.send(sendData);<br />
}).catch(err => {<br />
if (count > 0) {<br />
config.debug && console.log(`[Request Retry] ${reqKey} (${count} times left)`);<br />
return new Promise(resolve => setTimeout(resolve, retryDelay)).then(() => sendRequest(count - 1));<br />
}<br />
return Promise.reject(err);<br />
});<br />
};<br />
<br />
return sendRequest(retryCount);<br />
};<br />
<br />
if (debounceTime > 0) {<br />
promise = handleDebounce();<br />
} else {<br />
promise = sendRealRequest();<br />
}<br />
<br />
return {<br />
promise,<br />
cancel: () => {<br />
this.cancelDebounce(reqKey);<br />
cancel && cancel();<br />
},<br />
cancelDebounce: () => this.cancelDebounce(reqKey)<br />
};<br />
}<br />
<br />
get(url, options) {<br />
return this.request(HttpRequest.METHODS.GET, url, options);<br />
}<br />
<br />
post(url, options) {<br />
return this.request(HttpRequest.METHODS.POST, url, options);<br />
}<br />
<br />
put(url, options) {<br />
return this.request(HttpRequest.METHODS.PUT, url, options);<br />
}<br />
<br />
delete(url, options) {<br />
return this.request(HttpRequest.METHODS.DELETE, url, options);<br />
}<br />
}<br />
<br />#免责声明#
本文为转载 或 原创内容,未经授权禁止转载、摘编、复制及镜像使用、转载请注明作者、出处及原文链接、违者将依法追究责任。

3个月前 
4个月前 提交表单随机背景颜色并且播放烟花特效 ```html 提交触发...

3个月前 ```html 手机端侧边栏(无遮挡) * {margin: 0; padding: 0; box-sizing: bord...

1年前 赚钱的方式多种多样,关键在于结合自身资源、技能和兴趣,选择适合自...

2个月前 > 功能介绍 该工具是一款零依赖、极致性能的 PHP 版网站 favicon(网站图标...

5个月前 好的,没问题!我已经帮你把整个插件系统整合好了,这是一个可以直接运...