From 27503bf4a7c3fafde6471320f0b2f0e268b3f04f Mon Sep 17 00:00:00 2001 From: abearxiong Date: Thu, 5 Mar 2026 21:53:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E8=87=B3=200.0.61=EF=BC=8C=E9=87=8D=E6=9E=84=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E9=80=BB=E8=BE=91=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=B5=8F=E8=A7=88=E5=99=A8=E7=BC=93=E5=AD=98=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- .../query-login/browser-cache/cache-store.ts | 123 ++++++++++++++++++ query/query-login/browser-cache/cache.ts | 29 +++++ query/query-login/login-cache.ts | 24 ++-- query/query-login/query-login-browser.ts | 2 +- query/query-login/query-login.ts | 28 +++- query/query-proxy/proxy.ts | 28 +++- query/query-proxy/router-api-proxy.ts | 4 + 8 files changed, 217 insertions(+), 23 deletions(-) create mode 100644 query/query-login/browser-cache/cache-store.ts create mode 100644 query/query-login/browser-cache/cache.ts diff --git a/package.json b/package.json index 5301496..b6af5c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kevisual/api", - "version": "0.0.60", + "version": "0.0.61", "description": "", "main": "mod.ts", "scripts": { diff --git a/query/query-login/browser-cache/cache-store.ts b/query/query-login/browser-cache/cache-store.ts new file mode 100644 index 0000000..bf81d00 --- /dev/null +++ b/query/query-login/browser-cache/cache-store.ts @@ -0,0 +1,123 @@ +import { createStore, UseStore, get, set, del, clear, keys, values, entries, update, setMany, getMany, delMany } from 'idb-keyval'; + +/** + * 缓存存储选项 + */ +export type CacheStoreOpts = { + /** + * 数据库名称 + */ + dbName?: string; + /** + * 存储空间名称 + */ + storeName?: string; +}; +export class BaseCacheStore { + store: UseStore; + constructor(opts?: CacheStoreOpts) { + this.store = createStore(opts?.dbName || 'default-db', opts?.storeName || 'cache-store'); + } + async get(key: string) { + return get(key, this.store); + } + async set(key: string, value: any) { + return set(key, value, this.store); + } + async del(key: string) { + return del(key, this.store); + } + async clear() { + return clear(this.store); + } + async keys() { + return keys(this.store); + } + async values() { + return values(this.store); + } + async entries() { + return entries(this.store); + } + async update(key: string, updater: (value: any) => any) { + return update(key, updater, this.store); + } + async setMany(entries: [string, any][]) { + return setMany(entries, this.store); + } + async getMany(keys: string[]) { + return getMany(keys, this.store); + } + async delMany(keys: string[]) { + return delMany(keys, this.store); + } +} + +/** + * 缓存存储 + */ +export class CacheStore extends BaseCacheStore { + constructor(opts?: CacheStoreOpts) { + super(opts); + } + async getData(key: string) { + const data = await this.get(key); + return data.data as T; + } + async setData(key: string, data: any) { + return this.set(key, data); + } + /** + * 获取缓存数据,并检查是否过期 + * @param key 缓存键 + * @returns 缓存数据 + */ + async getCheckData(key: string) { + const data = await this.get(key); + if (data.expireTime && data.expireTime < Date.now()) { + await super.del(key); + return null; + } + return data.data as T; + } + /** + * 设置缓存数据,并检查是否过期 + * @param key 缓存键 + * @param data 缓存数据 + * @param opts 缓存选项 + * @returns 缓存数据 + */ + async setCheckData(key: string, data: any, opts?: { expireTime?: number; updatedAt?: number }) { + const now = Date.now(); + const expireTime = now + (opts?.expireTime || 1000 * 60 * 60 * 24 * 10); + const newData = { + data, + updatedAt: opts?.updatedAt || Date.now(), + expireTime, + }; + await this.set(key, newData); + return data; + } + async checkNew(key: string, data: any): Promise { + const existing = await this.get(key); + if (!existing) { + return true; + } + if (!data?.updatedAt) { + return false; + } + const updatedAt = new Date(data.updatedAt).getTime(); + if (isNaN(updatedAt)) { + return false; + } + return updatedAt > existing.updatedAt; + } + /** + * 删除缓存数据 + * @param key 缓存键 + * @returns 缓存数据 + */ + async delCheckData(key: string) { + return this.del(key); + } +} diff --git a/query/query-login/browser-cache/cache.ts b/query/query-login/browser-cache/cache.ts new file mode 100644 index 0000000..81bf7b6 --- /dev/null +++ b/query/query-login/browser-cache/cache.ts @@ -0,0 +1,29 @@ +export { CacheStore, BaseCacheStore } from './cache-store.ts' +import { CacheStore } from './cache-store.ts' + +/** + * 一个简单的缓存类,用于存储字符串。 + * 对数据进行添加对比内容。 + */ +export class MyCache extends CacheStore { + key: string; + constructor(opts?: { key?: string }) { + const { key, ...rest } = opts || {}; + super(rest); + this.key = key || 'my-cache'; + } + async getData(key: string = this.key): Promise { + return super.getCheckData(key) as any; + } + /** + * 设置缓存数据,默认过期时间为10天 + * @param data + * @param opts + */ + async setData(data: U, opts?: { expireTime?: number, updatedAt?: number }) { + super.setCheckData(this.key, data, opts); + } + async del(): Promise { + await super.del(this.key); + } +} diff --git a/query/query-login/login-cache.ts b/query/query-login/login-cache.ts index 3501858..5a7ac8b 100644 --- a/query/query-login/login-cache.ts +++ b/query/query-login/login-cache.ts @@ -146,19 +146,21 @@ export class LoginCacheStore implements CacheStore { /** * 初始化,设置默认值 */ - async init() { + async init(): Promise { const defaultData: CacheLogin = { ...this.cacheData }; - if (this.cache.init) { - try { - const cacheData = await this.cache.init(); - this.cacheData = cacheData || defaultData; - } catch (error) { - console.log('cacheInit error', error); + return new Promise(async (resolve) => { + if (this.cache.init) { + try { + const cacheData = await this.cache.init(); + this.cacheData = cacheData || defaultData; + } catch (error) { + console.log('cacheInit error', error); + } + } else { + this.cacheData = (await this.getValue()) || defaultData; } - } else { - this.cacheData = (await this.getValue()) || defaultData; - } - return this.cacheData; + resolve(this.cacheData); + }); } /** * 设置当前用户 diff --git a/query/query-login/query-login-browser.ts b/query/query-login/query-login-browser.ts index 34dcbbd..511ab25 100644 --- a/query/query-login/query-login-browser.ts +++ b/query/query-login/query-login-browser.ts @@ -1,5 +1,5 @@ import { QueryLogin, QueryLoginOpts } from './query-login.ts'; -import { MyCache } from '@kevisual/cache'; +import { MyCache } from './browser-cache/cache.ts'; type QueryLoginNodeOptsWithoutCache = Omit; export class QueryLoginBrowser extends QueryLogin { diff --git a/query/query-login/query-login.ts b/query/query-login/query-login.ts index 6933601..bad7746 100644 --- a/query/query-login/query-login.ts +++ b/query/query-login/query-login.ts @@ -3,6 +3,7 @@ import type { Result, DataOpts } from '@kevisual/query/query'; import { LoginCacheStore, CacheStore, User } from './login-cache.ts'; import { Cache } from './login-cache.ts'; import { BaseLoad } from '@kevisual/load'; +import { EventEmitter } from 'eventemitter3' export type QueryLoginOpts = { query?: Query; isBrowser?: boolean; @@ -26,9 +27,11 @@ export class QueryLogin extends BaseQuery { */ cacheStore: CacheStore; isBrowser: boolean; - load?: boolean; storage: Storage; + load: boolean = false; + status: 'init' | 'logining' | 'loginSuccess' | 'loginError' = 'init'; onLoad?: () => void; + emitter = new EventEmitter(); constructor(opts?: QueryLoginOpts) { super({ @@ -42,14 +45,29 @@ export class QueryLogin extends BaseQuery { if (!this.storage) { throw new Error('storage is required'); } + this.cacheStore.init().then(() => { + this.onLoad?.(); + this.load = true; + this.emitter.emit('load'); + }); } setQuery(query: Query) { this.query = query; } - private async init() { - await this.cacheStore.init(); - this.load = true; - this.onLoad?.(); + async init() { + if (this.load) { + return this.cacheStore.cacheData; + } + return new Promise(async (resolve) => { + const timer = setTimeout(() => { + resolve(this.cacheStore.cacheData); + }, 1000 * 20); // 20秒超时,避免一直等待 + const listener = () => { + clearTimeout(timer); + resolve(this.cacheStore.cacheData); + } + this.emitter.once('load', listener); + }); } async post(data: any, opts?: DataOpts) { try { diff --git a/query/query-proxy/proxy.ts b/query/query-proxy/proxy.ts index 0aee068..69bee1d 100644 --- a/query/query-proxy/proxy.ts +++ b/query/query-proxy/proxy.ts @@ -4,6 +4,7 @@ import { filter } from '@kevisual/js-filter' import { EventEmitter } from 'eventemitter3'; import { initApi } from './router-api-proxy.ts'; import Fuse from 'fuse.js'; +import { cloneDeep } from 'es-toolkit'; export const RouteTypeList = ['api', 'context', 'worker', 'page'] as const; export type RouterViewItemInfo = RouterViewApi | RouterViewContext | RouterViewWorker | RouteViewPage; @@ -26,6 +27,10 @@ type RouteViewBase = { * 默认动作配置 */ action?: { path?: string; key?: string; id?: string; payload?: any;[key: string]: any }; + /** + * 本地状态,loading、active、error等 + */ + routerStatus?: 'loading' | 'active' | 'inactive' | 'error'; } export type RouterViewApi = { type: 'api', @@ -67,7 +72,7 @@ export type RouterViewWorker = { * @returns */ export const pickRouterViewData = (item: RouterViewItem) => { - const { action, response, _id, ...rest } = item; + const { action, response, _id, ...rest } = cloneDeep(item); if (rest.type === 'api') { if (rest.api) { delete rest.api.query; @@ -83,6 +88,7 @@ export const pickRouterViewData = (item: RouterViewItem) => { delete rest.context.router; } } + delete rest.routerStatus; return rest } /** @@ -98,7 +104,7 @@ export type RouteViewPage = { export type RouterViewQuery = { id: string, query: string, - title: string + title: string, } /** * 后端存储结构 @@ -143,6 +149,7 @@ export class QueryProxy { } private initRouterView(item: RouterViewItem) { + item.routerStatus = 'loading'; if (item.type === 'api' && item.api?.url) { const url = item.api.url; if (item?.api?.query) return item; @@ -245,10 +252,14 @@ export class QueryProxy { // @ts-ignore const context = globalThis['context'] || {} const router = item?.context?.router || context[item?.context?.key] as QueryRouterServer; + if (item) { + item.routerStatus = router ? 'active' : 'error'; + } if (!router) { console.warn(`未发现Context router ${item?.context?.key}`); return } + const routes = router.getList(); // TODO: args // const args = fromJSONSchema(r); @@ -308,6 +319,9 @@ export class QueryProxy { } const viewItem = item.worker; const worker = viewItem?.worker; + if (item) { + item.routerStatus = worker ? 'active' : 'error'; + } if (!worker) { console.warn('Worker not initialized'); return; @@ -377,11 +391,15 @@ export class QueryProxy { const url = item.page.url; try { if (typeof window !== 'undefined') { - await import(url).then((module) => { }).catch((err) => { - console.error('引入Page脚本失败:', url, err); - }); + await import(url) + if (item) { + item.routerStatus = 'active'; + } } } catch (e) { + if (item) { + item.routerStatus = 'error'; + } console.warn('引入Page脚本失败:', url, e); return; } diff --git a/query/query-proxy/router-api-proxy.ts b/query/query-proxy/router-api-proxy.ts index c72fb9d..9885bc7 100644 --- a/query/query-proxy/router-api-proxy.ts +++ b/query/query-proxy/router-api-proxy.ts @@ -17,12 +17,16 @@ export const initApi = async (opts: { const token = opts?.token; const query = item?.api?.query || new Query({ url: item?.api?.url || '/api/router' }) const res = await query.post<{ list: RouterItem[] }>({ path: "router", key: 'list', token: token }); + if (item) { + item.routerStatus = res?.code === 200 ? 'active' : 'error'; + } if (res.code !== 200) { return { code: res.code, message: `初始化路由失败: ${res.message}, url: ${query.url}` } } + let _list = res.data?.list || [] if (opts?.exclude) { if (opts?.exclude) {