/** * 列表共用加载器:分页、同请求去重、身份隔离、初次加载回显缓存与刷新失败保留旧结果。 * 缓存来自统一 auth 模块;旧查询/旧账号/已卸载组件的响应不能覆盖当前列表。 */ import { onMounted } from "vue"; import { watch } from "vue"; import { onScopeDispose } from "vue"; import { ref } from "vue"; import { apiCacheKey } from "~/lib/auth.js"; import { apiCacheVersion } from "~/lib/auth.js"; import { useApi } from "~/lib/auth.js"; import { writeApiCache } from "~/lib/auth.js"; import { readApiCache } from "~/lib/auth.js"; import { clearApiCache } from "~/lib/auth.js"; /** u 为接口路径,d 可提供查询参数、外部 loader、行键和缓存命名空间等现有列表选项。 */ function u(u, d = {}) { let f = useApi(), p = d.identity ?? (typeof f.identity == `function` ? () => f.identity() : void 0), m = d.retainOnRefreshError !== !1, h = ref([]), g = ref(0), _ = ref(!1), v = ref(``), y = ref(!1), b = ref(1), x = ref(d.limit ?? 10), S = 0, C = ``, w = !0, T = !1, E = null; // 身份变更或查询构造失败时统一作废在途序号,清理可见数据,防止跨账号残留。 function D() { (S++, (h.value = []), (g.value = 0), (_.value = !1), (v.value = ``), (y.value = !1), (C = ``), (E = null), (w = !1)); } (p && watch( p, () => { (clearApiCache(), D()); }, { flush: `sync`, }, ), onScopeDispose(() => { ((T = !0), S++, (E = null)); })); // 优先使用业务行键,否则使用 id;没有稳定键的行不做对象复用。 function O(e) { if (d.rowKey) return d.rowKey(e); if ( e && typeof e == `object` && `id` in e && (typeof e.id == `string` || typeof e.id == `number`) ) return e.id; } /** 构造查询快照、读取一次初始缓存,再发真实请求刷新;同参数同身份共享 Promise。 */ function k() { if (T) return Promise.resolve(); let e, t, n = p?.(); try { ((e = { page: b.value, limit: x.value, ...(d.params?.() ?? {}), }), (t = apiCacheKey( u, e, typeof d.snapshotKey == `function` ? d.snapshotKey() : d.snapshotKey, ))); } catch (e) { return ( D(), (v.value = e instanceof Error ? e.message : `查询条件无效`), Promise.resolve() ); } if (E?.query === t && E.identity === n) return E.promise; let r = ++S, o = () => !T && r === S && (!p || p() === n); ((_.value = !0), (v.value = ``)); let l = y.value && t === C, k = p ? readApiCache(n, t) : void 0, A = apiCacheVersion(); (l || ((h.value = []), (g.value = 0), (y.value = !1)), w && k && ((h.value = k.list), (g.value = k.count), (y.value = !0), (C = t), (l = !0)), (w = !1)); let j = (async () => { try { let r = d.loader ? await d.loader(e) : await f.get(u, e); if (!o()) return; // 复用同 id 的行对象以保留 UI 引用,但删除新响应已不存在的字段,避免旧值残留。 let i = new Map( h.value.flatMap((e) => { let t = O(e); return t === void 0 ? [] : [[t, e]]; }), ); ((h.value = (r.list ?? []).map((e) => { let t = O(e), n = t === void 0 ? void 0 : i.get(t); if (!n || typeof n != `object` || typeof e != `object` || !e) return e; let r = n; for (let t of Object.keys(r)) Object.prototype.hasOwnProperty.call(e, t) || delete r[t]; return (Object.assign(r, e), n); })), (g.value = r.count ?? 0), (y.value = !0), (C = t), p && writeApiCache( n, t, { list: h.value, count: g.value, }, A, )); } catch (e) { // 只有同一查询已拥有结果时才可保留旧列表;新筛选失败不能继续展示上一查询。 if (!o()) return; ((v.value = e instanceof Error ? e.message : `加载失败`), (!m || !l) && ((h.value = []), (g.value = 0), (y.value = !1))); } finally { (o() && (_.value = !1), r === S && (E = null)); } })(); return ( o() && _.value && (E = { query: t, identity: n, promise: j, }), j ); } // PrimeVue 页码从 0 开始,后端接口从 1 开始;切筛选的 reload 则回到第一页。 function A(e) { ((b.value = e.page + 1), (x.value = e.rows), k()); } function j() { ((b.value = 1), k()); } return ( d.immediate !== !1 && onMounted(k), { list: h, total: g, loading: _, error: v, hasResult: y, page: b, limit: x, load: k, onPage: A, reload: j, } ); } export { u as t };