refactor(frontend): remove top-level await from useAsyncData
Transition to non-blocking data fetching to prevent navigation delays. Initial data is now applied via immediate watchers instead of blocking setup.
This commit is contained in:
@@ -48,7 +48,7 @@ type AncientArtifactListInitialData = {
|
|||||||
page: ListPage<AncientArtifact> | null;
|
page: ListPage<AncientArtifact> | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data: initialData } = await useAsyncData<AncientArtifactListInitialData>(
|
const { data: initialData } = useAsyncData<AncientArtifactListInitialData>(
|
||||||
`ancient-artifact-list-initial:${locale.value}`,
|
`ancient-artifact-list-initial:${locale.value}`,
|
||||||
async () => {
|
async () => {
|
||||||
const [optionsResult, artifactsResult] = await Promise.allSettled([
|
const [optionsResult, artifactsResult] = await Promise.allSettled([
|
||||||
@@ -68,13 +68,25 @@ const { data: initialData } = await useAsyncData<AncientArtifactListInitialData>
|
|||||||
{ default: () => ({ options: null, page: null }) }
|
{ default: () => ({ options: null, page: null }) }
|
||||||
);
|
);
|
||||||
|
|
||||||
const initialPage = initialData.value?.page ?? null;
|
const initialPageLoaded = ref(false);
|
||||||
options.value = initialData.value?.options ?? null;
|
|
||||||
artifacts.value = initialPage?.items ?? [];
|
function applyInitialData(data: AncientArtifactListInitialData | null | undefined) {
|
||||||
const initialPageLoaded = ref(initialPage !== null);
|
if (!data) return;
|
||||||
loading.value = !initialPageLoaded.value;
|
|
||||||
nextCursor.value = initialPage?.nextCursor ?? null;
|
if (!options.value && data.options) {
|
||||||
hasMoreArtifacts.value = initialPage?.hasMore ?? false;
|
options.value = data.options;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialPageLoaded.value || !data.page) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
artifacts.value = data.page.items;
|
||||||
|
nextCursor.value = data.page.nextCursor;
|
||||||
|
hasMoreArtifacts.value = data.page.hasMore;
|
||||||
|
initialPageLoaded.value = true;
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
const showEditor = computed(() => route.name === 'ancient-artifact-new');
|
const showEditor = computed(() => route.name === 'ancient-artifact-new');
|
||||||
const canCreateArtifact = computed(() => currentUser.value?.permissions.includes('items.create') === true);
|
const canCreateArtifact = computed(() => currentUser.value?.permissions.includes('items.create') === true);
|
||||||
@@ -160,6 +172,8 @@ onMounted(async () => {
|
|||||||
watch(artifactQuery, () => {
|
watch(artifactQuery, () => {
|
||||||
void loadArtifacts();
|
void loadArtifacts();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(initialData, applyInitialData, { immediate: true });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const detailTabs = computed<TabOption[]>(() => [
|
|||||||
{ value: 'history', label: t('history.editHistory') }
|
{ value: 'history', label: t('history.editHistory') }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { data: initialHabitat } = await useAsyncData<HabitatDetail | null>(
|
const { data: initialHabitat } = useAsyncData<HabitatDetail | null>(
|
||||||
`habitat-detail:${activeHabitatRouteId() ?? 'none'}:${locale.value}`,
|
`habitat-detail:${activeHabitatRouteId() ?? 'none'}:${locale.value}`,
|
||||||
async () => {
|
async () => {
|
||||||
const routeId = activeHabitatRouteId();
|
const routeId = activeHabitatRouteId();
|
||||||
@@ -51,8 +51,7 @@ const { data: initialHabitat } = await useAsyncData<HabitatDetail | null>(
|
|||||||
{ default: () => null }
|
{ default: () => null }
|
||||||
);
|
);
|
||||||
|
|
||||||
habitat.value = initialHabitat.value;
|
const initialHabitatLoaded = ref(false);
|
||||||
const initialHabitatLoaded = ref(initialHabitat.value !== null);
|
|
||||||
const habitatSeo = computed(() =>
|
const habitatSeo = computed(() =>
|
||||||
habitat.value && route.meta.editorModal !== true
|
habitat.value && route.meta.editorModal !== true
|
||||||
? resolveSeo({
|
? resolveSeo({
|
||||||
@@ -66,6 +65,13 @@ const habitatSeo = computed(() =>
|
|||||||
|
|
||||||
useHead(() => (habitatSeo.value ? resolvedSeoHead(habitatSeo.value) : {}));
|
useHead(() => (habitatSeo.value ? resolvedSeoHead(habitatSeo.value) : {}));
|
||||||
|
|
||||||
|
function applyInitialHabitat(value: HabitatDetail | null | undefined) {
|
||||||
|
if (!value || initialHabitatLoaded.value) return;
|
||||||
|
|
||||||
|
habitat.value = value;
|
||||||
|
initialHabitatLoaded.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
type PokemonRow = {
|
type PokemonRow = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -219,6 +225,8 @@ watch(
|
|||||||
void loadHabitatDetail();
|
void loadHabitatDetail();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(initialHabitat, applyInitialHabitat, { immediate: true });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const query = computed(() => ({
|
|||||||
isEventItem: props.eventOnly ? 'true' : 'false'
|
isEventItem: props.eventOnly ? 'true' : 'false'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { data: initialData } = await useAsyncData<ListPage<Habitat> | null>(
|
const { data: initialData } = useAsyncData<ListPage<Habitat> | null>(
|
||||||
`${props.eventOnly ? 'event-habitat-list-initial' : 'habitat-list-initial'}:${locale.value}`,
|
`${props.eventOnly ? 'event-habitat-list-initial' : 'habitat-list-initial'}:${locale.value}`,
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
@@ -45,12 +45,18 @@ const { data: initialData } = await useAsyncData<ListPage<Habitat> | null>(
|
|||||||
{ default: () => null }
|
{ default: () => null }
|
||||||
);
|
);
|
||||||
|
|
||||||
const initialPage = initialData.value;
|
const initialPageLoaded = ref(false);
|
||||||
habitats.value = initialPage?.items ?? [];
|
const loading = ref(true);
|
||||||
const initialPageLoaded = ref(initialPage !== null);
|
|
||||||
const loading = ref(!initialPageLoaded.value);
|
function applyInitialData(page: ListPage<Habitat> | null | undefined) {
|
||||||
nextCursor.value = initialPage?.nextCursor ?? null;
|
if (!page || initialPageLoaded.value) return;
|
||||||
hasMoreHabitats.value = initialPage?.hasMore ?? false;
|
|
||||||
|
habitats.value = page.items;
|
||||||
|
nextCursor.value = page.nextCursor;
|
||||||
|
hasMoreHabitats.value = page.hasMore;
|
||||||
|
initialPageLoaded.value = true;
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
const showEditor = computed(() => route.name === 'habitat-new' || route.name === 'event-habitat-new');
|
const showEditor = computed(() => route.name === 'habitat-new' || route.name === 'event-habitat-new');
|
||||||
const canCreateHabitat = computed(() => currentUser.value?.permissions.includes('habitats.create') === true);
|
const canCreateHabitat = computed(() => currentUser.value?.permissions.includes('habitats.create') === true);
|
||||||
@@ -134,6 +140,8 @@ onMounted(async () => {
|
|||||||
watch(query, () => {
|
watch(query, () => {
|
||||||
void loadHabitats();
|
void loadHabitats();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(initialData, applyInitialData, { immediate: true });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ const possibleTagEvidenceSections = computed(() => [
|
|||||||
{ key: 'neutral', title: t('pages.pokemon.tradingNeutral'), rows: item.value?.possibleTags?.evidence.neutral ?? [] }
|
{ key: 'neutral', title: t('pages.pokemon.tradingNeutral'), rows: item.value?.possibleTags?.evidence.neutral ?? [] }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { data: initialItem } = await useAsyncData<ItemDetail | null>(
|
const { data: initialItem } = useAsyncData<ItemDetail | null>(
|
||||||
`item-detail:${String(route.name)}:${activeItemRouteId() ?? 'none'}:${locale.value}`,
|
`item-detail:${String(route.name)}:${activeItemRouteId() ?? 'none'}:${locale.value}`,
|
||||||
async () => {
|
async () => {
|
||||||
const routeId = activeItemRouteId();
|
const routeId = activeItemRouteId();
|
||||||
@@ -91,8 +91,7 @@ const { data: initialItem } = await useAsyncData<ItemDetail | null>(
|
|||||||
{ default: () => null }
|
{ default: () => null }
|
||||||
);
|
);
|
||||||
|
|
||||||
item.value = initialItem.value;
|
const initialItemLoaded = ref(false);
|
||||||
const initialItemLoaded = ref(initialItem.value !== null);
|
|
||||||
const itemSeo = computed(() =>
|
const itemSeo = computed(() =>
|
||||||
item.value && route.meta.editorModal !== true
|
item.value && route.meta.editorModal !== true
|
||||||
? resolveSeo({
|
? resolveSeo({
|
||||||
@@ -106,6 +105,13 @@ const itemSeo = computed(() =>
|
|||||||
|
|
||||||
useHead(() => (itemSeo.value ? resolvedSeoHead(itemSeo.value) : {}));
|
useHead(() => (itemSeo.value ? resolvedSeoHead(itemSeo.value) : {}));
|
||||||
|
|
||||||
|
function applyInitialItem(value: ItemDetail | null | undefined) {
|
||||||
|
if (!value || initialItemLoaded.value) return;
|
||||||
|
|
||||||
|
item.value = value;
|
||||||
|
initialItemLoaded.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
const customization = computed(() => {
|
const customization = computed(() => {
|
||||||
if (!item.value) {
|
if (!item.value) {
|
||||||
return [];
|
return [];
|
||||||
@@ -195,6 +201,8 @@ watch(
|
|||||||
void loadItemDetail();
|
void loadItemDetail();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(initialItem, applyInitialItem, { immediate: true });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ type ItemListInitialData = {
|
|||||||
page: ListPage<Item> | null;
|
page: ListPage<Item> | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data: initialData } = await useAsyncData<ItemListInitialData>(
|
const { data: initialData } = useAsyncData<ItemListInitialData>(
|
||||||
`${props.eventOnly ? 'event-item-list-initial' : 'item-list-initial'}:${locale.value}`,
|
`${props.eventOnly ? 'event-item-list-initial' : 'item-list-initial'}:${locale.value}`,
|
||||||
async () => {
|
async () => {
|
||||||
const [optionsResult, itemsResult] = await Promise.allSettled([
|
const [optionsResult, itemsResult] = await Promise.allSettled([
|
||||||
@@ -130,13 +130,25 @@ const { data: initialData } = await useAsyncData<ItemListInitialData>(
|
|||||||
{ default: () => ({ options: null, page: null }) }
|
{ default: () => ({ options: null, page: null }) }
|
||||||
);
|
);
|
||||||
|
|
||||||
const initialPage = initialData.value?.page ?? null;
|
const initialPageLoaded = ref(false);
|
||||||
options.value = initialData.value?.options ?? null;
|
|
||||||
items.value = initialPage?.items ?? [];
|
function applyInitialData(data: ItemListInitialData | null | undefined) {
|
||||||
const initialPageLoaded = ref(initialPage !== null);
|
if (!data) return;
|
||||||
loading.value = !initialPageLoaded.value;
|
|
||||||
nextCursor.value = initialPage?.nextCursor ?? null;
|
if (!options.value && data.options) {
|
||||||
hasMoreItems.value = initialPage?.hasMore ?? false;
|
options.value = data.options;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialPageLoaded.value || !data.page) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
items.value = data.page.items;
|
||||||
|
nextCursor.value = data.page.nextCursor;
|
||||||
|
hasMoreItems.value = data.page.hasMore;
|
||||||
|
initialPageLoaded.value = true;
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
const showEditor = computed(() => route.name === 'item-new' || route.name === 'event-item-new');
|
const showEditor = computed(() => route.name === 'item-new' || route.name === 'event-item-new');
|
||||||
const canCreateItem = computed(() => currentUser.value?.permissions.includes('items.create') === true);
|
const canCreateItem = computed(() => currentUser.value?.permissions.includes('items.create') === true);
|
||||||
@@ -543,6 +555,8 @@ onBeforeUnmount(() => {
|
|||||||
watch(itemQuery, () => {
|
watch(itemQuery, () => {
|
||||||
void loadItems();
|
void loadItems();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(initialData, applyInitialData, { immediate: true });
|
||||||
watch(itemCreateDefaults, persistItemCreateDefaults, { deep: true });
|
watch(itemCreateDefaults, persistItemCreateDefaults, { deep: true });
|
||||||
watch(showEditor, () => {
|
watch(showEditor, () => {
|
||||||
closeCreateDefaultsMenu();
|
closeCreateDefaultsMenu();
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const weathers = ['晴天', '阴天', '雨天'];
|
|||||||
const relatedPokemonLimit = 6;
|
const relatedPokemonLimit = 6;
|
||||||
const pokemonDetailRouteNames = new Set(['pokemon-detail', 'pokemon-edit']);
|
const pokemonDetailRouteNames = new Set(['pokemon-detail', 'pokemon-edit']);
|
||||||
|
|
||||||
const { data: initialPokemon } = await useAsyncData<PokemonDetail | null>(
|
const { data: initialPokemon } = useAsyncData<PokemonDetail | null>(
|
||||||
`pokemon-detail:${activePokemonRouteId() ?? 'none'}:${locale.value}`,
|
`pokemon-detail:${activePokemonRouteId() ?? 'none'}:${locale.value}`,
|
||||||
async () => {
|
async () => {
|
||||||
const routeId = activePokemonRouteId();
|
const routeId = activePokemonRouteId();
|
||||||
@@ -58,9 +58,7 @@ const { data: initialPokemon } = await useAsyncData<PokemonDetail | null>(
|
|||||||
{ default: () => null }
|
{ default: () => null }
|
||||||
);
|
);
|
||||||
|
|
||||||
pokemon.value = initialPokemon.value;
|
const initialPokemonLoaded = ref(false);
|
||||||
relatedHabitatTab.value = initialPokemon.value ? habitatTabValue(initialPokemon.value.environment.id) : '';
|
|
||||||
const initialPokemonLoaded = ref(initialPokemon.value !== null);
|
|
||||||
const pokemonSeo = computed(() =>
|
const pokemonSeo = computed(() =>
|
||||||
pokemon.value && route.meta.editorModal !== true
|
pokemon.value && route.meta.editorModal !== true
|
||||||
? resolveSeo({
|
? resolveSeo({
|
||||||
@@ -74,6 +72,14 @@ const pokemonSeo = computed(() =>
|
|||||||
|
|
||||||
useHead(() => (pokemonSeo.value ? resolvedSeoHead(pokemonSeo.value) : {}));
|
useHead(() => (pokemonSeo.value ? resolvedSeoHead(pokemonSeo.value) : {}));
|
||||||
|
|
||||||
|
function applyInitialPokemon(value: PokemonDetail | null | undefined) {
|
||||||
|
if (!value || initialPokemonLoaded.value) return;
|
||||||
|
|
||||||
|
pokemon.value = value;
|
||||||
|
relatedHabitatTab.value = habitatTabValue(value.environment.id);
|
||||||
|
initialPokemonLoaded.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
type HabitatRow = {
|
type HabitatRow = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -518,6 +524,8 @@ watch(
|
|||||||
void loadPokemonDetail();
|
void loadPokemonDetail();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(initialPokemon, applyInitialPokemon, { immediate: true });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const query = computed(() => ({
|
|||||||
favoriteThingMode: favoriteThingMode.value
|
favoriteThingMode: favoriteThingMode.value
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { data: initialData } = await useAsyncData<PokemonListInitialData>(
|
const { data: initialData } = useAsyncData<PokemonListInitialData>(
|
||||||
`${props.eventOnly ? 'event-pokemon-list-initial' : 'pokemon-list-initial'}:${locale.value}`,
|
`${props.eventOnly ? 'event-pokemon-list-initial' : 'pokemon-list-initial'}:${locale.value}`,
|
||||||
async () => {
|
async () => {
|
||||||
const [optionsResult, pokemonResult] = await Promise.allSettled([
|
const [optionsResult, pokemonResult] = await Promise.allSettled([
|
||||||
@@ -65,15 +65,14 @@ const { data: initialData } = await useAsyncData<PokemonListInitialData>(
|
|||||||
{ default: () => ({ options: null, page: null }) }
|
{ default: () => ({ options: null, page: null }) }
|
||||||
);
|
);
|
||||||
|
|
||||||
const initialPage = initialData.value?.page ?? null;
|
const options = ref<Options | null>(null);
|
||||||
const options = ref<Options | null>(initialData.value?.options ?? null);
|
const pokemon = ref<Pokemon[]>([]);
|
||||||
const pokemon = ref<Pokemon[]>(initialPage?.items ?? []);
|
|
||||||
const currentUser = ref<AuthUser | null>(null);
|
const currentUser = ref<AuthUser | null>(null);
|
||||||
const initialPageLoaded = ref(initialPage !== null);
|
const initialPageLoaded = ref(false);
|
||||||
const loading = ref(!initialPageLoaded.value);
|
const loading = ref(true);
|
||||||
const loadingMore = ref(false);
|
const loadingMore = ref(false);
|
||||||
const nextCursor = ref<string | null>(initialPage?.nextCursor ?? null);
|
const nextCursor = ref<string | null>(null);
|
||||||
const hasMorePokemon = ref(initialPage?.hasMore ?? false);
|
const hasMorePokemon = ref(false);
|
||||||
const showEditor = computed(() => route.name === 'pokemon-new' || route.name === 'event-pokemon-new');
|
const showEditor = computed(() => route.name === 'pokemon-new' || route.name === 'event-pokemon-new');
|
||||||
const canCreatePokemon = computed(() => currentUser.value?.permissions.includes('pokemon.create') === true);
|
const canCreatePokemon = computed(() => currentUser.value?.permissions.includes('pokemon.create') === true);
|
||||||
const pageTitle = computed(() => t(props.eventOnly ? 'pages.eventPokemon.title' : 'pages.pokemon.title'));
|
const pageTitle = computed(() => t(props.eventOnly ? 'pages.eventPokemon.title' : 'pages.pokemon.title'));
|
||||||
@@ -82,6 +81,24 @@ const pageKicker = computed(() => t(props.eventOnly ? 'pages.eventPokemon.kicker
|
|||||||
const newPokemonPath = computed(() => (props.eventOnly ? '/event-pokemon/new' : '/pokemon/new'));
|
const newPokemonPath = computed(() => (props.eventOnly ? '/event-pokemon/new' : '/pokemon/new'));
|
||||||
const loadingListLabel = computed(() => t(props.eventOnly ? 'pages.eventPokemon.loadingList' : 'pages.pokemon.loadingList'));
|
const loadingListLabel = computed(() => t(props.eventOnly ? 'pages.eventPokemon.loadingList' : 'pages.pokemon.loadingList'));
|
||||||
|
|
||||||
|
function applyInitialData(data: PokemonListInitialData | null | undefined) {
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
if (!options.value && data.options) {
|
||||||
|
options.value = data.options;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialPageLoaded.value || !data.page) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pokemon.value = data.page.items;
|
||||||
|
nextCursor.value = data.page.nextCursor;
|
||||||
|
hasMorePokemon.value = data.page.hasMore;
|
||||||
|
initialPageLoaded.value = true;
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
async function loadPokemon(reset = true) {
|
async function loadPokemon(reset = true) {
|
||||||
if (!reset && (loading.value || loadingMore.value || !hasMorePokemon.value)) {
|
if (!reset && (loading.value || loadingMore.value || !hasMorePokemon.value)) {
|
||||||
return;
|
return;
|
||||||
@@ -163,6 +180,8 @@ onMounted(async () => {
|
|||||||
watch(query, () => {
|
watch(query, () => {
|
||||||
void loadPokemon();
|
void loadPokemon();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(initialData, applyInitialData, { immediate: true });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ const recipeSubtitle = computed(() => {
|
|||||||
return categoryName ?? t('pages.recipes.detailSubtitle');
|
return categoryName ?? t('pages.recipes.detailSubtitle');
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: initialRecipe } = await useAsyncData<RecipeDetail | null>(
|
const { data: initialRecipe } = useAsyncData<RecipeDetail | null>(
|
||||||
`recipe-detail:${String(route.params.id)}:${locale.value}`,
|
`recipe-detail:${String(route.params.id)}:${locale.value}`,
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
@@ -54,8 +54,7 @@ const { data: initialRecipe } = await useAsyncData<RecipeDetail | null>(
|
|||||||
{ default: () => null }
|
{ default: () => null }
|
||||||
);
|
);
|
||||||
|
|
||||||
recipe.value = initialRecipe.value;
|
const initialRecipeLoaded = ref(false);
|
||||||
const initialRecipeLoaded = ref(initialRecipe.value !== null);
|
|
||||||
const recipeSeo = computed(() =>
|
const recipeSeo = computed(() =>
|
||||||
recipe.value && route.meta.editorModal !== true
|
recipe.value && route.meta.editorModal !== true
|
||||||
? resolveSeo({
|
? resolveSeo({
|
||||||
@@ -69,6 +68,13 @@ const recipeSeo = computed(() =>
|
|||||||
|
|
||||||
useHead(() => (recipeSeo.value ? resolvedSeoHead(recipeSeo.value) : {}));
|
useHead(() => (recipeSeo.value ? resolvedSeoHead(recipeSeo.value) : {}));
|
||||||
|
|
||||||
|
function applyInitialRecipe(value: RecipeDetail | null | undefined) {
|
||||||
|
if (!value || initialRecipeLoaded.value) return;
|
||||||
|
|
||||||
|
recipe.value = value;
|
||||||
|
initialRecipeLoaded.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
async function loadRecipeDetail() {
|
async function loadRecipeDetail() {
|
||||||
try {
|
try {
|
||||||
const nextRecipe = await api.recipeDetail(String(route.params.id));
|
const nextRecipe = await api.recipeDetail(String(route.params.id));
|
||||||
@@ -119,6 +125,8 @@ watch(
|
|||||||
void loadRecipeDetail();
|
void loadRecipeDetail();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(initialRecipe, applyInitialRecipe, { immediate: true });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ type RecipeListInitialData = {
|
|||||||
page: ListPage<Item> | null;
|
page: ListPage<Item> | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data: initialData } = await useAsyncData<RecipeListInitialData>(
|
const { data: initialData } = useAsyncData<RecipeListInitialData>(
|
||||||
`recipe-list-initial:${locale.value}`,
|
`recipe-list-initial:${locale.value}`,
|
||||||
async () => {
|
async () => {
|
||||||
const [optionsResult, itemsResult] = await Promise.allSettled([
|
const [optionsResult, itemsResult] = await Promise.allSettled([
|
||||||
@@ -72,13 +72,25 @@ const { data: initialData } = await useAsyncData<RecipeListInitialData>(
|
|||||||
{ default: () => ({ options: null, page: null }) }
|
{ default: () => ({ options: null, page: null }) }
|
||||||
);
|
);
|
||||||
|
|
||||||
const initialPage = initialData.value?.page ?? null;
|
const initialPageLoaded = ref(false);
|
||||||
options.value = initialData.value?.options ?? null;
|
|
||||||
items.value = initialPage?.items ?? [];
|
function applyInitialData(data: RecipeListInitialData | null | undefined) {
|
||||||
const initialPageLoaded = ref(initialPage !== null);
|
if (!data) return;
|
||||||
loading.value = !initialPageLoaded.value;
|
|
||||||
nextCursor.value = initialPage?.nextCursor ?? null;
|
if (!options.value && data.options) {
|
||||||
hasMoreItems.value = initialPage?.hasMore ?? false;
|
options.value = data.options;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialPageLoaded.value || !data.page) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
items.value = data.page.items;
|
||||||
|
nextCursor.value = data.page.nextCursor;
|
||||||
|
hasMoreItems.value = data.page.hasMore;
|
||||||
|
initialPageLoaded.value = true;
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
const showEditor = computed(() => route.name === 'recipe-new');
|
const showEditor = computed(() => route.name === 'recipe-new');
|
||||||
const canCreateRecipe = computed(() => currentUser.value?.permissions.includes('recipes.create') === true);
|
const canCreateRecipe = computed(() => currentUser.value?.permissions.includes('recipes.create') === true);
|
||||||
@@ -180,6 +192,8 @@ onMounted(async () => {
|
|||||||
watch(itemQuery, () => {
|
watch(itemQuery, () => {
|
||||||
void loadItems();
|
void loadItems();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(initialData, applyInitialData, { immediate: true });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
Reference in New Issue
Block a user