From 7c8426651df1fbac7acfe66183808ac960c6045b Mon Sep 17 00:00:00 2001 From: xiaomai Date: Thu, 30 Apr 2026 19:33:01 +0800 Subject: [PATCH] feat: add favorite thing items to pokemon detail Fetch favorite thing items associated with a pokemon in backend Display items with category tabs in PokemonDetail view --- backend/src/queries.ts | 22 ++++++++++++-- frontend/src/services/api.ts | 1 + frontend/src/views/PokemonDetail.vue | 44 ++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/backend/src/queries.ts b/backend/src/queries.ts index 4e90867..24a2b3a 100644 --- a/backend/src/queries.ts +++ b/backend/src/queries.ts @@ -439,7 +439,7 @@ export async function getPokemon(id: number) { return null; } - const [habitats, itemDrops] = await Promise.all([ + const [habitats, itemDrops, favoriteThingItems] = await Promise.all([ query( ` SELECT @@ -468,6 +468,24 @@ export async function getPokemon(id: number) { ORDER BY psid.skill_id, i.name `, [id] + ), + query( + ` + SELECT + i.id, + i.name, + json_build_object('id', c.id, 'name', c.name) AS category, + json_agg(json_build_object('id', ft.id, 'name', ft.name) ORDER BY ft.name) AS tags + FROM pokemon_favorite_things pft + JOIN item_favorite_things ift ON ift.favorite_thing_id = pft.favorite_thing_id + JOIN favorite_things ft ON ft.id = pft.favorite_thing_id + JOIN items i ON i.id = ift.item_id + JOIN item_categories c ON c.id = i.category_id + WHERE pft.pokemon_id = $1 + GROUP BY i.id, i.name, c.id, c.name + ORDER BY c.name, i.name + `, + [id] ) ]); @@ -483,7 +501,7 @@ export async function getPokemon(id: number) { })) : []; - return { ...pokemon, skills, habitats }; + return { ...pokemon, skills, habitats, favoriteThingItems }; } function cleanPokemonPayload(payload: Record): PokemonPayload { diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 2253930..0538be3 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -33,6 +33,7 @@ export interface Pokemon extends EditInfo { export interface PokemonDetail extends Pokemon { skills: Array; + favoriteThingItems: Array; habitats: Array<{ id: number; name: string; diff --git a/frontend/src/views/PokemonDetail.vue b/frontend/src/views/PokemonDetail.vue index 79ad5e1..b790938 100644 --- a/frontend/src/views/PokemonDetail.vue +++ b/frontend/src/views/PokemonDetail.vue @@ -6,10 +6,12 @@ import EditMeta from '../components/EditMeta.vue'; import EntityChips from '../components/EntityChips.vue'; import PageHeader from '../components/PageHeader.vue'; import Skeleton from '../components/Skeleton.vue'; +import Tabs, { type TabOption } from '../components/Tabs.vue'; import { api, type PokemonDetail } from '../services/api'; const route = useRoute(); const pokemon = ref(null); +const itemCategoryTab = ref(''); const timeOfDays = ['早晨', '中午', '傍晚', '晚上']; const weathers = ['晴天', '阴天', '雨天']; @@ -75,6 +77,28 @@ const habitatRows = computed(() => { })); }); const skillDropRows = computed(() => pokemon.value?.skills.filter((skill) => skill.itemDrop) ?? []); +const itemCategoryTabs = computed(() => { + const categories = new Map(); + + pokemon.value?.favoriteThingItems.forEach((item) => { + categories.set(String(item.category.id), item.category.name); + }); + + const tabs = [...categories.entries()] + .sort(([, nameA], [, nameB]) => nameA.localeCompare(nameB)) + .map(([value, label]) => ({ value, label })); + + return tabs.length > 1 ? [{ value: '', label: '全部' }, ...tabs] : []; +}); +const favoriteThingItems = computed(() => { + const items = pokemon.value?.favoriteThingItems ?? []; + + if (!itemCategoryTab.value) { + return items; + } + + return items.filter((item) => String(item.category.id) === itemCategoryTab.value); +}); onMounted(async () => { pokemon.value = await api.pokemonDetail(String(route.params.id)); @@ -168,6 +192,26 @@ onMounted(async () => { + + +

+
+