Files
cultivation-world-simulator/web/src/stores/ui.ts
bridge 4abc639aa6
Some checks failed
Tests / test (push) Failing after 10m52s
feat: shallow ref vue
2026-02-06 22:51:58 +08:00

96 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { defineStore } from 'pinia';
import { ref, shallowRef } from 'vue';
import { avatarApi } from '../api';
import type { AvatarDetail, RegionDetail, SectDetail } from '../types/core';
export type SelectionType = 'avatar' | 'region' | 'sect';
export interface Selection {
type: SelectionType;
id: string;
}
export const useUiStore = defineStore('ui', () => {
// --- Selection & Panels ---
const selectedTarget = ref<Selection | null>(null);
// 详情数据 (可能为空,或正在加载)
// 使用 shallowRef 避免深层响应式转换带来的性能开销 (对于大型嵌套对象,如 AvatarDetail)
const detailData = shallowRef<AvatarDetail | RegionDetail | SectDetail | null>(null);
const isLoadingDetail = ref(false);
const detailError = ref<string | null>(null);
// 请求计数器,用于处理竞态条件。
let detailRequestId = 0;
// --- Actions ---
async function select(type: SelectionType, id: string) {
if (selectedTarget.value?.type === type && selectedTarget.value?.id === id) {
return; // Already selected
}
selectedTarget.value = { type, id };
detailData.value = null; // Reset current data
await refreshDetail();
}
function clearSelection() {
selectedTarget.value = null;
detailData.value = null;
detailError.value = null;
}
function clearHoverCache() {
// 清除详情缓存,强制下次选择时重新加载。
detailData.value = null;
}
async function refreshDetail() {
if (!selectedTarget.value) return;
// 每次请求增加计数器,只接受最新请求的响应。
const currentRequestId = ++detailRequestId;
const target = { ...selectedTarget.value };
isLoadingDetail.value = true;
detailError.value = null;
// 检查是否应该接受响应requestId 匹配且 target 未变化。
const shouldAcceptResponse = () =>
currentRequestId === detailRequestId &&
selectedTarget.value?.type === target.type &&
selectedTarget.value?.id === target.id;
try {
const data = await avatarApi.fetchDetailInfo(target);
if (shouldAcceptResponse()) {
detailData.value = data as any;
}
} catch (e) {
if (shouldAcceptResponse()) {
detailError.value = e instanceof Error ? e.message : 'Failed to load detail';
}
} finally {
if (shouldAcceptResponse()) {
isLoadingDetail.value = false;
}
}
}
return {
selectedTarget,
detailData,
isLoadingDetail,
detailError,
select,
clearSelection,
clearHoverCache,
refreshDetail
};
});