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
This commit is contained in:
@@ -439,7 +439,7 @@ export async function getPokemon(id: number) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [habitats, itemDrops] = await Promise.all([
|
const [habitats, itemDrops, favoriteThingItems] = await Promise.all([
|
||||||
query(
|
query(
|
||||||
`
|
`
|
||||||
SELECT
|
SELECT
|
||||||
@@ -468,6 +468,24 @@ export async function getPokemon(id: number) {
|
|||||||
ORDER BY psid.skill_id, i.name
|
ORDER BY psid.skill_id, i.name
|
||||||
`,
|
`,
|
||||||
[id]
|
[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<string, unknown>): PokemonPayload {
|
function cleanPokemonPayload(payload: Record<string, unknown>): PokemonPayload {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export interface Pokemon extends EditInfo {
|
|||||||
|
|
||||||
export interface PokemonDetail extends Pokemon {
|
export interface PokemonDetail extends Pokemon {
|
||||||
skills: Array<Skill & { itemDrop: NamedEntity | null }>;
|
skills: Array<Skill & { itemDrop: NamedEntity | null }>;
|
||||||
|
favoriteThingItems: Array<NamedEntity & { category: NamedEntity; tags: NamedEntity[] }>;
|
||||||
habitats: Array<{
|
habitats: Array<{
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import EditMeta from '../components/EditMeta.vue';
|
|||||||
import EntityChips from '../components/EntityChips.vue';
|
import EntityChips from '../components/EntityChips.vue';
|
||||||
import PageHeader from '../components/PageHeader.vue';
|
import PageHeader from '../components/PageHeader.vue';
|
||||||
import Skeleton from '../components/Skeleton.vue';
|
import Skeleton from '../components/Skeleton.vue';
|
||||||
|
import Tabs, { type TabOption } from '../components/Tabs.vue';
|
||||||
import { api, type PokemonDetail } from '../services/api';
|
import { api, type PokemonDetail } from '../services/api';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const pokemon = ref<PokemonDetail | null>(null);
|
const pokemon = ref<PokemonDetail | null>(null);
|
||||||
|
const itemCategoryTab = ref('');
|
||||||
const timeOfDays = ['早晨', '中午', '傍晚', '晚上'];
|
const timeOfDays = ['早晨', '中午', '傍晚', '晚上'];
|
||||||
const weathers = ['晴天', '阴天', '雨天'];
|
const weathers = ['晴天', '阴天', '雨天'];
|
||||||
|
|
||||||
@@ -75,6 +77,28 @@ const habitatRows = computed<HabitatRow[]>(() => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
const skillDropRows = computed(() => pokemon.value?.skills.filter((skill) => skill.itemDrop) ?? []);
|
const skillDropRows = computed(() => pokemon.value?.skills.filter((skill) => skill.itemDrop) ?? []);
|
||||||
|
const itemCategoryTabs = computed<TabOption[]>(() => {
|
||||||
|
const categories = new Map<string, string>();
|
||||||
|
|
||||||
|
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 () => {
|
onMounted(async () => {
|
||||||
pokemon.value = await api.pokemonDetail(String(route.params.id));
|
pokemon.value = await api.pokemonDetail(String(route.params.id));
|
||||||
@@ -168,6 +192,26 @@ onMounted(async () => {
|
|||||||
<EntityChips :items="pokemon.favorite_things" />
|
<EntityChips :items="pokemon.favorite_things" />
|
||||||
</DetailSection>
|
</DetailSection>
|
||||||
|
|
||||||
|
<DetailSection title="喜欢的东西关联物品">
|
||||||
|
<template v-if="pokemon.favoriteThingItems.length">
|
||||||
|
<Tabs
|
||||||
|
v-if="itemCategoryTabs.length"
|
||||||
|
id="pokemon-favorite-items"
|
||||||
|
v-model="itemCategoryTab"
|
||||||
|
:tabs="itemCategoryTabs"
|
||||||
|
label="关联物品分类"
|
||||||
|
/>
|
||||||
|
<ul v-if="favoriteThingItems.length" class="row-list">
|
||||||
|
<li v-for="item in favoriteThingItems" :key="item.id">
|
||||||
|
<RouterLink :to="`/items/${item.id}`">{{ item.name }}</RouterLink>
|
||||||
|
<EntityChips :items="item.tags" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p v-else class="meta-line">无</p>
|
||||||
|
</template>
|
||||||
|
<p v-else class="meta-line">无</p>
|
||||||
|
</DetailSection>
|
||||||
|
|
||||||
<DetailSection title="栖息地">
|
<DetailSection title="栖息地">
|
||||||
<ul class="row-list appearance-list">
|
<ul class="row-list appearance-list">
|
||||||
<li v-for="habitat in habitatRows" :key="`${habitat.id}-${habitat.rarity}`">
|
<li v-for="habitat in habitatRows" :key="`${habitat.id}-${habitat.rarity}`">
|
||||||
|
|||||||
Reference in New Issue
Block a user