// frontend/composables/usePreload.js // Vue 3 组合式 API:包装 core.get(),暴露响应式 state { data, loading, error, refresh } import { ref, getCurrentInstance, onBeforeUnmount, watch } from 'vue' import { get, refresh as coreRefresh, abortRequest } from '@/utils/preloadApi/core' /** * @param {string|Ref} key - 逻辑 key * @param {object|Ref} [params] - 请求参数 * @returns {{ data: Ref, loading: Ref, error: Ref, refresh: Function }} * * @example * const { data, loading, error, refresh } = usePreload('asset.detail', { id: 123 }) * // With reactive params: * const { data, loading, error } = usePreload('asset.detail', () => ({ id: route.params.id })) */ export function usePreload(key, params) { // 校验上下文 if (!getCurrentInstance()) { console.warn('[preload] usePreload must be called in setup()') } const data = ref(null) const loading = ref(true) const error = ref(null) let mounted = true let fetchVersion = 0 /** * 执行获取(不阻塞 setup) * @param {boolean} [force=false] - 跳过 TTL 缓存 */ function doFetch(force = false) { // 先清理上一轮 in-flight 请求 abortRequest(key, params) const version = ++fetchVersion loading.value = true error.value = null // 用 .then() 异步更新 data,不阻塞 setup const promise = force ? coreRefresh(key, params, true) : get(key, params) promise .then((result) => { if (!mounted || version !== fetchVersion) return data.value = result loading.value = false }) .catch((err) => { if (!mounted || version !== fetchVersion) return error.value = err loading.value = false }) return promise } // 监听 params 变化(key 通常是静态字符串,params 动态时传入 getter) // 普通值包装为 getter 确保 Vue watch source 合法 const resolvedParams = typeof params === 'function' ? params : () => params watch( resolvedParams, () => { if (mounted) doFetch() }, { deep: true } ) // 初始加载 doFetch() // 组件卸载时清理 onBeforeUnmount(() => { mounted = false abortRequest(key, params) }) /** * 手动刷新 * @param {boolean} [force=false] - 跳过 TTL */ function refresh(force = false) { return doFetch(force) } return { data, loading, error, refresh } }