From 30245606d24ded9331b3e6a1a03169200a88fa47 Mon Sep 17 00:00:00 2001 From: abearxiong Date: Wed, 18 Feb 2026 05:28:12 +0800 Subject: [PATCH] chore: bump @kevisual/router to version 0.0.75, update QueryClient constructor, and enhance token handling in Query class --- bun.lock | 4 +-- package.json | 4 +-- src/adapter.ts | 49 ++++++++++++++------------------- src/query-api.ts | 5 +++- src/query-browser.ts | 26 +----------------- src/query.ts | 65 ++++++++++++++++++-------------------------- 6 files changed, 56 insertions(+), 97 deletions(-) diff --git a/bun.lock b/bun.lock index 97576f5..e226e4e 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "@kevisual/query", "devDependencies": { "@kevisual/code-builder": "^0.0.6", - "@kevisual/router": "^0.0.74", + "@kevisual/router": "^0.0.75", "@types/node": "^25.2.3", "es-toolkit": "^1.44.0", "typescript": "^5.9.3", @@ -18,7 +18,7 @@ "packages": { "@kevisual/code-builder": ["@kevisual/code-builder@0.0.6", "", { "bin": { "code-builder": "bin/code.js", "builder": "bin/code.js" } }, "sha512-0aqATB31/yw4k4s5/xKnfr4DKbUnx8e3Z3BmKbiXTrc+CqWiWTdlGe9bKI9dZ2Df+xNp6g11W4xM2NICNyyCCw=="], - "@kevisual/router": ["@kevisual/router@0.0.74", "", { "dependencies": { "es-toolkit": "1.44.0" } }, "sha512-J8qDsvrpf317H0Gq9YkeGwI+GS23RC0q/mYbKOia8wF33ylz+pDhBN8T1KmXx90AVBt/tMGNVJRgEhTVdTgpvA=="], + "@kevisual/router": ["@kevisual/router@0.0.75", "", { "dependencies": { "es-toolkit": "^1.44.0" } }, "sha512-WBDRKMjNYTP7ymkUUtiQwWYIcqnc+TGo3rFuRze8ovYV2UN5cQxIkIfsDbgWOdV1/v9b57gtiJvJRqWjCBWKRg=="], "@types/node": ["@types/node@25.2.3", "", { "dependencies": { "undici-types": "7.16.0" } }, "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ=="], diff --git a/package.json b/package.json index 1bec79a..72aef4f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kevisual/query", - "version": "0.0.46", + "version": "0.0.47", "type": "module", "scripts": { "build": "npm run clean && bun run bun.config.ts", @@ -19,7 +19,7 @@ "description": "", "devDependencies": { "@kevisual/code-builder": "^0.0.6", - "@kevisual/router": "^0.0.74", + "@kevisual/router": "^0.0.75", "@types/node": "^25.2.3", "typescript": "^5.9.3", "es-toolkit": "^1.44.0", diff --git a/src/adapter.ts b/src/adapter.ts index d76033e..8bf57fc 100644 --- a/src/adapter.ts +++ b/src/adapter.ts @@ -13,14 +13,6 @@ export type AdapterOpts = { body?: Record | FormData; // body 可以是对象、字符串或 FormData timeout?: number; method?: Method; - /** - * @deprecated use responseType - */ - isBlob?: boolean; // 是否返回 Blob 对象, 第一优先 - /** - * @deprecated use responseType - */ - isText?: boolean; // 是否返回文本内容, 第二优先 /** * 响应类型, * */ @@ -29,7 +21,7 @@ export type AdapterOpts = { }; export const isTextForContentType = (contentType: string | null) => { if (!contentType) return false; - const textTypes = ['text/', 'xml', 'html', 'javascript', 'css', 'csv', 'plain', 'x-www-form-urlencoded', 'md']; + const textTypes = ['text/', 'xml', 'html', 'javascript', 'css', 'csv', 'plain', 'x-www-form-urlencoded', 'md', 'json']; return textTypes.some((type) => contentType.includes(type)); }; /** @@ -42,13 +34,6 @@ export const adapter = async (opts: AdapterOpts = {}, overloadOpts?: RequestInit const controller = new AbortController(); const signal = controller.signal; const isPostFile = opts.isPostFile || false; // 是否为文件上传 - let responseType = opts.responseType || 'json'; // 响应类型 - if (opts.isBlob) { - responseType = 'blob'; - } else if (opts.isText) { - responseType = 'text'; - } - const timeout = opts.timeout || 60000 * 3; // 默认超时时间为 60s * 3 const timer = setTimeout(() => { controller.abort(); @@ -110,22 +95,32 @@ export const adapter = async (opts: AdapterOpts = {}, overloadOpts?: RequestInit .then(async (response) => { // 获取 Content-Type 头部信息 const contentType = response.headers.get('Content-Type'); - if (responseType === 'blob') { - return await response.blob(); // 直接返回 Blob 对象 - } - const isText = responseType === 'text'; const isJson = contentType && contentType.includes('application/json'); + const isSuccess = response.ok; // 判断返回的数据类型 - if (isJson && !isText) { - return await response.json(); // 解析为 JSON + if (isJson) { + const json = await response.json(); + if (json?.code) { + return json; + } + return { + code: isSuccess ? 200 : response.status, + status: response.status, + data: json, + } } else if (isTextForContentType(contentType)) { return { - code: response.status, + code: isSuccess ? 200 : response.status, status: response.status, data: await response.text(), // 直接返回文本内容 }; } else { - return response; + return { + code: isSuccess ? 200 : response.status, + status: response.status, + data: '非文本非JSON响应, 请手动处理response。', + response + }; } }) .catch((err) => { @@ -143,8 +138,4 @@ export const adapter = async (opts: AdapterOpts = {}, overloadOpts?: RequestInit .finally(() => { clearTimeout(timer); }); -}; -/** - * adapter - */ -export const queryFetch = adapter; +}; \ No newline at end of file diff --git a/src/query-api.ts b/src/query-api.ts index be954f3..94df036 100644 --- a/src/query-api.ts +++ b/src/query-api.ts @@ -121,8 +121,11 @@ export class QueryApi

} for (const [key, pos] of Object.entries(methods)) { - that[path][key] = (data?: Partial>, opts?: DataOpts) => { + that[path][key] = (data?: Partial>, opts: DataOpts = {}) => { const _pos = pick(pos, ['path', 'key', 'id']); + if (pos.metadata?.viewItem?.api?.url && !opts.url) { + opts.url = pos.metadata.viewItem.api.url; + } return that.query.post({ ..._pos, payload: data diff --git a/src/query-browser.ts b/src/query-browser.ts index a670508..c9be60a 100644 --- a/src/query-browser.ts +++ b/src/query-browser.ts @@ -19,23 +19,8 @@ type QueryOpts = { * 前端调用后端QueryRouter, 封装 beforeRequest 和 wss */ export class QueryClient extends Query { - tokenName: string; - storage: Storage; - token: string; - constructor(opts?: QueryOptions & { tokenName?: string; storage?: Storage; io?: boolean }) { + constructor(opts?: QueryOptions & { io?: boolean }) { super(opts); - this.tokenName = opts?.tokenName || 'token'; - this.storage = opts?.storage || globalThis.localStorage; - this.beforeRequest = async (opts) => { - const token = this.token || this.getToken(); - if (token) { - opts.headers = { - ...opts.headers, - Authorization: `Bearer ${token}`, - }; - } - return opts; - }; if (opts?.io) { this.createWs(); } @@ -43,15 +28,6 @@ export class QueryClient extends Query { createWs(opts?: QueryWsOpts) { this.qws = new QueryWs({ url: this.url, ...opts }); } - getToken() { - return this.storage.getItem(this.tokenName); - } - saveToken(token: string) { - this.storage.setItem(this.tokenName, token); - } - removeToken() { - this.storage.removeItem(this.tokenName); - } } // 移除默认生成的实例 // export const client = new QueryClient(); diff --git a/src/query.ts b/src/query.ts index 0d4d0f4..df0fc6e 100644 --- a/src/query.ts +++ b/src/query.ts @@ -24,6 +24,8 @@ export type QueryOptions = { headers?: Record; timeout?: number; isClient?: boolean; + tokenName?: string; + storage?: Storage; beforeRequest?: Fn; } export type Data = { @@ -46,34 +48,11 @@ export type DataOpts = Partial & { */ noStop?: boolean; }; -/** - * 设置基础响应, 设置 success 和 showError, - * success 是 code 是否等于 200 - * showError 是 如果 success 为 false 且 noMsg 为 false, 则调用 showError - * @param res 响应 - */ -export const setBaseResponse = (res: Partial void) => void; noMsg?: boolean }>) => { - res.success = res.code === 200; - /** - * 显示错误 - * @param fn 错误处理函数 - */ - res.showError = (fn?: () => void) => { - if (!res.success && !res.noMsg) { - fn?.(); - } - }; - return res as Result; -}; + export const wrapperError = ({ code, message }: { code?: number; message?: string }) => { const result = { code: code || 500, - success: false, - message: message || 'api request error', - showError: (fn?: () => void) => { - // - }, - noMsg: true, + message: message || '请求错误' }; return result; }; @@ -105,12 +84,13 @@ export class Query { stop?: boolean; // 默认不使用ws qws: QueryWs; - /** - * 默认是 /client/router或者 默认是 /api/router - */ - isClient = false; + tokenName: string; + storage: Storage; + token: string; constructor(opts?: QueryOptions) { this.adapter = opts?.adapter || adapter; + this.tokenName = opts?.tokenName || 'token'; + this.storage = opts?.storage || globalThis?.localStorage; const defaultURL = opts?.isClient ? '/client/router' : '/api/router'; this.url = opts?.url || defaultURL; this.headers = opts?.headers || { @@ -121,7 +101,7 @@ export class Query { this.beforeRequest = opts.beforeRequest; } else { this.beforeRequest = async (opts) => { - const token = globalThis?.localStorage?.getItem('token'); + const token = this.token || this.storage?.getItem?.(this.tokenName); if (token) { opts.headers = { ...opts.headers, @@ -162,7 +142,6 @@ export class Query { */ async post(body: Data & P, options?: DataOpts): Promise> { const url = options?.url || this.url; - console.log('query post', url, body, options); const { headers, adapter, beforeRequest, afterResponse, timeout, ...rest } = options || {}; const _headers = { ...this.headers, ...headers }; const _adapter = adapter || this.adapter; @@ -182,7 +161,7 @@ export class Query { if (res === false) { return wrapperError({ code: 500, - message: 'request is cancel', + message: '请求取消', // @ts-ignore req: req, }); @@ -192,14 +171,14 @@ export class Query { console.error('request beforeFn error', e, req); return wrapperError({ code: 500, - message: 'api request beforeFn error', + message: '请求在请求前处理时发生错误', // @ts-ignore req: req, }); } if (this.stop && !options?.noStop) { const that = this; - await new Promise((resolve) => { + const res = await new Promise((resolve) => { let timer = 0; const detect = setInterval(() => { if (!that.stop) { @@ -207,11 +186,21 @@ export class Query { resolve(true); } timer++; - if (timer > 30) { - console.error('request stop: timeout', req.url, timer); + if (timer > 5) { + console.error('等待请求失败:', req.url, timer); + clearInterval(detect); + resolve(false); } }, 1000); }); + if (!res) { + return wrapperError({ + code: 500, + message: '请求取消,可能是因为用户未登录或者token过期', + // @ts-ignore + req: req, + }); + } } return _adapter(req).then(async (res) => { try { @@ -225,10 +214,10 @@ export class Query { return res; } catch (e) { - console.error('request afterFn error', e, req); + console.error('请求在响应后处理时发生错误', e, req); return wrapperError({ code: 500, - message: 'api request afterFn error', + message: '请求在响应后处理时发生错误', // @ts-ignore req: req, });