xn-fe-tools 1.6.0 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("vue"),t=e=>!("object"!=typeof e||null===e||Array.isArray(e)||e instanceof Date||e instanceof RegExp||e instanceof File||e instanceof Blob||"undefined"!=typeof FormData&&e instanceof FormData),r=(e,n)=>{const o={...e};for(const s in n){if(!Object.prototype.hasOwnProperty.call(n,s))continue;const e=n[s];if(void 0===e)continue;const a=o[s];t(e)&&t(a)?o[s]=r(a,e):o[s]=e}return o},n=e=>e>=200&&e<300,o=()=>!0,s=e=>new Promise(t=>setTimeout(t,e)),a=async e=>{if(!(e=>{if(!e)return!0;if(204===e.status||205===e.status||304===e.status)return!0;const t=e.headers.get("content-length");return!(!t||0!==Number(t))})(e))try{return await e.json()}catch(t){let n;try{n=await e.text()}catch{return}if(!n.trim())return;try{return JSON.parse(n)}catch(r){return}}},i=(e,t,r,n,o)=>({message:e,type:t,status:r,response:n,error:o}),u=e=>{if(e instanceof Error){const t=new Error(e.message);return e.name&&(t.name=e.name),e.stack&&(t.stack=e.stack),t}return new Error(String(e))},c=async(e,t,r)=>!!e?.enabled&&(!(t>(e.maxRetryCount??3))&&(e.shouldRetry?await e.shouldRetry(t,r):"network"===r.type||"timeout"===r.type)),l=(e,t)=>{const r=Object.create(e);let n=!1,o=!1,s=!1;const a=e.then.bind(e),i=e.catch.bind(e),u=a(e=>{s=!0===e.__errorSuppressed,delete e.__errorSuppressed;const t=new Proxy(e,{get(e,t){if(("1"===t||"number"==typeof t&&1===t)&&(o=!0),t===Symbol.iterator){const t=e[Symbol.iterator]();return function*(){let e=0;for(const r of t)1===e&&(o=!0),yield r,e++}}return e[t]}}),r=e[1];return e[2]||!r||s||n||queueMicrotask(()=>{queueMicrotask(()=>{if(!(o||o||n||s)){const e=new Error(`Unhandled fetch error: ${r.message} (type: ${r.type})`);throw e.fetchError=r,e}})}),t});r.cancel=()=>{t&&t()};const c=u.then.bind(u);return r.then=function(e,r){return e&&"function"==typeof e&&!r&&e.length<=2?l(c(t=>{const r=t[2],n=t[0],o=t[3];return r&&void 0!==n&&e(n,o),t}),t):c(e,r)},r.catchHttp=function(e){n=!0;const r=c(t=>{const r=t[2],n=t[1];return!r&&n&&"http"===n.type&&e(n),!r&&n&&(t.__errorSuppressed=!0),t});return l(r,t)},r.catchBusiness=function(e){n=!0;const r=c(t=>{const r=t[1],n=t[2];return!n&&r&&"business"===r.type&&e(r),!n&&r&&(t.__errorSuppressed=!0),t});return l(r,t)},r.catchNetwork=function(e){if(n=!0,e&&"function"==typeof e){const r=c(t=>{const r=t[1],n=t[2];return!n&&r&&e(r),!n&&r&&(t.__errorSuppressed=!0),t});return l(r,t)}return i(e)},r};class d{globalConfig={};constructor(e){e&&(this.globalConfig={...e}),this.request=this.request.bind(this),this.sendGet=this.sendGet.bind(this),this.sendPost=this.sendPost.bind(this),this.sendPostForm=this.sendPostForm.bind(this),this.sendPostBlob=this.sendPostBlob.bind(this),this.sendGetBlob=this.sendGetBlob.bind(this),this.setGlobalConfig=this.setGlobalConfig.bind(this)}async executeRequest(e,r,l=1){try{const y=e,w=y.signal;w&&(w.aborted?r.abort():w.addEventListener("abort",()=>r.abort(),{once:!0}));const b=(d=y.headers,f=y.url,p=y.method,d?"function"==typeof d?d({url:f,method:p}):d:{}),{body:v,urlParams:S}=((e,r,n)=>{if(!e)return{};if("GET"===r||"HEAD"===r||"OPTIONS"===r){if(t(e)){const t=new URLSearchParams;for(const r in e)if(Object.prototype.hasOwnProperty.call(e,r)){const n=e[r];null!=n&&t.append(r,String(n))}return{urlParams:t.toString()}}return{}}if("form"===n&&e instanceof FormData)return{body:e};if("form"===n&&t(e)){const t=new FormData;for(const r in e)if(Object.prototype.hasOwnProperty.call(e,r)){const n=e[r];null!=n&&(n instanceof File||n instanceof Blob?t.append(r,n):t.append(r,String(n)))}return{body:t}}return"json"===n?{body:JSON.stringify(e)}:"text"===n?{body:String(e)}:"blob"===n&&e instanceof Blob||"arraybuffer"===n&&e instanceof ArrayBuffer?{body:e}:{body:String(e)}})(y.data,y.method||"GET",y.requestType||"json"),x=((e,t,r)=>{if(t.startsWith("http://")||t.startsWith("https://")){if(r){const e=t.includes("?")?"&":"?";return`${t}${e}${r}`}return t}let n=t;e&&(n=`${e.endsWith("/")?e.slice(0,-1):e}${t.startsWith("/")?t:`/${t}`}`);if(r){const e=n.includes("?")?"&":"?";n=`${n}${e}${r}`}return n})(y.baseURL,y.url,S),E=y.timeout;let q,P=!1,T=b;v&&"string"==typeof v&&"json"===y.requestType?T={"Content-Type":"application/json",...b}:v instanceof FormData&&(T=b);const R=new AbortController,B=()=>R.abort();r.signal.addEventListener("abort",B);const $={method:y.method||"GET",headers:T,body:v,mode:"cors",credentials:!1===y.withCredentials?"same-origin":"include",signal:R.signal},C=fetch(x,$);let G;try{if(E){const e=new Promise((e,t)=>{q=setTimeout(()=>{P=!0,R.abort(),t(i("Request timeout","timeout"))},E)});G=await Promise.race([C,e])}else G=await C}catch(g){if(q&&clearTimeout(q),P){const e=i("Request timeout","timeout");if(await c(y.retry,l,e))return r.signal.removeEventListener("abort",B),await s(y.retry?.delay||1e3),this.executeRequest(y,r,l+1);let t=!1;if(y.onNetworkError){const r=()=>{t=!0};await y.onNetworkError(u(new Error(e.message)),r)}const n=[void 0,e,!1,void 0];return n.__errorSuppressed=t,r.signal.removeEventListener("abort",B),n}throw g}finally{q&&clearTimeout(q),r.signal.removeEventListener("abort",B)}const _=y.responseType||"json";let D;try{D=await(async(e,t)=>{if(!e)throw new Error("No response received");switch(t){case"json":default:return await a(e);case"text":return await e.text();case"blob":return await e.blob();case"arraybuffer":return await e.arrayBuffer();case"formData":return await e.formData()}})(G,_)}catch(h){const e=i("Failed to parse response","network",G.status,void 0,h instanceof Error?h:new Error(String(h)));if(await c(y.retry,l,e))return await s(y.retry?.delay||1e3),this.executeRequest(y,r,l+1);let t=!1;if(y.onNetworkError){const r=()=>{t=!0};await y.onNetworkError(u(e.error||new Error(e.message)),r)}const n=[void 0,e,!1,void 0];return n.__errorSuppressed=t,n}if(!(y.validateStatus||n)(G.status)){const e=i(`HTTP Error: ${G.status} ${G.statusText}`,"http",G.status,D);if(await c(y.retry,l,e))return await s(y.retry?.delay||1e3),this.executeRequest(y,r,l+1);let t=!1;if(y.onHttpError){const r=()=>{t=!0},n=new Error(e.message);await y.onHttpError(n,G.status,D,r)}const n=[void 0,e,!1,{data:D,status:G.status,statusText:G.statusText,headers:G.headers,response:G}];return n.__errorSuppressed=t,n}if(!(y.validateResponse||o)(D)){const e=i("Business validation failed","business",G.status,D);if(await c(y.retry,l,e))return await s(y.retry?.delay||1e3),this.executeRequest(y,r,l+1);let t=!1;if(y.onBusinessError){const r=()=>{t=!0},n=new Error(e.message);await y.onBusinessError(n,D,r)}const n=[void 0,e,!1,{data:D,status:G.status,statusText:G.statusText,headers:G.headers,response:G}];return n.__errorSuppressed=t,n}let F=D;if(y.transformResponse)try{F=y.transformResponse(D)}catch(m){console.warn("[toAwaitFetch] transformResponse error:",m)}return[F,void 0,!0,{data:D,status:G.status,statusText:G.statusText,headers:G.headers,response:G}]}catch(g){const t=(e=>e instanceof DOMException&&"AbortError"===e.name)(g),n=i(t?"Request aborted":g instanceof Error?g.message:"Network error","network",void 0,void 0,g instanceof Error?g:new Error(String(g)));if(!t&&await c(e.retry,l,n))return await s(e.retry?.delay||1e3),this.executeRequest(e,r,l+1);let o=!1;if(e.onNetworkError){const t=()=>{o=!0};await e.onNetworkError(u(n.error||new Error(n.message)),t)}const a=[void 0,n,!1,void 0];return a.__errorSuppressed=o,a}var d,f,p}request(e){const t=r({...this.globalConfig},e),n=new AbortController,o=this.executeRequest(t,n);return l(o,()=>n.abort())}setGlobalConfig(e){this.globalConfig={...e}}getGlobalConfig(){return{...this.globalConfig}}mergeGlobalConfig(e){this.globalConfig=r(this.globalConfig,e)}sendGet(e,t,r){return this.request({...r,url:e,method:"GET",data:t})}sendPost(e,t,r){return this.request({...r,url:e,method:"POST",data:t,requestType:"json"})}sendPostForm(e,t,r){return this.request({...r,url:e,method:"POST",data:t,requestType:"form"})}sendPostBlob(e,t,r){return this.request({...r,url:e,method:"POST",data:t,requestType:"json",responseType:"blob"})}sendGetBlob(e,t,r){return this.request({...r,url:e,method:"GET",data:t,responseType:"blob"})}}const f=e=>new d(e),p=new d,g=e=>{e?.cancel?.()},h=Object.assign(e=>e&&"object"==typeof e&&"url"in e?p.request(e):f(e),{create:f,sendGet:p.sendGet.bind(p),sendPost:p.sendPost.bind(p),sendPostForm:p.sendPostForm.bind(p),sendPostBlob:p.sendPostBlob.bind(p),sendGetBlob:p.sendGetBlob.bind(p),setGlobalConfig:p.setGlobalConfig.bind(p),cancel:g});function m(e,t){if(!e||!t)return;const r=t.replace(/\[(\d+)\]/g,".$1").split(".").filter(Boolean);let n=e;for(const o of r){if(null==n)return;n=n[o]}return n}function y(t){return"function"==typeof t?t():e.unref(t)}function w(t,r){"undefined"!=typeof __DEV__&&__DEV__&&void 0!==t&&!function(t){return"function"==typeof t||e.isRef(t)}(t)&&console.warn(`[${r}] 检测到 params 是普通对象,参数变化将不会触发请求更新。\n请使用 computed() 或 getter 函数包装:`)}const b={};const v=new Map,S=new Map,x=new Map,E=new Set;let q=null;const P=(e,t)=>new Promise((r,n)=>{if(q)return r(q);const o=indexedDB.open(e,1);o.onupgradeneeded=e=>{const r=e.target.result;r.objectStoreNames.contains(t)||r.createObjectStore(t)},o.onsuccess=e=>{q=e.target.result,r(q)},o.onerror=e=>{n(e.target.error)}});const T="1.6.0";exports.VERSION=T,exports.cancel=g,exports.createInstance=f,exports.getTableRequestGlobalConfig=function(){return{...b,pagination:{...b.pagination}}},exports.getVersion=function(){return T},exports.sendGet=(e,t,r)=>p.request({...r,url:e,method:"GET",data:t}),exports.sendGetBlob=(e,t,r)=>p.sendGetBlob(e,t,r),exports.sendPost=(e,t,r)=>p.sendPost(e,t,r),exports.sendPostBlob=(e,t,r)=>p.sendPostBlob(e,t,r),exports.sendPostForm=(e,t,r)=>p.sendPostForm(e,t,r),exports.setGlobalConfig=e=>{p.setGlobalConfig(e)},exports.setTableRequestGlobalConfig=function(e){Object.assign(b,e),e.pagination&&(b.pagination={...b.pagination,...e.pagination})},exports.toAwaitFetch=h,exports.useInputNumber=function(t,r){let n,o;null===t||"object"!=typeof t||e.isRef(t)?(n=t,o=r||{}):(o=t,n="");const s=()=>e.toValue(o.precision)??0,a=()=>e.toValue(o.showThousands)??!1,i=e.ref(e.toValue(n)?.toString()??""),u=e.computed(()=>{let e=i.value;if(a()){const[t,r]=e.split(".");let n=t.replace(/\B(?=(\d{3})+(?!\d))/g,",");return r?n+=`.${r}`:e.includes(".")&&(n+="."),n}return e}),c=t=>{let r=null==t?"":t.toString().trim();const n=e.toValue(o.allowNegative)?/[^\d.-]/g:/[^\d.]/g;r=r.replace(n,""),r=r.replace(/^-/,"$#$").replace(/-/g,"").replace("$#$","-"),r.length>1&&r.startsWith("0")&&"."!==r[1]&&(r=r.replace(/^0+/,""),""===r&&(r="0")),r.startsWith("-0")&&r.length>2&&"."!==r[2]&&(r="-"+r.slice(2).replace(/^0+/,""),"-"===r&&(r="-0"));const a=s();if(a>0){r=r.replace(/^\./g,"").replace(/\./g,"$#$").replace(/\./g,"").replace("$#$",".");const t=r.indexOf(".");if(-1!==t){if(r.slice(t+1).length>a){const n=e.toValue(o.decimalLimitMode)??"discard";if("discard"===n)r=r.slice(0,t+a+1);else{const e=Math.pow(10,a);let o=Number(r);isNaN(o)?r=r.slice(0,t+a+1):("round"===n?o=Math.round(o*e)/e:"ceil"===n&&(o=Math.ceil(o*e)/e),r=o.toString())}}}}else r=r.replace(/\./g,"");return r};return e.watch(()=>e.toValue(n),e=>{const t=null==e?"":e.toString();t!==i.value&&(i.value=t)}),{value:i,displayValue:u,handleInput:e=>{const t=c(e);i.value=t},handleBlur:()=>{if(!i.value)return;let t=Number(i.value);const r=o.max??1/0,n=o.min??-1/0;t>r&&(t=r,i.value=r.toString()),t<n&&(t=n,i.value=n.toString());const a=e.toValue(o.autoFixed)??!1;i.value=!0===a||"pad"===a?t.toFixed(s()):"trim"===a?Number(t.toFixed(s())).toString():t.toString()},formatter:e=>u.value,parser:e=>a()?e.replace(/,/g,""):e,clean:c}},exports.useRequestCache=function(t){const{requestFn:r,cacheKey:n,cacheTime:o=3e5,storage:s="memory",params:a,transformResult:i,immediate:u=!0,storageKeyPrefix:c="req_cache_",forceRefreshOnFirst:l=!1,dbName:d="RequestCacheDB",storeName:f="request_cache",onSuccess:p,onError:g}=t,h=(e=>{if("undefined"==typeof window)return"memory";try{if("localStorage"===e&&!window.localStorage)throw new Error;if("sessionStorage"===e&&!window.sessionStorage)throw new Error;if("indexedDB"===e&&!window.indexedDB)throw new Error;return e}catch(t){return console.warn(`[useRequestCache] ${e} is not supported in this environment, falling back to 'memory' mode.`),"memory"}})(y(s)),m=e.ref(),w=e.ref(!1),b=e.ref(null),q=()=>{const e=a?y(a):{};if("function"==typeof n)try{return n(e)}catch(t){return console.warn("[useRequestCache] Generate key failed",t),""}return n},T=async e=>{if("indexedDB"===h){try{const t=await P(d,f);return new Promise((r,n)=>{const o=t.transaction([f],"readwrite").objectStore(f).delete(`${c}${e}`);o.onsuccess=()=>r(),o.onerror=()=>n(o.error)})}catch(r){console.warn("[useRequestCache] IndexedDB delete failed",r)}return}const t="localStorage"===h?localStorage:"sessionStorage"===h?sessionStorage:null;t&&t.removeItem(`${c}${e}`)},R=async(e=!1)=>{const t=q();if(!t)return void console.warn("[useRequestCache] Cache key is empty, skipping request");const n=y(l)&&!E.has(t),s=e||n,u=a?y(a):{};if(!s&&"indexedDB"!==h){const e=v.get(t);if(e){if(Date.now()-e.timestamp<o)return m.value=i?i(e.data):e.data,p&&p(m.value),m.value;v.delete(t)}}w.value=!0,b.value=null;try{if(S.has(t)){if(p||g){const e=x.get(t);e&&(p&&e.onSuccess.push(p),g&&e.onError.push(g))}const e=await S.get(t);return m.value=i?i(e):e,m.value}if(!s&&"memory"!==h){const e=await(async e=>{if("indexedDB"===h)try{const t=await P(d,f);return new Promise(r=>{const n=t.transaction([f],"readonly").objectStore(f).get(`${c}${e}`);n.onsuccess=()=>r(n.result||null),n.onerror=()=>r(null)})}catch(r){return console.warn("[useRequestCache] IndexedDB read failed",r),null}const t="localStorage"===h?localStorage:"sessionStorage"===h?sessionStorage:null;if(!t)return null;try{const r=t.getItem(`${c}${e}`);if(r)return JSON.parse(r)}catch(r){console.warn("[useRequestCache] Read storage failed",r)}return null})(t);if(e){if(Date.now()-e.timestamp<o)return"indexedDB"!==h&&v.set(t,e),m.value=i?i(e.data):e.data,p&&p(m.value),m.value;await T(t)}}const e=r(u);S.set(t,e),x.set(t,{onSuccess:p?[p]:[],onError:g?[g]:[]});try{const r=await e,o={data:r,timestamp:Date.now()};"indexedDB"!==h&&v.set(t,o),"memory"!==h&&await(async(e,t)=>{if("indexedDB"===h){try{const r=await P(d,f);return new Promise((n,o)=>{const s=r.transaction([f],"readwrite").objectStore(f).put(t,`${c}${e}`);s.onsuccess=()=>n(),s.onerror=()=>o(s.error)})}catch(n){console.warn("[useRequestCache] IndexedDB write failed",n)}return}const r="localStorage"===h?localStorage:"sessionStorage"===h?sessionStorage:null;if(r)try{r.setItem(`${c}${e}`,JSON.stringify(t))}catch(n){console.warn("[useRequestCache] Write storage failed",n)}})(t,o),n&&E.add(t),m.value=i?i(r):r;const s=x.get(t);if(s&&s.onSuccess){[...s.onSuccess].forEach(e=>e(m.value))}return S.delete(t),x.delete(t),m.value}catch(R){b.value=R;const e=x.get(t);if(e&&e.onError){[...e.onError].forEach(e=>e(R))}throw S.delete(t),x.delete(t),R}}catch(R){throw b.value=R,R}finally{w.value=!1}},B=()=>R(!1);return a?e.watch(()=>y(a),()=>{B()},{deep:!0,immediate:u}):u&&B(),{data:m,loading:w,error:b,run:B,refresh:()=>R(!0),clearCache:async e=>{const t=e||q();t&&(v.delete(t),"memory"!==h&&await T(t))}}},exports.useTableRequest=function(t,r={}){const n={...b.pagination,...r.pagination},{params:o,immediate:s=b.immediate??!0,debounceDelay:a=b.debounceDelay??0,dataField:i=b.dataField??"",totalField:u=b.totalField??"",currentPageField:c=b.currentPageField??"",transformParams:l,transformResult:d,onSuccess:f,onError:p,incremental:g=b.incremental??!1}=r,{defaultPage:h=1,defaultPageSize:v=10,pageKey:S="page",pageSizeKey:x="pageSize"}=n,E=e.ref([]),q=e.ref(!1),P=e.ref(null),T=e.reactive({page:h,pageSize:v,total:0});let R=0;async function B(){const e=++R;q.value=!0,P.value=null;try{const r=o?y(o):{},n={[S]:T.page,[x]:T.pageSize};let s;s=l?l(r,{page:T.page,pageSize:T.pageSize}):{...r,...n};const a=await t(s);if(e!==R)return;if(d){const e=d(a);if(e&&"object"==typeof e&&Array.isArray(e.list)){const t=e.list;g&&T.page>h?E.value=[...E.value,...t]:E.value=t,T.total="number"==typeof e.total?e.total:e.list.length}else E.value=[],T.total=0,console.warn("[useTableRequest] transformResult 返回格式无法识别,请返回 { list: T[], total: number } 格式。")}else{const e=a;if(Array.isArray(e))g&&T.page>h?E.value=[...E.value,...e]:E.value=e,T.total=e.length;else if(e&&"object"==typeof e){if(i&&""!==i.trim()){const t=m(e,i)||[];g&&T.page>h?E.value=[...E.value,...t]:E.value=t}else E.value=[];u&&""!==u.trim()?T.total=m(e,u)??0:T.total=0}else E.value=[],T.total=0}if(c&&""!==c.trim()){const e=m(a,c);"number"==typeof e&&e!==T.page&&(console.warn(`[useTableRequest] 后端返回页码(${e})与客户端页码(${T.page})不一致,已自动修正`),T.page=e)}f?.(E.value,a)}catch(r){if(e!==R)return;P.value=r instanceof Error?r:new Error(String(r)),p?.(P.value)}finally{e===R&&(q.value=!1)}}const $=a>0?function(e,t){let r=null;return(...n)=>{r&&clearTimeout(r),r=setTimeout(()=>{e(...n),r=null},t)}}(B,a):B;let C=!1;async function G(){await B()}return e.watch(()=>[T.page,T.pageSize],()=>{C||$()}),o&&(w(o,"useTableRequest"),e.watch(()=>y(o),()=>{C=!0,T.page=h,C=!1,$()},{deep:!0})),s&&B(),{data:E,loading:q,error:P,pagination:T,run:G,onPageChange:async function(e){const t=T.page!==e;T.page=e,t||await B()},onSizeChange:async function(e){const t=T.pageSize!==e,r=T.page!==h;T.pageSize=e,T.page=h,t||r||await B()},refresh:async function(){g?(C=!0,T.page=h,E.value=[],await e.nextTick(),C=!1,await B()):await G()}}};
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("vue"),t=e=>!("object"!=typeof e||null===e||Array.isArray(e)||e instanceof Date||e instanceof RegExp||e instanceof File||e instanceof Blob||"undefined"!=typeof FormData&&e instanceof FormData),r=(e,n)=>{const o={...e};for(const s in n){if(!Object.prototype.hasOwnProperty.call(n,s))continue;const e=n[s];if(void 0===e)continue;const a=o[s];t(e)&&t(a)?o[s]=r(a,e):o[s]=e}return o},n=e=>e>=200&&e<300,o=()=>!0,s=e=>new Promise(t=>setTimeout(t,e)),a=async e=>{if(!(e=>{if(!e)return!0;if(204===e.status||205===e.status||304===e.status)return!0;const t=e.headers.get("content-length");return!(!t||0!==Number(t))})(e))try{return await e.json()}catch(t){let n;try{n=await e.text()}catch{return}if(!n.trim())return;try{return JSON.parse(n)}catch(r){return}}},i=(e,t,r,n,o)=>({message:e,type:t,status:r,response:n,error:o}),u=e=>{if(e instanceof Error){const t=new Error(e.message);return e.name&&(t.name=e.name),e.stack&&(t.stack=e.stack),t}return new Error(String(e))},c=async(e,t,r)=>!!e?.enabled&&(!(t>(e.maxRetryCount??3))&&(e.shouldRetry?await e.shouldRetry(t,r):"network"===r.type||"timeout"===r.type)),l=(e,t)=>{const r=Object.create(e);let n=!1,o=!1,s=!1;const a=e.then.bind(e),i=e.catch.bind(e),u=a(e=>{s=!0===e.__errorSuppressed,delete e.__errorSuppressed;const t=new Proxy(e,{get(e,t){if(("1"===t||"number"==typeof t&&1===t)&&(o=!0),t===Symbol.iterator){const t=e[Symbol.iterator]();return function*(){let e=0;for(const r of t)1===e&&(o=!0),yield r,e++}}return e[t]}}),r=e[1];return e[2]||!r||s||n||queueMicrotask(()=>{queueMicrotask(()=>{if(!(o||o||n||s)){const e=new Error(`Unhandled fetch error: ${r.message} (type: ${r.type})`);throw e.fetchError=r,e}})}),t});r.cancel=()=>{t&&t()};const c=u.then.bind(u);return r.then=function(e,r){return e&&"function"==typeof e&&!r&&e.length<=2?l(c(t=>{const r=t[2],n=t[0],o=t[3];return r&&void 0!==n&&e(n,o),t}),t):c(e,r)},r.catchHttp=function(e){n=!0;const r=c(t=>{const r=t[2],n=t[1];return!r&&n&&"http"===n.type&&e(n),!r&&n&&(t.__errorSuppressed=!0),t});return l(r,t)},r.catchBusiness=function(e){n=!0;const r=c(t=>{const r=t[1],n=t[2];return!n&&r&&"business"===r.type&&e(r),!n&&r&&(t.__errorSuppressed=!0),t});return l(r,t)},r.catchNetwork=function(e){if(n=!0,e&&"function"==typeof e){const r=c(t=>{const r=t[1],n=t[2];return!n&&r&&e(r),!n&&r&&(t.__errorSuppressed=!0),t});return l(r,t)}return i(e)},r};class d{globalConfig={};constructor(e){e&&(this.globalConfig={...e}),this.request=this.request.bind(this),this.sendGet=this.sendGet.bind(this),this.sendPost=this.sendPost.bind(this),this.sendPostForm=this.sendPostForm.bind(this),this.sendPostBlob=this.sendPostBlob.bind(this),this.sendGetBlob=this.sendGetBlob.bind(this),this.setGlobalConfig=this.setGlobalConfig.bind(this)}async executeRequest(e,r,l=1){try{const y=e,w=y.signal;w&&(w.aborted?r.abort():w.addEventListener("abort",()=>r.abort(),{once:!0}));const b=(d=y.headers,f=y.url,p=y.method,d?"function"==typeof d?d({url:f,method:p}):d:{}),{body:v,urlParams:S}=((e,r,n)=>{if(!e)return{};if("GET"===r||"HEAD"===r||"OPTIONS"===r){if(t(e)){const t=new URLSearchParams;for(const r in e)if(Object.prototype.hasOwnProperty.call(e,r)){const n=e[r];null!=n&&t.append(r,String(n))}return{urlParams:t.toString()}}return{}}if("form"===n&&e instanceof FormData)return{body:e};if("form"===n&&t(e)){const t=new FormData;for(const r in e)if(Object.prototype.hasOwnProperty.call(e,r)){const n=e[r];null!=n&&(n instanceof File||n instanceof Blob?t.append(r,n):t.append(r,String(n)))}return{body:t}}return"json"===n?{body:JSON.stringify(e)}:"text"===n?{body:String(e)}:"blob"===n&&e instanceof Blob||"arraybuffer"===n&&e instanceof ArrayBuffer?{body:e}:{body:String(e)}})(y.data,y.method||"GET",y.requestType||"json"),x=((e,t,r)=>{if(t.startsWith("http://")||t.startsWith("https://")){if(r){const e=t.includes("?")?"&":"?";return`${t}${e}${r}`}return t}let n=t;e&&(n=`${e.endsWith("/")?e.slice(0,-1):e}${t.startsWith("/")?t:`/${t}`}`);if(r){const e=n.includes("?")?"&":"?";n=`${n}${e}${r}`}return n})(y.baseURL,y.url,S),E=y.timeout;let q,P=!1,T=b;v&&"string"==typeof v&&"json"===y.requestType?T={"Content-Type":"application/json",...b}:v instanceof FormData&&(T=b);const R=new AbortController,B=()=>R.abort();r.signal.addEventListener("abort",B);const $={method:y.method||"GET",headers:T,body:v,mode:"cors",credentials:!1===y.withCredentials?"same-origin":"include",signal:R.signal},C=fetch(x,$);let G;try{if(E){const e=new Promise((e,t)=>{q=setTimeout(()=>{P=!0,R.abort(),t(i("Request timeout","timeout"))},E)});G=await Promise.race([C,e])}else G=await C}catch(g){if(q&&clearTimeout(q),P){const e=i("Request timeout","timeout");if(await c(y.retry,l,e))return r.signal.removeEventListener("abort",B),await s(y.retry?.delay||1e3),this.executeRequest(y,r,l+1);let t=!1;if(y.onNetworkError){const r=()=>{t=!0};await y.onNetworkError(u(new Error(e.message)),r)}const n=[void 0,e,!1,void 0];return n.__errorSuppressed=t,r.signal.removeEventListener("abort",B),n}throw g}finally{q&&clearTimeout(q),r.signal.removeEventListener("abort",B)}const _=y.responseType||"json";let D;try{D=await(async(e,t)=>{if(!e)throw new Error("No response received");switch(t){case"json":default:return await a(e);case"text":return await e.text();case"blob":return await e.blob();case"arraybuffer":return await e.arrayBuffer();case"formData":return await e.formData()}})(G,_)}catch(h){const e=i("Failed to parse response","network",G.status,void 0,h instanceof Error?h:new Error(String(h)));if(await c(y.retry,l,e))return await s(y.retry?.delay||1e3),this.executeRequest(y,r,l+1);let t=!1;if(y.onNetworkError){const r=()=>{t=!0};await y.onNetworkError(u(e.error||new Error(e.message)),r)}const n=[void 0,e,!1,void 0];return n.__errorSuppressed=t,n}if(!(y.validateStatus||n)(G.status)){const e=i(`HTTP Error: ${G.status} ${G.statusText}`,"http",G.status,D);if(await c(y.retry,l,e))return await s(y.retry?.delay||1e3),this.executeRequest(y,r,l+1);let t=!1;if(y.onHttpError){const r=()=>{t=!0},n=new Error(e.message);await y.onHttpError(n,G.status,D,r)}const n=[void 0,e,!1,{data:D,status:G.status,statusText:G.statusText,headers:G.headers,response:G}];return n.__errorSuppressed=t,n}if(!(y.validateResponse||o)(D)){const e=i("Business validation failed","business",G.status,D);if(await c(y.retry,l,e))return await s(y.retry?.delay||1e3),this.executeRequest(y,r,l+1);let t=!1;if(y.onBusinessError){const r=()=>{t=!0},n=new Error(e.message);await y.onBusinessError(n,D,r)}const n=[void 0,e,!1,{data:D,status:G.status,statusText:G.statusText,headers:G.headers,response:G}];return n.__errorSuppressed=t,n}let F=D;if(y.transformResponse)try{F=y.transformResponse(D)}catch(m){console.warn("[toAwaitFetch] transformResponse error:",m)}return[F,void 0,!0,{data:D,status:G.status,statusText:G.statusText,headers:G.headers,response:G}]}catch(g){const t=(e=>e instanceof DOMException&&"AbortError"===e.name)(g),n=i(t?"Request aborted":g instanceof Error?g.message:"Network error","network",void 0,void 0,g instanceof Error?g:new Error(String(g)));if(!t&&await c(e.retry,l,n))return await s(e.retry?.delay||1e3),this.executeRequest(e,r,l+1);let o=!1;if(e.onNetworkError){const t=()=>{o=!0};await e.onNetworkError(u(n.error||new Error(n.message)),t)}const a=[void 0,n,!1,void 0];return a.__errorSuppressed=o,a}var d,f,p}request(e){const t=r({...this.globalConfig},e),n=new AbortController,o=this.executeRequest(t,n);return l(o,()=>n.abort())}setGlobalConfig(e){this.globalConfig={...e}}getGlobalConfig(){return{...this.globalConfig}}mergeGlobalConfig(e){this.globalConfig=r(this.globalConfig,e)}sendGet(e,t,r){return this.request({...r,url:e,method:"GET",data:t})}sendPost(e,t,r){return this.request({...r,url:e,method:"POST",data:t,requestType:"json"})}sendPostForm(e,t,r){return this.request({...r,url:e,method:"POST",data:t,requestType:"form"})}sendPostBlob(e,t,r){return this.request({...r,url:e,method:"POST",data:t,requestType:"json",responseType:"blob"})}sendGetBlob(e,t,r){return this.request({...r,url:e,method:"GET",data:t,responseType:"blob"})}}const f=e=>new d(e),p=new d,g=e=>{e?.cancel?.()},h=Object.assign(e=>e&&"object"==typeof e&&"url"in e?p.request(e):f(e),{create:f,sendGet:p.sendGet.bind(p),sendPost:p.sendPost.bind(p),sendPostForm:p.sendPostForm.bind(p),sendPostBlob:p.sendPostBlob.bind(p),sendGetBlob:p.sendGetBlob.bind(p),setGlobalConfig:p.setGlobalConfig.bind(p),cancel:g});function m(e,t){if(!e||!t)return;const r=t.replace(/\[(\d+)\]/g,".$1").split(".").filter(Boolean);let n=e;for(const o of r){if(null==n)return;n=n[o]}return n}function y(t){return"function"==typeof t?t():e.unref(t)}function w(t,r){"undefined"!=typeof __DEV__&&__DEV__&&void 0!==t&&!function(t){return"function"==typeof t||e.isRef(t)}(t)&&console.warn(`[${r}] 检测到 params 是普通对象,参数变化将不会触发请求更新。\n请使用 computed() 或 getter 函数包装:`)}const b={};const v=new Map,S=new Map,x=new Map,E=new Set;let q=null;const P=(e,t)=>new Promise((r,n)=>{if(q)return r(q);const o=indexedDB.open(e,1);o.onupgradeneeded=e=>{const r=e.target.result;r.objectStoreNames.contains(t)||r.createObjectStore(t)},o.onsuccess=e=>{q=e.target.result,r(q)},o.onerror=e=>{n(e.target.error)}});const T="1.6.2";exports.VERSION=T,exports.cancel=g,exports.createInstance=f,exports.getTableRequestGlobalConfig=function(){return{...b,pagination:{...b.pagination}}},exports.getVersion=function(){return T},exports.sendGet=(e,t,r)=>p.request({...r,url:e,method:"GET",data:t}),exports.sendGetBlob=(e,t,r)=>p.sendGetBlob(e,t,r),exports.sendPost=(e,t,r)=>p.sendPost(e,t,r),exports.sendPostBlob=(e,t,r)=>p.sendPostBlob(e,t,r),exports.sendPostForm=(e,t,r)=>p.sendPostForm(e,t,r),exports.setGlobalConfig=e=>{p.setGlobalConfig(e)},exports.setTableRequestGlobalConfig=function(e){Object.assign(b,e),e.pagination&&(b.pagination={...b.pagination,...e.pagination})},exports.toAwaitFetch=h,exports.useInputNumber=function(t,r){let n,o;null===t||"object"!=typeof t||e.isRef(t)?(n=t,o=r||{}):(o=t,n="");const s=()=>e.toValue(o.precision)??0,a=()=>e.toValue(o.showThousands)??!1,i=e.ref(e.toValue(n)?.toString()??""),u=e.computed(()=>{let e=i.value;if(a()){const[t,r]=e.split(".");let n=t.replace(/\B(?=(\d{3})+(?!\d))/g,",");return r?n+=`.${r}`:e.includes(".")&&(n+="."),n}return e}),c=t=>{let r=null==t?"":t.toString().trim();const n=e.toValue(o.allowNegative)?/[^\d.-]/g:/[^\d.]/g;r=r.replace(n,""),r=r.replace(/^-/,"$#$").replace(/-/g,"").replace("$#$","-"),r.length>1&&r.startsWith("0")&&"."!==r[1]&&(r=r.replace(/^0+/,""),""===r&&(r="0")),r.startsWith("-0")&&r.length>2&&"."!==r[2]&&(r="-"+r.slice(2).replace(/^0+/,""),"-"===r&&(r="-0"));const a=s();if(a>0){r=r.replace(/^\./g,"").replace(/\./g,"$#$").replace(/\./g,"").replace("$#$",".");const t=r.indexOf(".");if(-1!==t){if(r.slice(t+1).length>a){const n=e.toValue(o.decimalLimitMode)??"discard";if("discard"===n)r=r.slice(0,t+a+1);else{const e=Math.pow(10,a);let o=Number(r);isNaN(o)?r=r.slice(0,t+a+1):("round"===n?o=Math.round(o*e)/e:"ceil"===n&&(o=Math.ceil(o*e)/e),r=o.toString())}}}}else r=r.replace(/\./g,"");return r};return e.watch(()=>e.toValue(n),e=>{const t=null==e?"":e.toString();t!==i.value&&(i.value=t)}),{value:i,displayValue:u,handleInput:e=>{const t=c(e);i.value=t},handleBlur:()=>{if(!i.value)return;let t=Number(i.value);const r=o.max??1/0,n=o.min??-1/0;t>r&&(t=r,i.value=r.toString()),t<n&&(t=n,i.value=n.toString());const a=e.toValue(o.autoFixed)??!1;i.value=!0===a||"pad"===a?t.toFixed(s()):"trim"===a?Number(t.toFixed(s())).toString():t.toString()},formatter:e=>u.value,parser:e=>a()?e.replace(/,/g,""):e,clean:c}},exports.useRequestCache=function(t){const{requestFn:r,cacheKey:n,cacheTime:o=3e5,storage:s="memory",params:a,transformResult:i,immediate:u=!0,storageKeyPrefix:c="req_cache_",forceRefreshOnFirst:l=!1,dbName:d="RequestCacheDB",storeName:f="request_cache",onSuccess:p,onError:g}=t,h=(e=>{if("undefined"==typeof window)return"memory";try{if("localStorage"===e&&!window.localStorage)throw new Error;if("sessionStorage"===e&&!window.sessionStorage)throw new Error;if("indexedDB"===e&&!window.indexedDB)throw new Error;return e}catch(t){return console.warn(`[useRequestCache] ${e} is not supported in this environment, falling back to 'memory' mode.`),"memory"}})(y(s)),m=e.ref(),w=e.ref(!1),b=e.ref(null),q=()=>{const e=a?y(a):{};if("function"==typeof n)try{return n(e)}catch(t){return console.warn("[useRequestCache] Generate key failed",t),""}return n},T=async e=>{if("indexedDB"===h){try{const t=await P(d,f);return new Promise((r,n)=>{const o=t.transaction([f],"readwrite").objectStore(f).delete(`${c}${e}`);o.onsuccess=()=>r(),o.onerror=()=>n(o.error)})}catch(r){console.warn("[useRequestCache] IndexedDB delete failed",r)}return}const t="localStorage"===h?localStorage:"sessionStorage"===h?sessionStorage:null;t&&t.removeItem(`${c}${e}`)},R=async(e=!1)=>{const t=q();if(!t)return void console.warn("[useRequestCache] Cache key is empty, skipping request");const n=y(l)&&!E.has(t),s=e||n,u=a?y(a):{};if(!s&&"indexedDB"!==h){const e=v.get(t);if(e){if(Date.now()-e.timestamp<o)return m.value=i?i(e.data):e.data,p&&p(m.value),m.value;v.delete(t)}}w.value=!0,b.value=null;try{if(S.has(t)){if(p||g){const e=x.get(t);e&&(p&&e.onSuccess.push(p),g&&e.onError.push(g))}const e=await S.get(t);return m.value=i?i(e):e,m.value}if(!s&&"memory"!==h){const e=await(async e=>{if("indexedDB"===h)try{const t=await P(d,f);return new Promise(r=>{const n=t.transaction([f],"readonly").objectStore(f).get(`${c}${e}`);n.onsuccess=()=>r(n.result||null),n.onerror=()=>r(null)})}catch(r){return console.warn("[useRequestCache] IndexedDB read failed",r),null}const t="localStorage"===h?localStorage:"sessionStorage"===h?sessionStorage:null;if(!t)return null;try{const r=t.getItem(`${c}${e}`);if(r)return JSON.parse(r)}catch(r){console.warn("[useRequestCache] Read storage failed",r)}return null})(t);if(e){if(Date.now()-e.timestamp<o)return"indexedDB"!==h&&v.set(t,e),m.value=i?i(e.data):e.data,p&&p(m.value),m.value;await T(t)}}const e=r(u);S.set(t,e),x.set(t,{onSuccess:p?[p]:[],onError:g?[g]:[]});try{const r=await e,o={data:r,timestamp:Date.now()};"indexedDB"!==h&&v.set(t,o),"memory"!==h&&await(async(e,t)=>{if("indexedDB"===h){try{const r=await P(d,f);return new Promise((n,o)=>{const s=r.transaction([f],"readwrite").objectStore(f).put(t,`${c}${e}`);s.onsuccess=()=>n(),s.onerror=()=>o(s.error)})}catch(n){console.warn("[useRequestCache] IndexedDB write failed",n)}return}const r="localStorage"===h?localStorage:"sessionStorage"===h?sessionStorage:null;if(r)try{r.setItem(`${c}${e}`,JSON.stringify(t))}catch(n){console.warn("[useRequestCache] Write storage failed",n)}})(t,o),n&&E.add(t),m.value=i?i(r):r;const s=x.get(t);if(s&&s.onSuccess){[...s.onSuccess].forEach(e=>e(m.value))}return S.delete(t),x.delete(t),m.value}catch(R){b.value=R;const e=x.get(t);if(e&&e.onError){[...e.onError].forEach(e=>e(R))}throw S.delete(t),x.delete(t),R}}catch(R){throw b.value=R,R}finally{w.value=!1}},B=()=>R(!1);return a?e.watch(()=>y(a),()=>{B()},{deep:!0,immediate:u}):u&&B(),{data:m,loading:w,error:b,run:B,refresh:()=>R(!0),clearCache:async e=>{const t=e||q();t&&(v.delete(t),"memory"!==h&&await T(t))}}},exports.useTableRequest=function(t,r={}){const n={...b.pagination,...r.pagination},{params:o,immediate:s=b.immediate??!0,debounceDelay:a=b.debounceDelay??0,dataField:i=b.dataField??"",totalField:u=b.totalField??"",currentPageField:c=b.currentPageField??"",transformParams:l,transformResult:d,onSuccess:f,onError:p,incremental:g=b.incremental??!1}=r,{defaultPage:h=1,defaultPageSize:v=10,pageKey:S="page",pageSizeKey:x="pageSize"}=n,E=e.ref([]),q=e.ref(!1),P=e.ref(null),T=e.reactive({page:h,pageSize:v,total:0});let R=0;async function B(){const e=++R;q.value=!0,P.value=null;try{const r=o?y(o):{},n={[S]:T.page,[x]:T.pageSize};let s;s=l?l(r,{page:T.page,pageSize:T.pageSize}):{...r,...n};const a=await t(s);if(e!==R)return;if(d){const e=d(a);if(e&&"object"==typeof e&&Array.isArray(e.list)){const t=e.list;g&&T.page>h?E.value=[...E.value,...t]:E.value=t,T.total="number"==typeof e.total?e.total:e.list.length}else E.value=[],T.total=0,console.warn("[useTableRequest] transformResult 返回格式无法识别,请返回 { list: T[], total: number } 格式。")}else{const e=a;if(Array.isArray(e))g&&T.page>h?E.value=[...E.value,...e]:E.value=e,T.total=e.length;else if(e&&"object"==typeof e){if(i&&""!==i.trim()){const t=m(e,i)||[];g&&T.page>h?E.value=[...E.value,...t]:E.value=t}else E.value=[];u&&""!==u.trim()?T.total=m(e,u)??0:T.total=0}else E.value=[],T.total=0}if(c&&""!==c.trim()){const e=m(a,c);"number"==typeof e&&e!==T.page&&(console.warn(`[useTableRequest] 后端返回页码(${e})与客户端页码(${T.page})不一致,已自动修正`),T.page=e)}f?.(E.value,a)}catch(r){if(e!==R)return;P.value=r instanceof Error?r:new Error(String(r)),p?.(P.value)}finally{e===R&&(q.value=!1)}}const $=a>0?function(e,t){let r=null;return(...n)=>{r&&clearTimeout(r),r=setTimeout(()=>{e(...n),r=null},t)}}(B,a):B;let C=!1;async function G(){await B()}return e.watch(()=>[T.page,T.pageSize],()=>{C||$()}),o&&(w(o,"useTableRequest"),e.watch(()=>y(o),()=>{C=!0,T.page=h,C=!1,$()},{deep:!0})),s&&B(),{data:E,loading:q,error:P,pagination:T,run:G,onPageChange:async function(e){const t=T.page!==e;T.page=e,t||await B()},onSizeChange:async function(e){const t=T.pageSize!==e,r=T.page!==h;T.pageSize=e,T.page=h,t||r||await B()},refresh:async function(){g?(C=!0,T.page=h,E.value=[],await e.nextTick(),C=!1,await B()):await G()}}};
package/dist/index.es.js CHANGED
@@ -1 +1 @@
1
- import{unref as e,isRef as t,ref as r,reactive as n,watch as o,nextTick as s,toValue as a,computed as i}from"vue";const u=e=>!("object"!=typeof e||null===e||Array.isArray(e)||e instanceof Date||e instanceof RegExp||e instanceof File||e instanceof Blob||"undefined"!=typeof FormData&&e instanceof FormData),c=(e,t)=>{const r={...e};for(const n in t){if(!Object.prototype.hasOwnProperty.call(t,n))continue;const e=t[n];if(void 0===e)continue;const o=r[n];u(e)&&u(o)?r[n]=c(o,e):r[n]=e}return r},l=e=>e>=200&&e<300,d=()=>!0,f=e=>new Promise(t=>setTimeout(t,e)),p=async e=>{if(!(e=>{if(!e)return!0;if(204===e.status||205===e.status||304===e.status)return!0;const t=e.headers.get("content-length");return!(!t||0!==Number(t))})(e))try{return await e.json()}catch(t){let n;try{n=await e.text()}catch{return}if(!n.trim())return;try{return JSON.parse(n)}catch(r){return}}},g=(e,t,r,n,o)=>({message:e,type:t,status:r,response:n,error:o}),h=e=>{if(e instanceof Error){const t=new Error(e.message);return e.name&&(t.name=e.name),e.stack&&(t.stack=e.stack),t}return new Error(String(e))},m=async(e,t,r)=>!!e?.enabled&&(!(t>(e.maxRetryCount??3))&&(e.shouldRetry?await e.shouldRetry(t,r):"network"===r.type||"timeout"===r.type)),y=(e,t)=>{const r=Object.create(e);let n=!1,o=!1,s=!1;const a=e.then.bind(e),i=e.catch.bind(e),u=a(e=>{s=!0===e.__errorSuppressed,delete e.__errorSuppressed;const t=new Proxy(e,{get(e,t){if(("1"===t||"number"==typeof t&&1===t)&&(o=!0),t===Symbol.iterator){const t=e[Symbol.iterator]();return function*(){let e=0;for(const r of t)1===e&&(o=!0),yield r,e++}}return e[t]}}),r=e[1];return e[2]||!r||s||n||queueMicrotask(()=>{queueMicrotask(()=>{if(!(o||o||n||s)){const e=new Error(`Unhandled fetch error: ${r.message} (type: ${r.type})`);throw e.fetchError=r,e}})}),t});r.cancel=()=>{t&&t()};const c=u.then.bind(u);return r.then=function(e,r){return e&&"function"==typeof e&&!r&&e.length<=2?y(c(t=>{const r=t[2],n=t[0],o=t[3];return r&&void 0!==n&&e(n,o),t}),t):c(e,r)},r.catchHttp=function(e){n=!0;const r=c(t=>{const r=t[2],n=t[1];return!r&&n&&"http"===n.type&&e(n),!r&&n&&(t.__errorSuppressed=!0),t});return y(r,t)},r.catchBusiness=function(e){n=!0;const r=c(t=>{const r=t[1],n=t[2];return!n&&r&&"business"===r.type&&e(r),!n&&r&&(t.__errorSuppressed=!0),t});return y(r,t)},r.catchNetwork=function(e){if(n=!0,e&&"function"==typeof e){const r=c(t=>{const r=t[1],n=t[2];return!n&&r&&e(r),!n&&r&&(t.__errorSuppressed=!0),t});return y(r,t)}return i(e)},r};class w{globalConfig={};constructor(e){e&&(this.globalConfig={...e}),this.request=this.request.bind(this),this.sendGet=this.sendGet.bind(this),this.sendPost=this.sendPost.bind(this),this.sendPostForm=this.sendPostForm.bind(this),this.sendPostBlob=this.sendPostBlob.bind(this),this.sendGetBlob=this.sendGetBlob.bind(this),this.setGlobalConfig=this.setGlobalConfig.bind(this)}async executeRequest(e,t,r=1){try{const y=e,w=y.signal;w&&(w.aborted?t.abort():w.addEventListener("abort",()=>t.abort(),{once:!0}));const b=(n=y.headers,o=y.url,s=y.method,n?"function"==typeof n?n({url:o,method:s}):n:{}),{body:v,urlParams:S}=((e,t,r)=>{if(!e)return{};if("GET"===t||"HEAD"===t||"OPTIONS"===t){if(u(e)){const t=new URLSearchParams;for(const r in e)if(Object.prototype.hasOwnProperty.call(e,r)){const n=e[r];null!=n&&t.append(r,String(n))}return{urlParams:t.toString()}}return{}}if("form"===r&&e instanceof FormData)return{body:e};if("form"===r&&u(e)){const t=new FormData;for(const r in e)if(Object.prototype.hasOwnProperty.call(e,r)){const n=e[r];null!=n&&(n instanceof File||n instanceof Blob?t.append(r,n):t.append(r,String(n)))}return{body:t}}return"json"===r?{body:JSON.stringify(e)}:"text"===r?{body:String(e)}:"blob"===r&&e instanceof Blob||"arraybuffer"===r&&e instanceof ArrayBuffer?{body:e}:{body:String(e)}})(y.data,y.method||"GET",y.requestType||"json"),E=((e,t,r)=>{if(t.startsWith("http://")||t.startsWith("https://")){if(r){const e=t.includes("?")?"&":"?";return`${t}${e}${r}`}return t}let n=t;e&&(n=`${e.endsWith("/")?e.slice(0,-1):e}${t.startsWith("/")?t:`/${t}`}`);if(r){const e=n.includes("?")?"&":"?";n=`${n}${e}${r}`}return n})(y.baseURL,y.url,S),P=y.timeout;let q,x=!1,T=b;v&&"string"==typeof v&&"json"===y.requestType?T={"Content-Type":"application/json",...b}:v instanceof FormData&&(T=b);const $=new AbortController,B=()=>$.abort();t.signal.addEventListener("abort",B);const R={method:y.method||"GET",headers:T,body:v,mode:"cors",credentials:!1===y.withCredentials?"same-origin":"include",signal:$.signal},C=fetch(E,R);let _;try{if(P){const e=new Promise((e,t)=>{q=setTimeout(()=>{x=!0,$.abort(),t(g("Request timeout","timeout"))},P)});_=await Promise.race([C,e])}else _=await C}catch(a){if(q&&clearTimeout(q),x){const e=g("Request timeout","timeout");if(await m(y.retry,r,e))return t.signal.removeEventListener("abort",B),await f(y.retry?.delay||1e3),this.executeRequest(y,t,r+1);let n=!1;if(y.onNetworkError){const t=()=>{n=!0};await y.onNetworkError(h(new Error(e.message)),t)}const o=[void 0,e,!1,void 0];return o.__errorSuppressed=n,t.signal.removeEventListener("abort",B),o}throw a}finally{q&&clearTimeout(q),t.signal.removeEventListener("abort",B)}const D=y.responseType||"json";let F;try{F=await(async(e,t)=>{if(!e)throw new Error("No response received");switch(t){case"json":default:return await p(e);case"text":return await e.text();case"blob":return await e.blob();case"arraybuffer":return await e.arrayBuffer();case"formData":return await e.formData()}})(_,D)}catch(i){const e=g("Failed to parse response","network",_.status,void 0,i instanceof Error?i:new Error(String(i)));if(await m(y.retry,r,e))return await f(y.retry?.delay||1e3),this.executeRequest(y,t,r+1);let n=!1;if(y.onNetworkError){const t=()=>{n=!0};await y.onNetworkError(h(e.error||new Error(e.message)),t)}const o=[void 0,e,!1,void 0];return o.__errorSuppressed=n,o}if(!(y.validateStatus||l)(_.status)){const e=g(`HTTP Error: ${_.status} ${_.statusText}`,"http",_.status,F);if(await m(y.retry,r,e))return await f(y.retry?.delay||1e3),this.executeRequest(y,t,r+1);let n=!1;if(y.onHttpError){const t=()=>{n=!0},r=new Error(e.message);await y.onHttpError(r,_.status,F,t)}const o=[void 0,e,!1,{data:F,status:_.status,statusText:_.statusText,headers:_.headers,response:_}];return o.__errorSuppressed=n,o}if(!(y.validateResponse||d)(F)){const e=g("Business validation failed","business",_.status,F);if(await m(y.retry,r,e))return await f(y.retry?.delay||1e3),this.executeRequest(y,t,r+1);let n=!1;if(y.onBusinessError){const t=()=>{n=!0},r=new Error(e.message);await y.onBusinessError(r,F,t)}const o=[void 0,e,!1,{data:F,status:_.status,statusText:_.statusText,headers:_.headers,response:_}];return o.__errorSuppressed=n,o}let G=F;if(y.transformResponse)try{G=y.transformResponse(F)}catch(c){console.warn("[toAwaitFetch] transformResponse error:",c)}return[G,void 0,!0,{data:F,status:_.status,statusText:_.statusText,headers:_.headers,response:_}]}catch(a){const n=(e=>e instanceof DOMException&&"AbortError"===e.name)(a),o=g(n?"Request aborted":a instanceof Error?a.message:"Network error","network",void 0,void 0,a instanceof Error?a:new Error(String(a)));if(!n&&await m(e.retry,r,o))return await f(e.retry?.delay||1e3),this.executeRequest(e,t,r+1);let s=!1;if(e.onNetworkError){const t=()=>{s=!0};await e.onNetworkError(h(o.error||new Error(o.message)),t)}const i=[void 0,o,!1,void 0];return i.__errorSuppressed=s,i}var n,o,s}request(e){const t=c({...this.globalConfig},e),r=new AbortController,n=this.executeRequest(t,r);return y(n,()=>r.abort())}setGlobalConfig(e){this.globalConfig={...e}}getGlobalConfig(){return{...this.globalConfig}}mergeGlobalConfig(e){this.globalConfig=c(this.globalConfig,e)}sendGet(e,t,r){return this.request({...r,url:e,method:"GET",data:t})}sendPost(e,t,r){return this.request({...r,url:e,method:"POST",data:t,requestType:"json"})}sendPostForm(e,t,r){return this.request({...r,url:e,method:"POST",data:t,requestType:"form"})}sendPostBlob(e,t,r){return this.request({...r,url:e,method:"POST",data:t,requestType:"json",responseType:"blob"})}sendGetBlob(e,t,r){return this.request({...r,url:e,method:"GET",data:t,responseType:"blob"})}}const b=e=>new w(e),v=new w,S=(e,t,r)=>v.request({...r,url:e,method:"GET",data:t}),E=(e,t,r)=>v.sendPost(e,t,r),P=(e,t,r)=>v.sendPostForm(e,t,r),q=(e,t,r)=>v.sendPostBlob(e,t,r),x=(e,t,r)=>v.sendGetBlob(e,t,r),T=e=>{v.setGlobalConfig(e)},$=e=>{e?.cancel?.()},B=Object.assign(e=>e&&"object"==typeof e&&"url"in e?v.request(e):b(e),{create:b,sendGet:v.sendGet.bind(v),sendPost:v.sendPost.bind(v),sendPostForm:v.sendPostForm.bind(v),sendPostBlob:v.sendPostBlob.bind(v),sendGetBlob:v.sendGetBlob.bind(v),setGlobalConfig:v.setGlobalConfig.bind(v),cancel:$});function R(e,t){if(!e||!t)return;const r=t.replace(/\[(\d+)\]/g,".$1").split(".").filter(Boolean);let n=e;for(const o of r){if(null==n)return;n=n[o]}return n}function C(t){return"function"==typeof t?t():e(t)}function _(e,r){"undefined"!=typeof __DEV__&&__DEV__&&void 0!==e&&!function(e){return"function"==typeof e||t(e)}(e)&&console.warn(`[${r}] 检测到 params 是普通对象,参数变化将不会触发请求更新。\n请使用 computed() 或 getter 函数包装:`)}const D={};function F(e){Object.assign(D,e),e.pagination&&(D.pagination={...D.pagination,...e.pagination})}function G(){return{...D,pagination:{...D.pagination}}}function j(e,t={}){const a={...D.pagination,...t.pagination},{params:i,immediate:u=D.immediate??!0,debounceDelay:c=D.debounceDelay??0,dataField:l=D.dataField??"",totalField:d=D.totalField??"",currentPageField:f=D.currentPageField??"",transformParams:p,transformResult:g,onSuccess:h,onError:m,incremental:y=D.incremental??!1}=t,{defaultPage:w=1,defaultPageSize:b=10,pageKey:v="page",pageSizeKey:S="pageSize"}=a,E=r([]),P=r(!1),q=r(null),x=n({page:w,pageSize:b,total:0});let T=0;async function $(){const t=++T;P.value=!0,q.value=null;try{const r=i?C(i):{},n={[v]:x.page,[S]:x.pageSize};let o;o=p?p(r,{page:x.page,pageSize:x.pageSize}):{...r,...n};const s=await e(o);if(t!==T)return;if(g){const e=g(s);if(e&&"object"==typeof e&&Array.isArray(e.list)){const t=e.list;y&&x.page>w?E.value=[...E.value,...t]:E.value=t,x.total="number"==typeof e.total?e.total:e.list.length}else E.value=[],x.total=0,console.warn("[useTableRequest] transformResult 返回格式无法识别,请返回 { list: T[], total: number } 格式。")}else{const e=s;if(Array.isArray(e))y&&x.page>w?E.value=[...E.value,...e]:E.value=e,x.total=e.length;else if(e&&"object"==typeof e){if(l&&""!==l.trim()){const t=R(e,l)||[];y&&x.page>w?E.value=[...E.value,...t]:E.value=t}else E.value=[];d&&""!==d.trim()?x.total=R(e,d)??0:x.total=0}else E.value=[],x.total=0}if(f&&""!==f.trim()){const e=R(s,f);"number"==typeof e&&e!==x.page&&(console.warn(`[useTableRequest] 后端返回页码(${e})与客户端页码(${x.page})不一致,已自动修正`),x.page=e)}h?.(E.value,s)}catch(r){if(t!==T)return;q.value=r instanceof Error?r:new Error(String(r)),m?.(q.value)}finally{t===T&&(P.value=!1)}}const B=c>0?function(e,t){let r=null;return(...n)=>{r&&clearTimeout(r),r=setTimeout(()=>{e(...n),r=null},t)}}($,c):$;let F=!1;async function G(){await $()}return o(()=>[x.page,x.pageSize],()=>{F||B()}),i&&(_(i,"useTableRequest"),o(()=>C(i),()=>{F=!0,x.page=w,F=!1,B()},{deep:!0})),u&&$(),{data:E,loading:P,error:q,pagination:x,run:G,onPageChange:async function(e){const t=x.page!==e;x.page=e,t||await $()},onSizeChange:async function(e){const t=x.pageSize!==e,r=x.page!==w;x.pageSize=e,x.page=w,t||r||await $()},refresh:async function(){y?(F=!0,x.page=w,E.value=[],await s(),F=!1,await $()):await G()}}}function N(e,n){let s,u;null===e||"object"!=typeof e||t(e)?(s=e,u=n||{}):(u=e,s="");const c=()=>a(u.precision)??0,l=()=>a(u.showThousands)??!1,d=r(a(s)?.toString()??""),f=i(()=>{let e=d.value;if(l()){const[t,r]=e.split(".");let n=t.replace(/\B(?=(\d{3})+(?!\d))/g,",");return r?n+=`.${r}`:e.includes(".")&&(n+="."),n}return e}),p=e=>{let t=null==e?"":e.toString().trim();const r=a(u.allowNegative)?/[^\d.-]/g:/[^\d.]/g;t=t.replace(r,""),t=t.replace(/^-/,"$#$").replace(/-/g,"").replace("$#$","-"),t.length>1&&t.startsWith("0")&&"."!==t[1]&&(t=t.replace(/^0+/,""),""===t&&(t="0")),t.startsWith("-0")&&t.length>2&&"."!==t[2]&&(t="-"+t.slice(2).replace(/^0+/,""),"-"===t&&(t="-0"));const n=c();if(n>0){t=t.replace(/^\./g,"").replace(/\./g,"$#$").replace(/\./g,"").replace("$#$",".");const e=t.indexOf(".");if(-1!==e){if(t.slice(e+1).length>n){const r=a(u.decimalLimitMode)??"discard";if("discard"===r)t=t.slice(0,e+n+1);else{const o=Math.pow(10,n);let s=Number(t);isNaN(s)?t=t.slice(0,e+n+1):("round"===r?s=Math.round(s*o)/o:"ceil"===r&&(s=Math.ceil(s*o)/o),t=s.toString())}}}}else t=t.replace(/\./g,"");return t};return o(()=>a(s),e=>{const t=null==e?"":e.toString();t!==d.value&&(d.value=t)}),{value:d,displayValue:f,handleInput:e=>{const t=p(e);d.value=t},handleBlur:()=>{if(!d.value)return;let e=Number(d.value);const t=u.max??1/0,r=u.min??-1/0;e>t&&(e=t,d.value=t.toString()),e<r&&(e=r,d.value=r.toString());const n=a(u.autoFixed)??!1;d.value=!0===n||"pad"===n?e.toFixed(c()):"trim"===n?Number(e.toFixed(c())).toString():e.toString()},formatter:e=>f.value,parser:e=>l()?e.replace(/,/g,""):e,clean:p}}const O=/* @__PURE__ */new Map,k=/* @__PURE__ */new Map,A=/* @__PURE__ */new Map,z=/* @__PURE__ */new Set;let M=null;const I=(e,t)=>new Promise((r,n)=>{if(M)return r(M);const o=indexedDB.open(e,1);o.onupgradeneeded=e=>{const r=e.target.result;r.objectStoreNames.contains(t)||r.createObjectStore(t)},o.onsuccess=e=>{M=e.target.result,r(M)},o.onerror=e=>{n(e.target.error)}});function L(e){const{requestFn:t,cacheKey:n,cacheTime:s=3e5,storage:a="memory",params:i,transformResult:u,immediate:c=!0,storageKeyPrefix:l="req_cache_",forceRefreshOnFirst:d=!1,dbName:f="RequestCacheDB",storeName:p="request_cache",onSuccess:g,onError:h}=e,m=(e=>{if("undefined"==typeof window)return"memory";try{if("localStorage"===e&&!window.localStorage)throw new Error;if("sessionStorage"===e&&!window.sessionStorage)throw new Error;if("indexedDB"===e&&!window.indexedDB)throw new Error;return e}catch(t){return console.warn(`[useRequestCache] ${e} is not supported in this environment, falling back to 'memory' mode.`),"memory"}})(C(a)),y=r(),w=r(!1),b=r(null),v=()=>{const e=i?C(i):{};if("function"==typeof n)try{return n(e)}catch(t){return console.warn("[useRequestCache] Generate key failed",t),""}return n},S=async e=>{if("indexedDB"===m){try{const t=await I(f,p);return new Promise((r,n)=>{const o=t.transaction([p],"readwrite").objectStore(p).delete(`${l}${e}`);o.onsuccess=()=>r(),o.onerror=()=>n(o.error)})}catch(r){console.warn("[useRequestCache] IndexedDB delete failed",r)}return}const t="localStorage"===m?localStorage:"sessionStorage"===m?sessionStorage:null;t&&t.removeItem(`${l}${e}`)},E=async(e=!1)=>{const r=v();if(!r)return void console.warn("[useRequestCache] Cache key is empty, skipping request");const n=C(d)&&!z.has(r),o=e||n,a=i?C(i):{};if(!o&&"indexedDB"!==m){const e=O.get(r);if(e){if(Date.now()-e.timestamp<s)return y.value=u?u(e.data):e.data,g&&g(y.value),y.value;O.delete(r)}}w.value=!0,b.value=null;try{if(k.has(r)){if(g||h){const e=A.get(r);e&&(g&&e.onSuccess.push(g),h&&e.onError.push(h))}const e=await k.get(r);return y.value=u?u(e):e,y.value}if(!o&&"memory"!==m){const e=await(async e=>{if("indexedDB"===m)try{const t=await I(f,p);return new Promise(r=>{const n=t.transaction([p],"readonly").objectStore(p).get(`${l}${e}`);n.onsuccess=()=>r(n.result||null),n.onerror=()=>r(null)})}catch(r){return console.warn("[useRequestCache] IndexedDB read failed",r),null}const t="localStorage"===m?localStorage:"sessionStorage"===m?sessionStorage:null;if(!t)return null;try{const r=t.getItem(`${l}${e}`);if(r)return JSON.parse(r)}catch(r){console.warn("[useRequestCache] Read storage failed",r)}return null})(r);if(e){if(Date.now()-e.timestamp<s)return"indexedDB"!==m&&O.set(r,e),y.value=u?u(e.data):e.data,g&&g(y.value),y.value;await S(r)}}const e=t(a);k.set(r,e),A.set(r,{onSuccess:g?[g]:[],onError:h?[h]:[]});try{const t=await e,o={data:t,timestamp:Date.now()};"indexedDB"!==m&&O.set(r,o),"memory"!==m&&await(async(e,t)=>{if("indexedDB"===m){try{const r=await I(f,p);return new Promise((n,o)=>{const s=r.transaction([p],"readwrite").objectStore(p).put(t,`${l}${e}`);s.onsuccess=()=>n(),s.onerror=()=>o(s.error)})}catch(n){console.warn("[useRequestCache] IndexedDB write failed",n)}return}const r="localStorage"===m?localStorage:"sessionStorage"===m?sessionStorage:null;if(r)try{r.setItem(`${l}${e}`,JSON.stringify(t))}catch(n){console.warn("[useRequestCache] Write storage failed",n)}})(r,o),n&&z.add(r),y.value=u?u(t):t;const s=A.get(r);if(s&&s.onSuccess){[...s.onSuccess].forEach(e=>e(y.value))}return k.delete(r),A.delete(r),y.value}catch(c){b.value=c;const e=A.get(r);if(e&&e.onError){[...e.onError].forEach(e=>e(c))}throw k.delete(r),A.delete(r),c}}catch(c){throw b.value=c,c}finally{w.value=!1}},P=()=>E(!1);return i?o(()=>C(i),()=>{P()},{deep:!0,immediate:c}):c&&P(),{data:y,loading:w,error:b,run:P,refresh:()=>E(!0),clearCache:async e=>{const t=e||v();t&&(O.delete(t),"memory"!==m&&await S(t))}}}const W="1.6.0";function H(){return W}export{W as VERSION,$ as cancel,b as createInstance,G as getTableRequestGlobalConfig,H as getVersion,S as sendGet,x as sendGetBlob,E as sendPost,q as sendPostBlob,P as sendPostForm,T as setGlobalConfig,F as setTableRequestGlobalConfig,B as toAwaitFetch,N as useInputNumber,L as useRequestCache,j as useTableRequest};
1
+ import{unref as e,isRef as t,ref as r,reactive as n,watch as o,nextTick as s,toValue as a,computed as i}from"vue";const u=e=>!("object"!=typeof e||null===e||Array.isArray(e)||e instanceof Date||e instanceof RegExp||e instanceof File||e instanceof Blob||"undefined"!=typeof FormData&&e instanceof FormData),c=(e,t)=>{const r={...e};for(const n in t){if(!Object.prototype.hasOwnProperty.call(t,n))continue;const e=t[n];if(void 0===e)continue;const o=r[n];u(e)&&u(o)?r[n]=c(o,e):r[n]=e}return r},l=e=>e>=200&&e<300,d=()=>!0,f=e=>new Promise(t=>setTimeout(t,e)),p=async e=>{if(!(e=>{if(!e)return!0;if(204===e.status||205===e.status||304===e.status)return!0;const t=e.headers.get("content-length");return!(!t||0!==Number(t))})(e))try{return await e.json()}catch(t){let n;try{n=await e.text()}catch{return}if(!n.trim())return;try{return JSON.parse(n)}catch(r){return}}},g=(e,t,r,n,o)=>({message:e,type:t,status:r,response:n,error:o}),h=e=>{if(e instanceof Error){const t=new Error(e.message);return e.name&&(t.name=e.name),e.stack&&(t.stack=e.stack),t}return new Error(String(e))},m=async(e,t,r)=>!!e?.enabled&&(!(t>(e.maxRetryCount??3))&&(e.shouldRetry?await e.shouldRetry(t,r):"network"===r.type||"timeout"===r.type)),y=(e,t)=>{const r=Object.create(e);let n=!1,o=!1,s=!1;const a=e.then.bind(e),i=e.catch.bind(e),u=a(e=>{s=!0===e.__errorSuppressed,delete e.__errorSuppressed;const t=new Proxy(e,{get(e,t){if(("1"===t||"number"==typeof t&&1===t)&&(o=!0),t===Symbol.iterator){const t=e[Symbol.iterator]();return function*(){let e=0;for(const r of t)1===e&&(o=!0),yield r,e++}}return e[t]}}),r=e[1];return e[2]||!r||s||n||queueMicrotask(()=>{queueMicrotask(()=>{if(!(o||o||n||s)){const e=new Error(`Unhandled fetch error: ${r.message} (type: ${r.type})`);throw e.fetchError=r,e}})}),t});r.cancel=()=>{t&&t()};const c=u.then.bind(u);return r.then=function(e,r){return e&&"function"==typeof e&&!r&&e.length<=2?y(c(t=>{const r=t[2],n=t[0],o=t[3];return r&&void 0!==n&&e(n,o),t}),t):c(e,r)},r.catchHttp=function(e){n=!0;const r=c(t=>{const r=t[2],n=t[1];return!r&&n&&"http"===n.type&&e(n),!r&&n&&(t.__errorSuppressed=!0),t});return y(r,t)},r.catchBusiness=function(e){n=!0;const r=c(t=>{const r=t[1],n=t[2];return!n&&r&&"business"===r.type&&e(r),!n&&r&&(t.__errorSuppressed=!0),t});return y(r,t)},r.catchNetwork=function(e){if(n=!0,e&&"function"==typeof e){const r=c(t=>{const r=t[1],n=t[2];return!n&&r&&e(r),!n&&r&&(t.__errorSuppressed=!0),t});return y(r,t)}return i(e)},r};class w{globalConfig={};constructor(e){e&&(this.globalConfig={...e}),this.request=this.request.bind(this),this.sendGet=this.sendGet.bind(this),this.sendPost=this.sendPost.bind(this),this.sendPostForm=this.sendPostForm.bind(this),this.sendPostBlob=this.sendPostBlob.bind(this),this.sendGetBlob=this.sendGetBlob.bind(this),this.setGlobalConfig=this.setGlobalConfig.bind(this)}async executeRequest(e,t,r=1){try{const y=e,w=y.signal;w&&(w.aborted?t.abort():w.addEventListener("abort",()=>t.abort(),{once:!0}));const b=(n=y.headers,o=y.url,s=y.method,n?"function"==typeof n?n({url:o,method:s}):n:{}),{body:v,urlParams:S}=((e,t,r)=>{if(!e)return{};if("GET"===t||"HEAD"===t||"OPTIONS"===t){if(u(e)){const t=new URLSearchParams;for(const r in e)if(Object.prototype.hasOwnProperty.call(e,r)){const n=e[r];null!=n&&t.append(r,String(n))}return{urlParams:t.toString()}}return{}}if("form"===r&&e instanceof FormData)return{body:e};if("form"===r&&u(e)){const t=new FormData;for(const r in e)if(Object.prototype.hasOwnProperty.call(e,r)){const n=e[r];null!=n&&(n instanceof File||n instanceof Blob?t.append(r,n):t.append(r,String(n)))}return{body:t}}return"json"===r?{body:JSON.stringify(e)}:"text"===r?{body:String(e)}:"blob"===r&&e instanceof Blob||"arraybuffer"===r&&e instanceof ArrayBuffer?{body:e}:{body:String(e)}})(y.data,y.method||"GET",y.requestType||"json"),E=((e,t,r)=>{if(t.startsWith("http://")||t.startsWith("https://")){if(r){const e=t.includes("?")?"&":"?";return`${t}${e}${r}`}return t}let n=t;e&&(n=`${e.endsWith("/")?e.slice(0,-1):e}${t.startsWith("/")?t:`/${t}`}`);if(r){const e=n.includes("?")?"&":"?";n=`${n}${e}${r}`}return n})(y.baseURL,y.url,S),P=y.timeout;let q,x=!1,T=b;v&&"string"==typeof v&&"json"===y.requestType?T={"Content-Type":"application/json",...b}:v instanceof FormData&&(T=b);const $=new AbortController,B=()=>$.abort();t.signal.addEventListener("abort",B);const R={method:y.method||"GET",headers:T,body:v,mode:"cors",credentials:!1===y.withCredentials?"same-origin":"include",signal:$.signal},C=fetch(E,R);let _;try{if(P){const e=new Promise((e,t)=>{q=setTimeout(()=>{x=!0,$.abort(),t(g("Request timeout","timeout"))},P)});_=await Promise.race([C,e])}else _=await C}catch(a){if(q&&clearTimeout(q),x){const e=g("Request timeout","timeout");if(await m(y.retry,r,e))return t.signal.removeEventListener("abort",B),await f(y.retry?.delay||1e3),this.executeRequest(y,t,r+1);let n=!1;if(y.onNetworkError){const t=()=>{n=!0};await y.onNetworkError(h(new Error(e.message)),t)}const o=[void 0,e,!1,void 0];return o.__errorSuppressed=n,t.signal.removeEventListener("abort",B),o}throw a}finally{q&&clearTimeout(q),t.signal.removeEventListener("abort",B)}const D=y.responseType||"json";let F;try{F=await(async(e,t)=>{if(!e)throw new Error("No response received");switch(t){case"json":default:return await p(e);case"text":return await e.text();case"blob":return await e.blob();case"arraybuffer":return await e.arrayBuffer();case"formData":return await e.formData()}})(_,D)}catch(i){const e=g("Failed to parse response","network",_.status,void 0,i instanceof Error?i:new Error(String(i)));if(await m(y.retry,r,e))return await f(y.retry?.delay||1e3),this.executeRequest(y,t,r+1);let n=!1;if(y.onNetworkError){const t=()=>{n=!0};await y.onNetworkError(h(e.error||new Error(e.message)),t)}const o=[void 0,e,!1,void 0];return o.__errorSuppressed=n,o}if(!(y.validateStatus||l)(_.status)){const e=g(`HTTP Error: ${_.status} ${_.statusText}`,"http",_.status,F);if(await m(y.retry,r,e))return await f(y.retry?.delay||1e3),this.executeRequest(y,t,r+1);let n=!1;if(y.onHttpError){const t=()=>{n=!0},r=new Error(e.message);await y.onHttpError(r,_.status,F,t)}const o=[void 0,e,!1,{data:F,status:_.status,statusText:_.statusText,headers:_.headers,response:_}];return o.__errorSuppressed=n,o}if(!(y.validateResponse||d)(F)){const e=g("Business validation failed","business",_.status,F);if(await m(y.retry,r,e))return await f(y.retry?.delay||1e3),this.executeRequest(y,t,r+1);let n=!1;if(y.onBusinessError){const t=()=>{n=!0},r=new Error(e.message);await y.onBusinessError(r,F,t)}const o=[void 0,e,!1,{data:F,status:_.status,statusText:_.statusText,headers:_.headers,response:_}];return o.__errorSuppressed=n,o}let G=F;if(y.transformResponse)try{G=y.transformResponse(F)}catch(c){console.warn("[toAwaitFetch] transformResponse error:",c)}return[G,void 0,!0,{data:F,status:_.status,statusText:_.statusText,headers:_.headers,response:_}]}catch(a){const n=(e=>e instanceof DOMException&&"AbortError"===e.name)(a),o=g(n?"Request aborted":a instanceof Error?a.message:"Network error","network",void 0,void 0,a instanceof Error?a:new Error(String(a)));if(!n&&await m(e.retry,r,o))return await f(e.retry?.delay||1e3),this.executeRequest(e,t,r+1);let s=!1;if(e.onNetworkError){const t=()=>{s=!0};await e.onNetworkError(h(o.error||new Error(o.message)),t)}const i=[void 0,o,!1,void 0];return i.__errorSuppressed=s,i}var n,o,s}request(e){const t=c({...this.globalConfig},e),r=new AbortController,n=this.executeRequest(t,r);return y(n,()=>r.abort())}setGlobalConfig(e){this.globalConfig={...e}}getGlobalConfig(){return{...this.globalConfig}}mergeGlobalConfig(e){this.globalConfig=c(this.globalConfig,e)}sendGet(e,t,r){return this.request({...r,url:e,method:"GET",data:t})}sendPost(e,t,r){return this.request({...r,url:e,method:"POST",data:t,requestType:"json"})}sendPostForm(e,t,r){return this.request({...r,url:e,method:"POST",data:t,requestType:"form"})}sendPostBlob(e,t,r){return this.request({...r,url:e,method:"POST",data:t,requestType:"json",responseType:"blob"})}sendGetBlob(e,t,r){return this.request({...r,url:e,method:"GET",data:t,responseType:"blob"})}}const b=e=>new w(e),v=new w,S=(e,t,r)=>v.request({...r,url:e,method:"GET",data:t}),E=(e,t,r)=>v.sendPost(e,t,r),P=(e,t,r)=>v.sendPostForm(e,t,r),q=(e,t,r)=>v.sendPostBlob(e,t,r),x=(e,t,r)=>v.sendGetBlob(e,t,r),T=e=>{v.setGlobalConfig(e)},$=e=>{e?.cancel?.()},B=Object.assign(e=>e&&"object"==typeof e&&"url"in e?v.request(e):b(e),{create:b,sendGet:v.sendGet.bind(v),sendPost:v.sendPost.bind(v),sendPostForm:v.sendPostForm.bind(v),sendPostBlob:v.sendPostBlob.bind(v),sendGetBlob:v.sendGetBlob.bind(v),setGlobalConfig:v.setGlobalConfig.bind(v),cancel:$});function R(e,t){if(!e||!t)return;const r=t.replace(/\[(\d+)\]/g,".$1").split(".").filter(Boolean);let n=e;for(const o of r){if(null==n)return;n=n[o]}return n}function C(t){return"function"==typeof t?t():e(t)}function _(e,r){"undefined"!=typeof __DEV__&&__DEV__&&void 0!==e&&!function(e){return"function"==typeof e||t(e)}(e)&&console.warn(`[${r}] 检测到 params 是普通对象,参数变化将不会触发请求更新。\n请使用 computed() 或 getter 函数包装:`)}const D={};function F(e){Object.assign(D,e),e.pagination&&(D.pagination={...D.pagination,...e.pagination})}function G(){return{...D,pagination:{...D.pagination}}}function j(e,t={}){const a={...D.pagination,...t.pagination},{params:i,immediate:u=D.immediate??!0,debounceDelay:c=D.debounceDelay??0,dataField:l=D.dataField??"",totalField:d=D.totalField??"",currentPageField:f=D.currentPageField??"",transformParams:p,transformResult:g,onSuccess:h,onError:m,incremental:y=D.incremental??!1}=t,{defaultPage:w=1,defaultPageSize:b=10,pageKey:v="page",pageSizeKey:S="pageSize"}=a,E=r([]),P=r(!1),q=r(null),x=n({page:w,pageSize:b,total:0});let T=0;async function $(){const t=++T;P.value=!0,q.value=null;try{const r=i?C(i):{},n={[v]:x.page,[S]:x.pageSize};let o;o=p?p(r,{page:x.page,pageSize:x.pageSize}):{...r,...n};const s=await e(o);if(t!==T)return;if(g){const e=g(s);if(e&&"object"==typeof e&&Array.isArray(e.list)){const t=e.list;y&&x.page>w?E.value=[...E.value,...t]:E.value=t,x.total="number"==typeof e.total?e.total:e.list.length}else E.value=[],x.total=0,console.warn("[useTableRequest] transformResult 返回格式无法识别,请返回 { list: T[], total: number } 格式。")}else{const e=s;if(Array.isArray(e))y&&x.page>w?E.value=[...E.value,...e]:E.value=e,x.total=e.length;else if(e&&"object"==typeof e){if(l&&""!==l.trim()){const t=R(e,l)||[];y&&x.page>w?E.value=[...E.value,...t]:E.value=t}else E.value=[];d&&""!==d.trim()?x.total=R(e,d)??0:x.total=0}else E.value=[],x.total=0}if(f&&""!==f.trim()){const e=R(s,f);"number"==typeof e&&e!==x.page&&(console.warn(`[useTableRequest] 后端返回页码(${e})与客户端页码(${x.page})不一致,已自动修正`),x.page=e)}h?.(E.value,s)}catch(r){if(t!==T)return;q.value=r instanceof Error?r:new Error(String(r)),m?.(q.value)}finally{t===T&&(P.value=!1)}}const B=c>0?function(e,t){let r=null;return(...n)=>{r&&clearTimeout(r),r=setTimeout(()=>{e(...n),r=null},t)}}($,c):$;let F=!1;async function G(){await $()}return o(()=>[x.page,x.pageSize],()=>{F||B()}),i&&(_(i,"useTableRequest"),o(()=>C(i),()=>{F=!0,x.page=w,F=!1,B()},{deep:!0})),u&&$(),{data:E,loading:P,error:q,pagination:x,run:G,onPageChange:async function(e){const t=x.page!==e;x.page=e,t||await $()},onSizeChange:async function(e){const t=x.pageSize!==e,r=x.page!==w;x.pageSize=e,x.page=w,t||r||await $()},refresh:async function(){y?(F=!0,x.page=w,E.value=[],await s(),F=!1,await $()):await G()}}}function N(e,n){let s,u;null===e||"object"!=typeof e||t(e)?(s=e,u=n||{}):(u=e,s="");const c=()=>a(u.precision)??0,l=()=>a(u.showThousands)??!1,d=r(a(s)?.toString()??""),f=i(()=>{let e=d.value;if(l()){const[t,r]=e.split(".");let n=t.replace(/\B(?=(\d{3})+(?!\d))/g,",");return r?n+=`.${r}`:e.includes(".")&&(n+="."),n}return e}),p=e=>{let t=null==e?"":e.toString().trim();const r=a(u.allowNegative)?/[^\d.-]/g:/[^\d.]/g;t=t.replace(r,""),t=t.replace(/^-/,"$#$").replace(/-/g,"").replace("$#$","-"),t.length>1&&t.startsWith("0")&&"."!==t[1]&&(t=t.replace(/^0+/,""),""===t&&(t="0")),t.startsWith("-0")&&t.length>2&&"."!==t[2]&&(t="-"+t.slice(2).replace(/^0+/,""),"-"===t&&(t="-0"));const n=c();if(n>0){t=t.replace(/^\./g,"").replace(/\./g,"$#$").replace(/\./g,"").replace("$#$",".");const e=t.indexOf(".");if(-1!==e){if(t.slice(e+1).length>n){const r=a(u.decimalLimitMode)??"discard";if("discard"===r)t=t.slice(0,e+n+1);else{const o=Math.pow(10,n);let s=Number(t);isNaN(s)?t=t.slice(0,e+n+1):("round"===r?s=Math.round(s*o)/o:"ceil"===r&&(s=Math.ceil(s*o)/o),t=s.toString())}}}}else t=t.replace(/\./g,"");return t};return o(()=>a(s),e=>{const t=null==e?"":e.toString();t!==d.value&&(d.value=t)}),{value:d,displayValue:f,handleInput:e=>{const t=p(e);d.value=t},handleBlur:()=>{if(!d.value)return;let e=Number(d.value);const t=u.max??1/0,r=u.min??-1/0;e>t&&(e=t,d.value=t.toString()),e<r&&(e=r,d.value=r.toString());const n=a(u.autoFixed)??!1;d.value=!0===n||"pad"===n?e.toFixed(c()):"trim"===n?Number(e.toFixed(c())).toString():e.toString()},formatter:e=>f.value,parser:e=>l()?e.replace(/,/g,""):e,clean:p}}const O=/* @__PURE__ */new Map,k=/* @__PURE__ */new Map,A=/* @__PURE__ */new Map,z=/* @__PURE__ */new Set;let M=null;const I=(e,t)=>new Promise((r,n)=>{if(M)return r(M);const o=indexedDB.open(e,1);o.onupgradeneeded=e=>{const r=e.target.result;r.objectStoreNames.contains(t)||r.createObjectStore(t)},o.onsuccess=e=>{M=e.target.result,r(M)},o.onerror=e=>{n(e.target.error)}});function L(e){const{requestFn:t,cacheKey:n,cacheTime:s=3e5,storage:a="memory",params:i,transformResult:u,immediate:c=!0,storageKeyPrefix:l="req_cache_",forceRefreshOnFirst:d=!1,dbName:f="RequestCacheDB",storeName:p="request_cache",onSuccess:g,onError:h}=e,m=(e=>{if("undefined"==typeof window)return"memory";try{if("localStorage"===e&&!window.localStorage)throw new Error;if("sessionStorage"===e&&!window.sessionStorage)throw new Error;if("indexedDB"===e&&!window.indexedDB)throw new Error;return e}catch(t){return console.warn(`[useRequestCache] ${e} is not supported in this environment, falling back to 'memory' mode.`),"memory"}})(C(a)),y=r(),w=r(!1),b=r(null),v=()=>{const e=i?C(i):{};if("function"==typeof n)try{return n(e)}catch(t){return console.warn("[useRequestCache] Generate key failed",t),""}return n},S=async e=>{if("indexedDB"===m){try{const t=await I(f,p);return new Promise((r,n)=>{const o=t.transaction([p],"readwrite").objectStore(p).delete(`${l}${e}`);o.onsuccess=()=>r(),o.onerror=()=>n(o.error)})}catch(r){console.warn("[useRequestCache] IndexedDB delete failed",r)}return}const t="localStorage"===m?localStorage:"sessionStorage"===m?sessionStorage:null;t&&t.removeItem(`${l}${e}`)},E=async(e=!1)=>{const r=v();if(!r)return void console.warn("[useRequestCache] Cache key is empty, skipping request");const n=C(d)&&!z.has(r),o=e||n,a=i?C(i):{};if(!o&&"indexedDB"!==m){const e=O.get(r);if(e){if(Date.now()-e.timestamp<s)return y.value=u?u(e.data):e.data,g&&g(y.value),y.value;O.delete(r)}}w.value=!0,b.value=null;try{if(k.has(r)){if(g||h){const e=A.get(r);e&&(g&&e.onSuccess.push(g),h&&e.onError.push(h))}const e=await k.get(r);return y.value=u?u(e):e,y.value}if(!o&&"memory"!==m){const e=await(async e=>{if("indexedDB"===m)try{const t=await I(f,p);return new Promise(r=>{const n=t.transaction([p],"readonly").objectStore(p).get(`${l}${e}`);n.onsuccess=()=>r(n.result||null),n.onerror=()=>r(null)})}catch(r){return console.warn("[useRequestCache] IndexedDB read failed",r),null}const t="localStorage"===m?localStorage:"sessionStorage"===m?sessionStorage:null;if(!t)return null;try{const r=t.getItem(`${l}${e}`);if(r)return JSON.parse(r)}catch(r){console.warn("[useRequestCache] Read storage failed",r)}return null})(r);if(e){if(Date.now()-e.timestamp<s)return"indexedDB"!==m&&O.set(r,e),y.value=u?u(e.data):e.data,g&&g(y.value),y.value;await S(r)}}const e=t(a);k.set(r,e),A.set(r,{onSuccess:g?[g]:[],onError:h?[h]:[]});try{const t=await e,o={data:t,timestamp:Date.now()};"indexedDB"!==m&&O.set(r,o),"memory"!==m&&await(async(e,t)=>{if("indexedDB"===m){try{const r=await I(f,p);return new Promise((n,o)=>{const s=r.transaction([p],"readwrite").objectStore(p).put(t,`${l}${e}`);s.onsuccess=()=>n(),s.onerror=()=>o(s.error)})}catch(n){console.warn("[useRequestCache] IndexedDB write failed",n)}return}const r="localStorage"===m?localStorage:"sessionStorage"===m?sessionStorage:null;if(r)try{r.setItem(`${l}${e}`,JSON.stringify(t))}catch(n){console.warn("[useRequestCache] Write storage failed",n)}})(r,o),n&&z.add(r),y.value=u?u(t):t;const s=A.get(r);if(s&&s.onSuccess){[...s.onSuccess].forEach(e=>e(y.value))}return k.delete(r),A.delete(r),y.value}catch(c){b.value=c;const e=A.get(r);if(e&&e.onError){[...e.onError].forEach(e=>e(c))}throw k.delete(r),A.delete(r),c}}catch(c){throw b.value=c,c}finally{w.value=!1}},P=()=>E(!1);return i?o(()=>C(i),()=>{P()},{deep:!0,immediate:c}):c&&P(),{data:y,loading:w,error:b,run:P,refresh:()=>E(!0),clearCache:async e=>{const t=e||v();t&&(O.delete(t),"memory"!==m&&await S(t))}}}const W="1.6.2";function H(){return W}export{W as VERSION,$ as cancel,b as createInstance,G as getTableRequestGlobalConfig,H as getVersion,S as sendGet,x as sendGetBlob,E as sendPost,q as sendPostBlob,P as sendPostForm,T as setGlobalConfig,F as setTableRequestGlobalConfig,B as toAwaitFetch,N as useInputNumber,L as useRequestCache,j as useTableRequest};
@@ -1,65 +1,69 @@
1
1
  {
2
- "version": "1.6.0",
3
- "generatedAt": "2026-03-11T02:40:03.145Z",
2
+ "version": "1.6.2",
3
+ "generatedAt": "2026-03-17T05:37:08.311Z",
4
4
  "hooks": [
5
5
  {
6
6
  "name": "toAwaitFetch",
7
- "description": "",
7
+ "description": "一个强大的 HTTP 请求工具,支持链式调用和解构两种使用方式,提供了完整的错误处理、重试机制和全局配置功能。",
8
8
  "typeDefinitions": "export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\n\nexport type RequestType = 'json' | 'form' | 'text' | 'blob' | 'arraybuffer';\n\nexport type ResponseType = 'json' | 'text' | 'blob' | 'arraybuffer' | 'formData';\n\nexport type HeadersConfig = \n\nexport type ValidateStatusFn = (status: number) => boolean;\n\nexport type ValidateResponseFn<T = any> = (response: T) => boolean;\n\nexport type TransformResponseFn<TRaw = any, TTransformed = any> = (data: TRaw) => TTransformed;\n\nexport type HttpErrorHandler<T = any> = (\n error: Error,\n status: number,\n response: T,\n suppressError: () => void,\n) => void | Promise<void>;\n\nexport type BusinessErrorHandler<T = any> = (\n error: Error,\n response: T,\n suppressError: () => void\n) => void | Promise<void>;\n\nexport type NetworkErrorHandler = (\n error: Error,\n suppressError: () => void,\n) => void | Promise<void>;\n\nexport type ShouldRetry = (attempt: number, error: FetchError) => boolean | Promise<boolean>;\n\nexport interface RetryConfig {\n /**\n * 是否开启重试\n * @default false\n */\n enabled?: boolean;\n /**\n * 最大重试次数\n * @default 3\n */\n maxRetryCount?: number;\n /**\n * 重试延迟时间(毫秒)\n * @default 1000\n */\n delay?: number;\n /**\n * 自定义重试判断函数,返回 true 表示继续重试,false 表示停止重试\n * 如果未提供,将使用默认的重试逻辑\n */\n shouldRetry?: ShouldRetry;\n}\n\nexport interface RequestConfig<TRequest = any, TResponse = any> {\n /**\n * 请求 URL(必需)\n */\n url: string;\n /**\n * HTTP 方法\n * @default 'GET'\n */\n method?: HttpMethod;\n /**\n * 请求数据\n * - GET 请求时,data 会转换为 URL 查询参数\n * - POST/PUT/PATCH 等请求时,data 作为请求体数据\n * - POST 请求的查询参数应该放在 URL 中(如 '/api/users?page=1')\n */\n data?: TRequest;\n /**\n * 验证请求状态码是否符合要求的函数,返回布尔类型\n */\n validateStatus?: ValidateStatusFn;\n /**\n * 验证请求内容是否符合业务要求,返回布尔类型\n */\n validateResponse?: ValidateResponseFn<TResponse>;\n /**\n * 响应数据转换函数\n * 用于将原始响应数据转换为业务中使用的数据格式\n * 在 validateResponse 验证通过后执行\n * @example\n * transformResponse: (data) => data.result.list\n */\n transformResponse?: TransformResponseFn<TResponse, TResponse>;\n /**\n * 用来捕获 validateStatus 为 false 时进行错误处理的函数\n */\n onHttpError?: HttpErrorHandler<TResponse>;\n /**\n * 用来捕获 validateResponse 为 false 时进行错误处理的函数\n * 主要用来处理业务场景的错误\n */\n onBusinessError?: BusinessErrorHandler<TResponse>;\n /**\n * 用来捕获网络异常的错误处理函数\n * 主要用来处理无网络、跨域等报错问题\n */\n onNetworkError?: NetworkErrorHandler;\n /**\n * 重试配置\n */\n retry?: RetryConfig;\n /**\n * 请求超时时间(毫秒)\n */\n timeout?: number;\n /**\n * 请求头配置\n * 可以是 HeadersInit、Record<string, string> 或返回这些类型的函数\n * 函数接收 { url, method } 作为参数,可用于动态生成请求头\n */\n headers?: HeadersConfig;\n /**\n * 取消请求的信号(可与返回的 cancel 一起使用)\n */\n signal?: AbortSignal;\n /**\n * 是否发送凭证(Cookie/授权头)\n * @default true\n */\n withCredentials?: boolean;\n /**\n * 请求数据类型\n * @default 'json'\n */\n requestType?: RequestType;\n /**\n * 响应数据类型\n * @default 'json'\n */\n responseType?: ResponseType;\n}\n\nexport interface GlobalConfig<TResponse = any> {\n /**\n * 基础 URL,会与请求的 url 拼接\n */\n baseURL?: string;\n /**\n * 验证请求状态码是否符合要求的函数,返回布尔类型\n */\n validateStatus?: ValidateStatusFn;\n /**\n * 验证请求内容是否符合业务要求,返回布尔类型\n */\n validateResponse?: ValidateResponseFn<TResponse>;\n /**\n * 响应数据转换函数\n * 用于将原始响应数据转换为业务中使用的数据格式\n * 在 validateResponse 验证通过后执行\n * @example\n * // 全局配置:提取通用的 data 字段\n * transformResponse: (response) => response.data\n */\n transformResponse?: TransformResponseFn<TResponse, TResponse>;\n /**\n * 用来捕获 validateStatus 为 false 时进行错误处理的函数\n */\n onHttpError?: HttpErrorHandler<TResponse>;\n /**\n * 用来捕获 validateResponse 为 false 时进行错误处理的函数\n * 主要用来处理业务场景的错误\n */\n onBusinessError?: BusinessErrorHandler<TResponse>;\n /**\n * 用来捕获网络异常的错误处理函数\n * 主要用来处理无网络、跨域等报错问题\n */\n onNetworkError?: NetworkErrorHandler;\n /**\n * 重试配置\n */\n retry?: RetryConfig;\n /**\n * 请求超时时间(毫秒)\n */\n timeout?: number;\n /**\n * 请求头配置\n * 可以是 HeadersInit、Record<string, string> 或返回这些类型的函数\n * 函数接收 { url, method } 作为参数,可用于动态生成请求头\n */\n headers?: HeadersConfig;\n /**\n * 是否发送凭证(Cookie/授权头)\n * @default true\n */\n withCredentials?: boolean;\n /**\n * 请求数据类型\n * @default 'json'\n */\n requestType?: RequestType;\n /**\n * 响应数据类型\n * @default 'json'\n */\n responseType?: ResponseType;\n}\n\nexport interface FetchResponse<T = any> {\n /**\n * 响应数据\n */\n data: T;\n /**\n * HTTP 状态码\n */\n status: number;\n /**\n * 状态文本\n */\n statusText: string;\n /**\n * 响应头\n */\n headers: Headers;\n /**\n * 原始 Response 对象\n */\n response: Response;\n}\n\nexport interface FetchError<T = any> {\n /**\n * 错误信息\n */\n message: string;\n /**\n * 错误类型\n */\n type: 'http' | 'business' | 'network' | 'timeout';\n /**\n * HTTP 状态码(如果是 HTTP 错误)\n */\n status?: number;\n /**\n * 响应数据(如果有)\n */\n response?: T;\n /**\n * 原始错误对象\n */\n error?: Error | TypeError | DOMException;\n}\n\nexport type FetchResult<T = any> = [T | undefined, FetchError | undefined, boolean, FetchResponse<T> | undefined];\n\nexport interface FetchResultPromise<T = any> extends Promise<FetchResult<T>> {\n /**\n * 成功回调(重载 Promise 的 then 方法)\n * @param callback 成功时的回调函数,接收数据和可选的完整响应对象\n * @returns 返回自身,支持链式调用\n */\n then(callback: (data: T, response: FetchResponse<T>) => void): this;\n /**\n * 取消当前请求(触发 AbortController)\n */\n cancel(): void;\n \n /**\n * Promise 的 then 方法(保持兼容性)\n */\n then<TResult1 = FetchResult<T>, TResult2 = never>(\n onfulfilled?: ((value: FetchResult<T>) => TResult1 | PromiseLike<TResult1>) | null | undefined,\n onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined\n ): Promise<TResult1 | TResult2>;\n \n /**\n * HTTP 验证错误回调\n * @param callback HTTP 验证错误时的回调函数(validateStatus 返回 false)\n * @returns 返回自身,支持链式调用\n */\n catchHttp(callback: (error: FetchError) => void): this;\n \n /**\n * 业务验证错误回调\n * @param callback 业务验证错误时的回调函数(validateResponse 返回 false)\n * @returns 返回自身,支持链式调用\n */\n catchBusiness(callback: (error: FetchError) => void): this;\n \n /**\n * 网络错误回调(网络错误、超时等)\n * @param callback 网络错误时的回调函数\n * @returns 返回自身,支持链式调用\n */\n catchNetwork(callback: (error: FetchError) => void): this;\n \n /**\n * Promise 的 catch 方法(保持兼容性)\n */\n catch<TResult = never>(\n onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined\n ): Promise<FetchResult<T> | TResult>;\n}\n\nexport interface FetchInstance {\n /**\n * 发送请求\n * @param config 请求配置\n * @returns FetchResultPromise<TResponse> 支持链式调用和解构\n */\n request<TRequest = any, TResponse = any>(\n config: RequestConfig<TRequest, TResponse>\n ): FetchResultPromise<TResponse>;\n\n /**\n * 设置全局配置\n * @param config 全局配置\n */\n setGlobalConfig(config: GlobalConfig): void;\n /**\n * 获取当前全局配置\n */\n getGlobalConfig(): GlobalConfig;\n /**\n * 合并全局配置\n * @param config 部分全局配置\n */\n mergeGlobalConfig(config: Partial<GlobalConfig>): void;\n\n /**\n * GET 请求(支持链式调用)\n * @param url 请求 URL\n * @param params 请求参数(会转换为 URL 查询参数)\n * @param config 请求配置(不包含 url、method 和 data)\n * @returns FetchResultPromise<TResponse> 支持链式调用和解构\n */\n sendGet<TResponse = any, TRequest = any>(\n url: string,\n params?: TRequest,\n config?: Omit<RequestConfig<TRequest, TResponse>, 'url' | 'method' | 'data'>\n ): FetchResultPromise<TResponse>;\n\n /**\n * POST 请求(支持链式调用)\n * 默认使用 JSON 格式发送数据\n * @param url 请求 URL\n * @param data 请求数据\n * @param config 请求配置(不包含 url、method 和 data)\n * @returns FetchResultPromise<TResponse> 支持链式调用和解构\n */\n sendPost<TResponse = any, TRequest = any>(\n url: string,\n data?: TRequest,\n config?: Omit<RequestConfig<TRequest, TResponse>, 'url' | 'method' | 'data'>\n ): FetchResultPromise<TResponse>;\n\n /**\n * POST 请求,使用 FormData 格式发送数据(支持文件上传)\n * @param url 请求 URL\n * @param data 请求数据(对象会自动转换为 FormData)\n * @param config 请求配置(不包含 url、method 和 data)\n * @returns FetchResultPromise<TResponse> 支持链式调用和解构\n */\n sendPostForm<TResponse = any, TRequest = any>(\n url: string,\n data?: TRequest,\n config?: Omit<RequestConfig<TRequest, TResponse>, 'url' | 'method' | 'data'>\n ): FetchResultPromise<TResponse>;\n\n /**\n * POST 请求,发送 Blob 数据\n * @param url 请求 URL\n * @param data 请求体(JSON/可序列化对象)\n * @param config 请求配置(不包含 url、method 和 data)\n * @returns FetchResultPromise<Blob> 支持链式调用和解构\n */\n sendPostBlob<TRequest = any>(\n url: string,\n data?: TRequest,\n config?: Omit<RequestConfig<TRequest, Blob>, 'url' | 'method' | 'data'>\n ): FetchResultPromise<Blob>;\n\n /**\n * GET 请求,响应返回 Blob 数据(用于下载文件)\n * @param url 请求 URL\n * @param params 请求参数(会转换为 URL 查询参数)\n * @param config 请求配置(不包含 url、method 和 data)\n * @returns FetchResultPromise<Blob> 支持链式调用和解构\n */\n sendGetBlob<TRequest = any>(\n url: string,\n params?: TRequest,\n config?: Omit<RequestConfig<TRequest, Blob>, 'url' | 'method' | 'data'>\n ): FetchResultPromise<Blob>;\n}\n\nexport type CreateFetch = (globalConfig?: GlobalConfig) => FetchInstance;\n\nexport type ToAwaitFetch = CreateFetch &",
9
9
  "templates": {
10
- "default": ""
10
+ "default": "import { sendGet, sendPost } from 'xn-fe-tools'\n\ninterface User {\n id: number\n name: string\n}\n\ninterface CreateUserRequest {\n name: string\n}\n\n// GET 请求(响应类型明确,请求参数省略)\nconst [data, error, success] = await sendGet<User[]>('/api/users')\n\n// POST 请求\nconst [created] = await sendPost<User, CreateUserRequest>('/api/users', { name: 'John' })"
11
11
  },
12
- "docPath": "docs/hooks/toAwaitFetch.md",
12
+ "docPath": "docs/tools/toAwaitFetch.md",
13
13
  "rulePath": "docs/rules/toAwaitFetch.mdc",
14
- "hasDoc": false,
14
+ "hasDoc": true,
15
15
  "hasRule": true
16
16
  },
17
17
  {
18
18
  "name": "toSentrySetup",
19
- "description": "",
19
+ "description": "Sentry 统一初始化工具集,支持 Vue 3、Vue 2、React 和纯浏览器环境。通过 sub-path exports 实现模块隔离,主入口 `xn-fe-tools` 不包含任何 Sentry 代码,Sentry SDK 已内置打包,**无需单独安装任何 Sentry 包**。",
20
20
  "typeDefinitions": "export interface BreadcrumbsConfig {\n /** 是否记录 console 输出,默认 true */\n console?: boolean\n /** 是否记录 DOM 交互事件,默认 true */\n dom?: boolean\n /** 是否记录 fetch 请求,默认 true */\n fetch?: boolean\n /** 是否记录 XMLHttpRequest 请求,默认 true */\n xhr?: boolean\n /** 是否记录浏览器导航历史,默认 true */\n history?: boolean\n}\n\nexport interface SentryBaseOptions {\n /** Sentry DSN,必填 */\n dsn: string\n /** 运行环境,默认 'production' */\n environment?: string\n /** 是否启用 Sentry,默认 true */\n enabled?: boolean\n /** 性能追踪采样率,默认 0.1(10%) */\n tracesSampleRate?: number\n /** 会话回放采样率,默认 0.1(10%) */\n replaysSessionSampleRate?: number\n /** 错误回放采样率,默认 1.0(100%) */\n replaysOnErrorSampleRate?: number\n /**\n * 是否启用 browserTracingIntegration,默认 true\n * 简化配置参数,优先级高于 integrations 数组\n */\n enableTracing?: boolean\n /**\n * 是否启用 replayIntegration,默认 true\n * 简化配置参数,优先级高于 integrations 数组\n */\n enableReplay?: boolean\n /**\n * 面包屑配置,传入时会使用该配置创建 breadcrumbsIntegration 并覆盖默认面包屑行为\n * 简化配置参数,优先级高于 integrations 数组\n */\n breadcrumbs?: BreadcrumbsConfig\n /** 事件上报前的回调,可用于过滤或修改事件 */\n beforeSend?: (event: unknown) => unknown | null\n /** 自定义 integrations 数组,与默认 integrations 合并 */\n integrations?: unknown[]\n /** 其他 Sentry 原生 init 配置项(透传) */\n [key: string]: unknown\n}\n\nexport interface Vue3SentryOptions extends SentryBaseOptions {\n /** Vue 3 应用实例,必填 */\n app: unknown\n /** Vue Router 实例,可选;提供时配置路由级别性能追踪 */\n router?: unknown\n}\n\nexport interface Vue2SentryOptions extends SentryBaseOptions {\n /** Vue 2 构造函数,必填 */\n Vue: unknown\n /** Vue Router 实例,可选;提供时配置路由级别性能追踪 */\n router?: unknown\n}\n\nexport interface ReactSentryOptions extends SentryBaseOptions {\n /** React Router 相关配置,可选;提供时配置路由级别性能追踪 */\n reactRouter?: unknown\n}\n\nexport interface BrowserSentryOptions extends SentryBaseOptions {}\n\n\nexport interface SentrySetupReturn {\n /** Sentry 是否已成功初始化 */\n isInitialized: boolean\n /** 手动上报异常,返回 event ID */\n captureException: (error: unknown) => string | undefined\n /** 手动上报消息,返回 event ID */\n captureMessage: (message: string) => string | undefined\n /** 设置/清除当前用户信息 */\n setUser: (user: { id?: string; email?: string; username?: string } | null) => void\n}\n\nexport interface SentryViteConfig {\n /** Sentry 组织 slug */\n org: string\n /** Sentry 项目 slug */\n project: string\n /** Sentry Auth Token */\n authToken: string\n /** Source Map 配置 */\n sourcemaps?: {\n /** 构建后自动删除的文件 glob,默认 ['./dist/\\*\\*\\/*.map'] */\n filesToDeleteAfterUpload?: string[]\n }\n /** 其他自定义配置 */\n [key: string]: unknown\n}",
21
21
  "templates": {
22
- "default": ""
22
+ "default": "import { toVue3SentrySetup, replayIntegration, breadcrumbsIntegration } from 'xn-fe-tools/sentry/vue3'\n\ntoVue3SentrySetup({\n dsn: 'https://xxx@sentry.io/xxx',\n app,\n enableReplay: false, // 关闭默认 replay\n integrations: [\n replayIntegration({ maskAllText: false }), // 自定义 replay 配置\n breadcrumbsIntegration({ console: false }),\n ],\n})"
23
23
  },
24
- "docPath": "docs/hooks/toSentrySetup.md",
24
+ "docPath": "docs/tools/toSentrySetup.md",
25
25
  "rulePath": "docs/rules/toSentrySetup.mdc",
26
- "hasDoc": false,
27
- "hasRule": false
26
+ "hasDoc": true,
27
+ "hasRule": true
28
28
  },
29
29
  {
30
30
  "name": "useInputNumber",
31
- "description": "",
31
+ "description": "一个用于管理数字输入、精度控制和格式化展示的 Vue Hook。",
32
32
  "typeDefinitions": "export interface UseInputNumberOptions {\n precision?: MaybeRefOrGetter<number>; // 小数点位数\n decimalLimitMode?: MaybeRefOrGetter<'discard' | 'ceil' | 'round'>; // 处理小数位超限的方式\n autoFixed?: MaybeRefOrGetter<boolean | 'pad' | 'trim'>; // 是否自动补全/修正小数点后的位数\n max?: number; // 最大值\n min?: number; // 最小值\n allowNegative?: MaybeRefOrGetter<boolean>; // 是否允许负数\n showThousands?: MaybeRefOrGetter<boolean>; // 是否显示千分位\n}\n\nexport interface UseInputNumberReturn {\n value: Ref<string>;\n displayValue: ComputedRef<string>;\n handleInput: (val: string | number | undefined) => void;\n handleBlur: () => void;\n formatter: (val: string) => string;\n parser: (val: string) => string;\n clean: (val: string | number | undefined) => string;\n}\n\nexport function useInputNumber(options?: UseInputNumberOptions): UseInputNumberReturn;\n\nexport function useInputNumber(value: MaybeRefOrGetter<string | number | undefined>, options?: UseInputNumberOptions): UseInputNumberReturn;",
33
33
  "templates": {
34
- "default": ""
34
+ "default": "<script setup lang=\"ts\">\nimport { useInputNumber } from 'xn-fe-tools'\n\n// 基础用法:第一个参数传入初始值,第二个参数传入配置选项\nconst { value, displayValue, handleInput, handleBlur } = useInputNumber('100.50', {\n precision: 2,\n showThousands: true,\n})\n</script>\n\n<template>\n <div>\n <input\n :value=\"displayValue\"\n @input=\"e => handleInput((e.target as HTMLInputElement).value)\"\n @blur=\"handleBlur\"\n />\n <p>原始数值: {{ value }}</p>\n </div>\n</template>",
35
+ "element-plus": "<script setup lang=\"ts\">\nimport { watch } from 'vue'\nimport { useInputNumber } from 'xn-fe-tools'\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: string | number\n displayValue?: string | number\n precision?: number\n autoFixed?: boolean\n max?: number\n min?: number\n allowNegative?: boolean\n showThousands?: boolean\n }>(),\n {\n modelValue: '',\n precision: 0,\n autoFixed: true,\n showThousands: false,\n allowNegative: false,\n }\n)\n\nconst emits = defineEmits(['update:modelValue', 'update:displayValue', 'change', 'blur', 'input'])\n\n// 使用 Hook:第一个参数传 Getter,第二个参数传 props\nconst {\n value,\n displayValue,\n handleBlur: _handleBlur,\n parser,\n formatter,\n handleInput,\n} = useInputNumber(() => props.modelValue, props)\n\nwatch(value, val => {\n emits('update:modelValue', val)\n emits('change', val)\n})\n\nwatch(displayValue, val => {\n emits('update:displayValue', val)\n})\n\nconst handleBlur = () => {\n _handleBlur()\n emits('blur')\n}\n\n// 注意:在使用 v-model=\"value\" 时,el-input 会自动处理输入\n</script>\n\n<template>\n <el-input\n v-bind=\"$attrs\"\n v-model=\"value\"\n @blur=\"handleBlur\"\n @input=\"handleInput\"\n :formatter=\"formatter\"\n :parser=\"parser\"\n >\n <!-- 透传插槽 -->\n <template v-for=\"(_, name) in $slots\" #[name]=\"scope\">\n <slot :name=\"name\" v-bind=\"scope || {}\" />\n </template>\n </el-input>\n</template>",
36
+ "ant-design-vue": "<script setup lang=\"ts\">\nimport { watch } from 'vue'\nimport { Input } from 'ant-design-vue'\nimport { useInputNumber } from 'xn-fe-tools'\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: string | number\n displayValue?: string | number\n precision?: number\n autoFixed?: boolean\n max?: number\n min?: number\n allowNegative?: boolean\n showThousands?: boolean\n }>(),\n {\n modelValue: '',\n displayValue: '',\n precision: 0,\n autoFixed: true,\n showThousands: false,\n allowNegative: false,\n }\n)\n\nconst emits = defineEmits(['update:modelValue', 'update:displayValue', 'change', 'blur'])\n\nconst {\n value,\n displayValue,\n handleBlur: _handleBlur,\n handleInput: _handleInput,\n clean: _clean,\n} = useInputNumber(() => props.modelValue, props)\n\n// 同步状态输出\nwatch(value, val => {\n emits('update:modelValue', val)\n})\n\nwatch(displayValue, val => {\n emits('update:displayValue', val)\n})\n\nconst handleBlur = () => {\n _handleBlur()\n emits('blur')\n}\n\nconst handleChange = (e: any) => {\n const inputEl = e.target\n const oldRawValue = inputEl.value\n const oldCursorPos = inputEl.selectionStart || 0\n\n // 使用 hook 内部的 clean 逻辑来计算光标前真正会保留的字符数量\n // 这样就能自动处理“中间输入负号被过滤”等情况,保持计数的一致性\n const validBeforeCursor = _clean(oldRawValue.slice(0, oldCursorPos)).length\n\n // 执行 hook 清洗逻辑\n _handleInput(oldRawValue)\n\n const newVal = displayValue.value\n\n // 强制同步 DOM\n if (inputEl.value !== newVal) {\n inputEl.value = newVal\n\n // 恢复光标位置:找到第 validBeforeCursor 个有效字符在 newVal 中的位置\n let newPos = 0\n let validCount = 0\n while (validCount < validBeforeCursor && newPos < newVal.length) {\n // 这里的判断逻辑要与 clean 保持一致:只有数字、小数点、负号被视为有效字符\n if (/[\\d.-]/.test(newVal[newPos])) {\n validCount++\n }\n newPos++\n }\n\n inputEl.setSelectionRange(newPos, newPos)\n }\n\n emits('change', e)\n}\n</script>\n\n<template>\n <Input v-bind=\"$attrs\" :value=\"displayValue\" @change=\"handleChange\" @blur=\"handleBlur\">\n <!-- 透传 Ant Design Vue 的插槽 (prefix, suffix, addonBefore, addonAfter 等) -->\n <template v-for=\"(_, name) in $slots\" #[name]=\"scope\">\n <slot :name=\"name\" v-bind=\"scope || {}\" />\n </template>\n </Input>\n</template>"
35
37
  },
36
- "docPath": "docs/hooks/useInputNumber.md",
38
+ "docPath": "docs/tools/useInputNumber.md",
37
39
  "rulePath": "docs/rules/useInputNumber.mdc",
38
- "hasDoc": false,
40
+ "hasDoc": true,
39
41
  "hasRule": true
40
42
  },
41
43
  {
42
44
  "name": "useRequestCache",
43
- "description": "",
45
+ "description": "用于处理请求缓存的 Hook,支持内存缓存、持久化缓存(LocalStorage/SessionStorage)、请求去重(并发共享)和结果转换。",
44
46
  "typeDefinitions": "export type CacheStrategy = 'memory' | 'localStorage' | 'sessionStorage' | 'indexedDB'\n\nexport type CacheKeyGenerator<P> = string | ((params: P) => string)\n\nexport interface UseRequestCacheOptions<T = any, P = any, R = any> {\n /** 请求函数 */\n requestFn: (params: P) => Promise<R>\n \n /** 缓存唯一标记,用于区分不同请求。可以是字符串或生成函数 */\n cacheKey: CacheKeyGenerator<P>\n \n /** 缓存时间(毫秒),默认 5 分钟 (300000ms) */\n cacheTime?: number\n \n /** Storage 策略,默认为 'memory' */\n storage?: MaybeRefOrGetter<CacheStrategy>\n \n /** 请求参数(响应式或普通对象),用于生成动态 key */\n params?: MaybeRefOrGetter<P>\n \n /** 结果转换函数,在返回数据前进行转换 */\n transformResult?: (data: R) => T\n \n /** 是否立即执行,默认 true */\n immediate?: boolean\n\n /** 手动指定的 storage key 前缀,默认 'req_cache_' */\n storageKeyPrefix?: string\n\n /** 首次请求是否强制更新(跳过缓存),默认为 false */\n forceRefreshOnFirst?: MaybeRefOrGetter<boolean>\n\n /** IndexedDB 数据库名称,仅在 storage 为 'indexedDB' 时有效,默认 'RequestCacheDB' */\n dbName?: string\n\n /** IndexedDB 存储对象名称,仅在 storage 为 'indexedDB' 时有效,默认 'request_cache' */\n storeName?: string\n\n /** 请求成功的回调 */\n onSuccess?: (data: T) => void\n\n /** 请求失败的回调 */\n onError?: (error: any) => void\n}\n\nexport interface UseRequestCacheReturn<T> {\n data: Ref<T | undefined>\n loading: Ref<boolean>\n error: Ref<any>\n \n /** 执行请求 (优先使用缓存) */\n run: () => Promise<T | undefined>\n \n /** 刷新(忽略缓存,强制请求) */\n refresh: () => Promise<T | undefined>\n \n /** 清除指定 key 的缓存,如果不传则清除当前自动生成的 key */\n clearCache: (key?: string) => Promise<void>\n}",
45
47
  "templates": {
46
- "default": ""
48
+ "default": "<script setup lang=\"ts\">\nimport { useRequestCache } from 'xn-fe-tools'\nimport { ref, computed } from 'vue'\n\n// 模拟请求函数\nconst fetchUserInfo = async (params: { id: number }) => {\n return new Promise(resolve =>\n setTimeout(() => {\n resolve({ id: params.id, name: `User ${params.id}`, role: 'admin' })\n }, 500)\n )\n}\n\nconst userId = ref(1)\n\nconst { data, loading, run, refresh } = useRequestCache({\n // 请求函数\n requestFn: fetchUserInfo,\n // 参数 (响应式)\n params: computed(() => ({ id: userId.value })),\n // 缓存 Key (动态)\n cacheKey: params => `user-info-${params.id}`,\n // 缓存时间 5 分钟\n cacheTime: 5 * 60 * 1000,\n // 自动执行\n immediate: true,\n})\n</script>\n\n<template>\n <div>\n <div v-if=\"loading\">Loading...</div>\n <div v-else>\n <pre>{{ data }}</pre>\n </div>\n\n <button @click=\"userId++\">Next User (New Request)</button>\n <button @click=\"userId--\">Prev User (Cached?)</button>\n <button @click=\"refresh\">Force Refresh</button>\n </div>\n</template>"
47
49
  },
48
- "docPath": "docs/hooks/useRequestCache.md",
50
+ "docPath": "docs/tools/useRequestCache.md",
49
51
  "rulePath": "docs/rules/useRequestCache.mdc",
50
- "hasDoc": false,
52
+ "hasDoc": true,
51
53
  "hasRule": true
52
54
  },
53
55
  {
54
56
  "name": "useTableRequest",
55
- "description": "",
57
+ "description": "用于表格请求的 Hook,支持分页、防抖、自动/手动请求、参数变化自动刷新等功能。",
56
58
  "typeDefinitions": "export type RequestFunction<P, R> = (params: P) => Promise<R>\n\nexport interface PaginationConfig {\n /** 默认页码,默认 1 */\n defaultPage?: number\n /** 默认每页数量,默认 10 */\n defaultPageSize?: number\n /** 页码字段名,默认 'page' */\n pageKey?: string\n /** 每页数量字段名,默认 'pageSize' */\n pageSizeKey?: string\n}\n\nexport interface TransformResultData<T> {\n /** 列表数据 */\n list: T[]\n /** 总数 */\n total: number\n}\n\nexport interface UseTableRequestOptions<T = any, R = any, P = any> {\n /** 请求参数(响应式) */\n params?: MaybeRefOrGetter<P>\n /** 是否立即执行,默认 true */\n immediate?: boolean\n /** 防抖延迟(毫秒),默认 0(不防抖) */\n debounceDelay?: number\n /** 分页配置 */\n pagination?: PaginationConfig\n /** 数据列表字段名,默认空(直接使用 response),支持点号分隔的嵌套路径,如 \"data\" 或 \"data.list\" */\n dataField?: string\n /** 总数字段名,默认空(自动推断),支持点号分隔的嵌套路径,如 \"total\" 或 \"data.total\" */\n totalField?: string\n /** 当前页码字段名,默认空(不修正),支持点号分隔的嵌套路径,如 \"page\" 或 \"data.currentPage\" */\n currentPageField?: string\n /** 请求前转换参数 */\n transformParams?: (params: P, pagination: { page: number; pageSize: number }) => any\n /** 请求后转换结果,返回 { list, total } */\n transformResult?: (response: R) => TransformResultData<T>\n /** 请求成功回调 */\n onSuccess?: (data: T[], response: R) => void\n /** 请求失败回调 */\n onError?: (error: Error) => void\n /** 是否开启增量模式(滚动加载),默认 false */\n incremental?: boolean\n}\n\nexport interface PaginationState {\n /** 当前页码 */\n page: number\n /** 每页数量 */\n pageSize: number\n /** 总数 */\n total: number\n}\n\nexport interface UseTableRequestReturn<T> {\n /** 列表数据 */\n data: Ref<T[]>\n /** 加载状态 */\n loading: Ref<boolean>\n /** 错误信息 */\n error: Ref<Error | null>\n /** 分页信息 */\n pagination: PaginationState\n /** 手动触发请求 */\n run: () => Promise<void>\n /** 翻页 */\n onPageChange: (page: number) => Promise<void>\n /** 改变每页数量 */\n onSizeChange: (size: number) => Promise<void>\n /** 刷新数据(增量模式下清空数据并重置页码,普通模式下等同于 run) */\n refresh: () => Promise<void>\n}\n\nexport interface UseTableRequestGlobalConfig {\n /** 是否立即执行,默认 true */\n immediate?: boolean\n /** 防抖延迟(毫秒),默认 0(不防抖) */\n debounceDelay?: number\n /** 分页配置 */\n pagination?: PaginationConfig\n /** 数据列表字段名,默认空(直接使用 response),支持点号分隔的嵌套路径,如 \"data\" 或 \"data.list\" */\n dataField?: string\n /** 总数字段名,默认空(自动推断),支持点号分隔的嵌套路径,如 \"total\" 或 \"data.total\" */\n totalField?: string\n /** 当前页码字段名,默认空(不修正),支持点号分隔的嵌套路径,如 \"page\" 或 \"data.currentPage\" */\n currentPageField?: string\n /** 是否开启增量模式(滚动加载),默认 false */\n incremental?: boolean\n}",
57
59
  "templates": {
58
- "default": ""
60
+ "default": "<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport { useTableRequest } from 'xn-fe-tools'\n\ninterface User {\n id: number\n name: string\n}\n\ninterface ApiResponse {\n code: number\n data: {\n records: User[]\n total: number\n }\n}\n\ninterface QueryParams {\n keyword: string\n}\n\n// 模拟请求函数\nconst fetchUserList = (params: QueryParams) => {\n // 这里的 fetch 返回 Promise<ApiResponse>\n return fetch(`/api/users?${new URLSearchParams(params as any)}`).then(\n res => res.json() as Promise<ApiResponse>\n )\n}\n\nconst search = ref('')\n\nconst { data, loading, error, pagination, run, onPageChange, onSizeChange } = useTableRequest<\n User,\n ApiResponse,\n QueryParams\n>(fetchUserList, {\n params: computed(() => ({ keyword: search.value })),\n immediate: true,\n debounceDelay: 300,\n transformResult: (res: ApiResponse) => ({\n list: res.data.records,\n total: res.data.total,\n }),\n})\n</script>\n\n<template>\n <div>\n <input v-model=\"search\" placeholder=\"搜索...\" />\n\n <div v-if=\"loading\">加载中...</div>\n <div v-else-if=\"error\">{{ error.message }}</div>\n <table v-else>\n <tr v-for=\"item in data\" :key=\"item.id\">\n <td>{{ item.name }}</td>\n </tr>\n </table>\n\n <div class=\"pagination\">\n <span>共 {{ pagination.total }} 条</span>\n <button @click=\"onPageChange(pagination.page - 1)\" :disabled=\"pagination.page <= 1\">\n 上一页\n </button>\n <span>第 {{ pagination.page }} 页</span>\n <button\n @click=\"onPageChange(pagination.page + 1)\"\n :disabled=\"pagination.page * pagination.pageSize >= pagination.total\"\n >\n 下一页\n </button>\n </div>\n </div>\n</template>",
61
+ "element-plus": "<script setup lang=\"ts\">\nimport { useTableRequest } from 'xn-fe-tools'\n\nconst { data, loading, pagination, onPageChange, onSizeChange } = useTableRequest(fetchData, {\n transformResult: res => ({ list: res.data, total: res.total }),\n})\n</script>\n\n<template>\n <el-table :data=\"data\" v-loading=\"loading\">\n <!-- columns -->\n </el-table>\n\n <el-pagination\n v-model:current-page=\"pagination.page\"\n v-model:page-size=\"pagination.pageSize\"\n :total=\"pagination.total\"\n :page-sizes=\"[10, 20, 50, 100]\"\n layout=\"total, sizes, prev, pager, next\"\n />\n</template>",
62
+ "ant-design-vue": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useTableRequest } from 'xn-fe-tools'\n\nconst { data, loading, pagination, run, onPageChange, onSizeChange } = useTableRequest(fetchData, {\n // ... options\n})\n\n// 适配 Ant Design Vue 的分页对象\nconst antdPagination = computed(() => ({\n current: pagination.page,\n pageSize: pagination.pageSize,\n total: pagination.total,\n showSizeChanger: true,\n onChange: onPageChange,\n onShowSizeChange: (_: number, size: number) => onSizeChange(size),\n}))\n\nconst handleTableChange = (pag: any) => {\n onPageChange(pag.current)\n if (pag.pageSize !== pagination.pageSize) {\n onSizeChange(pag.pageSize)\n }\n}\n</script>\n\n<template>\n <a-table\n :dataSource=\"data\"\n :loading=\"loading\"\n :pagination=\"antdPagination\"\n @change=\"handleTableChange\"\n >\n <!-- columns -->\n </a-table>\n</template>"
59
63
  },
60
- "docPath": "docs/hooks/useTableRequest.md",
64
+ "docPath": "docs/tools/useTableRequest.md",
61
65
  "rulePath": "docs/rules/useTableRequest.mdc",
62
- "hasDoc": false,
66
+ "hasDoc": true,
63
67
  "hasRule": true
64
68
  }
65
69
  ]
@@ -0,0 +1 @@
1
+ export * from '../hooks/toSentrySetup/toSentrySetup'
@@ -0,0 +1 @@
1
+ export * from '../hooks/toSentrySetup/toReactSentrySetup'
@@ -0,0 +1 @@
1
+ export * from '../hooks/toSentrySetup/toVue2SentrySetup'
@@ -0,0 +1 @@
1
+ export * from '../hooks/toSentrySetup/toVue3SentrySetup'
@@ -0,0 +1,177 @@
1
+ ## toSentrySetup 使用规则
2
+
3
+ ### 🎯 核心原则
4
+
5
+ - **按框架按需引入**:根据项目框架选择对应的子路径入口,不要从主入口引入
6
+ - **初始化时机**:在应用启动最早阶段调用(`main.ts` / `index.tsx`),确保异常能被捕获
7
+ - **DSN 走环境变量**:不要在代码中硬编码 DSN,应从 `import.meta.env` 读取
8
+ - **非生产环境禁用**:通过 `enabled` 参数控制,避免开发阶段产生噪音数据
9
+
10
+ ### 📁 推荐项目结构
11
+
12
+ ```
13
+ src/
14
+ ├── utils/
15
+ │ └── sentry.ts # Sentry 初始化封装(推荐)
16
+ └── main.ts # 应用入口,调用 sentry.ts
17
+ ```
18
+
19
+ ### 🔧 初始化配置
20
+
21
+ #### Vue 3 推荐配置
22
+
23
+ ```typescript
24
+ // src/utils/sentry.ts
25
+ import { toVue3SentrySetup } from 'xn-fe-tools/sentry/vue3'
26
+ import type { App } from 'vue'
27
+ import type { Router } from 'vue-router'
28
+
29
+ export function initSentry(app: App, router: Router) {
30
+ return toVue3SentrySetup({
31
+ dsn: import.meta.env.VITE_SENTRY_DSN,
32
+ app,
33
+ router,
34
+ environment: import.meta.env.MODE,
35
+ enabled: import.meta.env.PROD,
36
+ // 简化配置(推荐方式,无需导入任何 Sentry 包)
37
+ enableTracing: true,
38
+ enableReplay: true,
39
+ breadcrumbs: { console: false },
40
+ })
41
+ }
42
+ ```
43
+
44
+ ```typescript
45
+ // src/main.ts
46
+ import { createApp } from 'vue'
47
+ import { createRouter } from 'vue-router'
48
+ import { initSentry } from './utils/sentry'
49
+ import App from './App.vue'
50
+
51
+ const app = createApp(App)
52
+ const router = createRouter({ ... })
53
+
54
+ // Sentry 必须在 app.mount 前初始化
55
+ initSentry(app, router)
56
+
57
+ app.use(router).mount('#app')
58
+ ```
59
+
60
+ #### Vue 2 推荐配置
61
+
62
+ ```typescript
63
+ // src/utils/sentry.ts
64
+ import Vue from 'vue'
65
+ import Router from 'vue-router'
66
+ import { toVue2SentrySetup } from 'xn-fe-tools/sentry/vue2'
67
+
68
+ export function initSentry(router: Router) {
69
+ return toVue2SentrySetup({
70
+ dsn: process.env.VUE_APP_SENTRY_DSN,
71
+ Vue,
72
+ router,
73
+ environment: process.env.NODE_ENV,
74
+ enabled: process.env.NODE_ENV === 'production',
75
+ })
76
+ }
77
+ ```
78
+
79
+ #### React 推荐配置
80
+
81
+ ```typescript
82
+ // src/utils/sentry.ts
83
+ import { toReactSentrySetup } from 'xn-fe-tools/sentry/react'
84
+
85
+ export const sentry = toReactSentrySetup({
86
+ dsn: import.meta.env.VITE_SENTRY_DSN,
87
+ environment: import.meta.env.MODE,
88
+ enabled: import.meta.env.PROD,
89
+ })
90
+ ```
91
+
92
+ ### 📝 使用规范
93
+
94
+ #### 1. 返回值的使用
95
+
96
+ ```typescript
97
+ const { isInitialized, captureException, captureMessage, setUser } = initSentry(app, router)
98
+
99
+ // 登录后设置用户信息
100
+ function onLogin(user: User) {
101
+ setUser({ id: String(user.id), email: user.email })
102
+ }
103
+
104
+ // 登出时清除用户信息
105
+ function onLogout() {
106
+ setUser(null)
107
+ }
108
+
109
+ // 手动上报(通常不需要,Sentry 自动捕获未处理异常)
110
+ try {
111
+ riskyOperation()
112
+ } catch (err) {
113
+ captureException(err)
114
+ }
115
+ ```
116
+
117
+ #### 2. 高级集成(需要精细控制时)
118
+
119
+ ```typescript
120
+ // 使用重新导出的集成函数,无需单独安装 @sentry/vue
121
+ import { toVue3SentrySetup, replayIntegration, breadcrumbsIntegration } from 'xn-fe-tools/sentry/vue3'
122
+
123
+ toVue3SentrySetup({
124
+ dsn: import.meta.env.VITE_SENTRY_DSN,
125
+ app,
126
+ router,
127
+ // 关闭简化配置中的 replay,改用自定义配置
128
+ enableReplay: false,
129
+ integrations: [
130
+ replayIntegration({ maskAllText: false, blockAllMedia: false }),
131
+ breadcrumbsIntegration({ console: false, dom: true }),
132
+ ],
133
+ })
134
+ ```
135
+
136
+ #### 3. Source Map 上传(vite.config.ts)
137
+
138
+ ```typescript
139
+ import { defineConfig } from 'vite'
140
+ import { sentryVitePlugin } from '@sentry/vite-plugin' // 需单独安装为 devDependency
141
+ import { getSentryViteConfig } from 'xn-fe-tools/sentry/browser'
142
+
143
+ export default defineConfig({
144
+ plugins: [
145
+ sentryVitePlugin(getSentryViteConfig({
146
+ org: 'my-org',
147
+ project: 'my-project',
148
+ authToken: process.env.SENTRY_AUTH_TOKEN,
149
+ })),
150
+ ],
151
+ build: { sourcemap: true },
152
+ })
153
+ ```
154
+
155
+ ### 🚨 常见错误
156
+
157
+ | 错误场景 | 错误写法 | 正确写法 |
158
+ |---------|---------|---------|
159
+ | 从主入口引入 | `import { toVue3SentrySetup } from 'xn-fe-tools'` | `import { toVue3SentrySetup } from 'xn-fe-tools/sentry/vue3'` |
160
+ | 硬编码 DSN | `dsn: 'https://abc@sentry.io/123'` | `dsn: import.meta.env.VITE_SENTRY_DSN` |
161
+ | 初始化时机过晚 | 在路由守卫中初始化 | 在 `main.ts` 最早位置初始化 |
162
+ | 非生产环境上报 | 未设置 `enabled` | `enabled: import.meta.env.PROD` |
163
+
164
+ ### 📋 检查清单
165
+
166
+ #### 项目初始化时
167
+
168
+ - [ ] 创建 `src/utils/sentry.ts` 统一封装初始化逻辑
169
+ - [ ] DSN 配置在 `.env.production` 中,不提交到代码库
170
+ - [ ] `enabled` 设置为仅生产环境启用
171
+ - [ ] `main.ts` 中在 `app.mount` 前调用
172
+
173
+ #### 上线前
174
+
175
+ - [ ] `vite.config.ts` 配置 Source Map 上传(需安装 `@sentry/vite-plugin`)
176
+ - [ ] 验证 Sentry 控制台能收到测试异常
177
+ - [ ] 确认 `isInitialized` 为 `true`(生产构建验证)
@@ -0,0 +1,57 @@
1
+ # xn-fe-tools
2
+
3
+ ## [toAwaitFetch](/tools/toAwaitFetch)
4
+
5
+ 强大的 HTTP 请求工具,支持链式调用与解构双模式,内置错误处理、重试、超时与全局/单次配置。
6
+
7
+ 特性速览:
8
+
9
+ - 双模式:链式 & 解构 `[data, error, success]`
10
+ - 完整错误处理:HTTP / 业务 / 网络(支持 suppressError)
11
+ - 重试与超时:可配置次数、间隔与自定义重试条件
12
+ - 多种请求/响应类型:JSON、FormData、Blob
13
+ - 全局配置:baseURL、headers、校验/转换、重试、超时
14
+
15
+ ```typescript
16
+ import toAwaitFetch from 'xn-fe-tools'
17
+
18
+ interface User {
19
+ id: number
20
+ name: string
21
+ }
22
+
23
+ const fetchInstance = createInstance()
24
+
25
+ // 解构使用
26
+ const [data, error, success] = await fetchInstance.sendGet<User[]>('/api/users')
27
+
28
+ // 链式调用
29
+ await fetchInstance
30
+ .sendGet<User[]>('/api/users')
31
+ .then(data => console.log(data))
32
+ .catchHttp(error => console.error(error))
33
+ ```
34
+
35
+ ## 其他工具
36
+
37
+ ### [toSentrySetup](/tools/toSentrySetup)
38
+
39
+ Sentry 统一初始化工具集,支持 Vue 3、Vue 2、React 和纯浏览器四个框架入口。通过 sub-path exports 实现模块隔离,主包体积不受影响,Sentry SDK 已内置打包无需额外安装。
40
+
41
+ 特性速览:
42
+
43
+ - 四个框架入口:`vue3` / `vue2` / `react` / `browser`
44
+ - 支持 Vue Router / React Router 路由级性能追踪
45
+ - 简化配置参数:`enableTracing`、`enableReplay`、`breadcrumbs`
46
+ - 返回运行时控制:`captureException`、`captureMessage`、`setUser`
47
+ - 重新导出 Sentry 集成函数,支持高级自定义
48
+
49
+ ```ts
50
+ import { toVue3SentrySetup } from 'xn-fe-tools/sentry/vue3'
51
+
52
+ const { isInitialized, captureException } = toVue3SentrySetup({
53
+ dsn: 'https://xxx@sentry.io/xxx',
54
+ app,
55
+ router,
56
+ })
57
+ ```