188 lines
5.1 KiB
JavaScript
188 lines
5.1 KiB
JavaScript
/**
|
|
* 列表页面共用加载器:分页、同请求去重、身份隔离、缓存首屏与刷新失败保留旧结果。
|
|
* 缓存来自统一 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 };
|