feat(discussion): add discussion feature for game entities

Create entity_discussion_comments table and API endpoints
Add discussion tabs to Pokemon, Item, Recipe, and Habitat detail views
Support top-level comments, single-level replies, and deletion
This commit is contained in:
2026-05-02 09:54:00 +08:00
parent 7ee25e2437
commit b0d18a845d
12 changed files with 1101 additions and 0 deletions

View File

@@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import DetailSection from '../components/DetailSection.vue';
import EditHistoryPanel from '../components/EditHistoryPanel.vue';
import EntityDiscussionPanel from '../components/EntityDiscussionPanel.vue';
import EntityChips from '../components/EntityChips.vue';
import PageHeader from '../components/PageHeader.vue';
import Skeleton from '../components/Skeleton.vue';
@@ -22,6 +23,7 @@ const weathers = ['晴天', '阴天', '雨天'];
const showEditor = computed(() => route.name === 'habitat-edit');
const detailTabs = computed<TabOption[]>(() => [
{ value: 'details', label: t('common.details') },
{ value: 'discussion', label: t('discussion.title') },
{ value: 'history', label: t('history.editHistory') }
]);
@@ -229,6 +231,10 @@ watch(
</DetailSection>
</div>
<div v-else-if="detailTab === 'discussion'" class="detail-tab-panel">
<EntityDiscussionPanel entity-type="habitats" :entity-id="habitat.id" />
</div>
<div v-else class="detail-tab-panel">
<EditHistoryPanel :entity="habitat" :history="habitat.editHistory" />
</div>

View File

@@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import DetailSection from '../components/DetailSection.vue';
import EditHistoryPanel from '../components/EditHistoryPanel.vue';
import EntityDiscussionPanel from '../components/EntityDiscussionPanel.vue';
import EntityChips from '../components/EntityChips.vue';
import PageHeader from '../components/PageHeader.vue';
import Skeleton from '../components/Skeleton.vue';
@@ -20,6 +21,7 @@ const detailTab = ref('details');
const showEditor = computed(() => route.name === 'item-edit');
const detailTabs = computed<TabOption[]>(() => [
{ value: 'details', label: t('common.details') },
{ value: 'discussion', label: t('discussion.title') },
{ value: 'history', label: t('history.editHistory') }
]);
@@ -195,6 +197,10 @@ watch(
</DetailSection>
</div>
<div v-else-if="detailTab === 'discussion'" class="detail-tab-panel">
<EntityDiscussionPanel entity-type="items" :entity-id="item.id" />
</div>
<div v-else class="detail-tab-panel">
<EditHistoryPanel :entity="item" :history="item.editHistory" />
</div>

View File

@@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import DetailSection from '../components/DetailSection.vue';
import EditHistoryPanel from '../components/EditHistoryPanel.vue';
import EntityDiscussionPanel from '../components/EntityDiscussionPanel.vue';
import EntityChips from '../components/EntityChips.vue';
import PageHeader from '../components/PageHeader.vue';
import PokemonStatsPanel from '../components/PokemonStatsPanel.vue';
@@ -112,6 +113,7 @@ const skillDropRows = computed(() => pokemon.value?.skills.filter((skill) => ski
const showEditor = computed(() => route.name === 'pokemon-edit');
const detailTabs = computed<TabOption[]>(() => [
{ value: 'details', label: t('common.details') },
{ value: 'discussion', label: t('discussion.title') },
{ value: 'history', label: t('history.editHistory') }
]);
const itemCategoryTabs = computed<TabOption[]>(() => {
@@ -444,6 +446,10 @@ watch(
</DetailSection>
</div>
<div v-else-if="detailTab === 'discussion'" class="detail-tab-panel">
<EntityDiscussionPanel entity-type="pokemon" :entity-id="pokemon.id" />
</div>
<div v-else class="detail-tab-panel">
<EditHistoryPanel :entity="pokemon" :history="pokemon.editHistory" />
</div>

View File

@@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import DetailSection from '../components/DetailSection.vue';
import EditHistoryPanel from '../components/EditHistoryPanel.vue';
import EntityDiscussionPanel from '../components/EntityDiscussionPanel.vue';
import EntityChips from '../components/EntityChips.vue';
import PageHeader from '../components/PageHeader.vue';
import Skeleton from '../components/Skeleton.vue';
@@ -20,6 +21,7 @@ const detailTab = ref('details');
const showEditor = computed(() => route.name === 'recipe-edit');
const detailTabs = computed<TabOption[]>(() => [
{ value: 'details', label: t('common.details') },
{ value: 'discussion', label: t('discussion.title') },
{ value: 'history', label: t('history.editHistory') }
]);
@@ -105,6 +107,10 @@ watch(
</DetailSection>
</div>
<div v-else-if="detailTab === 'discussion'" class="detail-tab-panel">
<EntityDiscussionPanel entity-type="recipes" :entity-id="recipe.id" />
</div>
<div v-else class="detail-tab-panel">
<EditHistoryPanel :entity="recipe" :history="recipe.editHistory" />
</div>