feat(Moderation): implement moderation reports feature
- Added moderationRouter to handle moderation-related API routes. - Introduced new methods in AdminController for fetching all regions, region types, and creating regions. - Enhanced adminRouter with routes for moderation reports and status updates. - Updated navigationController to include moderation reports in the admin menu. - Implemented frontend components for reporting messages in the forum and managing moderation reports. - Added internationalization support for moderation-related texts in multiple languages.
This commit is contained in:
137
frontend/src/views/admin/ModerationReportsView.vue
Normal file
137
frontend/src/views/admin/ModerationReportsView.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div class="moderation-reports-view">
|
||||
<h2>{{ $t('admin.moderationReports.title') }}</h2>
|
||||
<p class="intro">{{ $t('admin.moderationReports.intro') }}</p>
|
||||
|
||||
<div class="filters">
|
||||
<label>
|
||||
{{ $t('admin.moderationReports.statusFilter') }}
|
||||
<select v-model="statusFilter" @change="loadReports">
|
||||
<option value="open">{{ $t('admin.moderationReports.status.open') }}</option>
|
||||
<option value="in_review">{{ $t('admin.moderationReports.status.in_review') }}</option>
|
||||
<option value="resolved">{{ $t('admin.moderationReports.status.resolved') }}</option>
|
||||
<option value="rejected">{{ $t('admin.moderationReports.status.rejected') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<button type="button" @click="loadReports">{{ $t('admin.moderationReports.reload') }}</button>
|
||||
</div>
|
||||
|
||||
<div v-if="loading">{{ $t('general.loading') }}</div>
|
||||
<div v-else-if="reports.length === 0">{{ $t('admin.moderationReports.empty') }}</div>
|
||||
<table v-else>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>{{ $t('admin.moderationReports.target') }}</th>
|
||||
<th>{{ $t('admin.moderationReports.reason') }}</th>
|
||||
<th>{{ $t('admin.moderationReports.reporter') }}</th>
|
||||
<th>{{ $t('admin.moderationReports.createdAt') }}</th>
|
||||
<th>{{ $t('admin.moderationReports.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="report in reports" :key="report.id">
|
||||
<td>#{{ report.id }}</td>
|
||||
<td>{{ report.targetType }}:{{ report.targetId }}</td>
|
||||
<td>
|
||||
<div>{{ report.reason }}</div>
|
||||
<small v-if="report.details">{{ report.details }}</small>
|
||||
</td>
|
||||
<td>{{ report.reporterUsername }}</td>
|
||||
<td>{{ formatDateTimeLong(report.createdAt) }}</td>
|
||||
<td class="actions-cell">
|
||||
<select v-model="draftStatus[report.id]">
|
||||
<option value="open">{{ $t('admin.moderationReports.status.open') }}</option>
|
||||
<option value="in_review">{{ $t('admin.moderationReports.status.in_review') }}</option>
|
||||
<option value="resolved">{{ $t('admin.moderationReports.status.resolved') }}</option>
|
||||
<option value="rejected">{{ $t('admin.moderationReports.status.rejected') }}</option>
|
||||
</select>
|
||||
<input
|
||||
v-model="draftNote[report.id]"
|
||||
type="text"
|
||||
:placeholder="$t('admin.moderationReports.notePlaceholder')"
|
||||
/>
|
||||
<button type="button" @click="applyStatus(report)">
|
||||
{{ $t('admin.moderationReports.apply') }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import { formatDateTimeLong } from '@/utils/datetime.js';
|
||||
import { showApiError, showSuccess } from '@/utils/feedback.js';
|
||||
|
||||
export default {
|
||||
name: 'ModerationReportsView',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
statusFilter: 'open',
|
||||
reports: [],
|
||||
draftStatus: {},
|
||||
draftNote: {},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadReports();
|
||||
},
|
||||
methods: {
|
||||
formatDateTimeLong,
|
||||
async loadReports() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const { data } = await apiClient.get('/api/admin/moderation/reports', {
|
||||
params: { status: this.statusFilter },
|
||||
});
|
||||
this.reports = Array.isArray(data) ? data : [];
|
||||
this.reports.forEach((report) => {
|
||||
this.draftStatus[report.id] = report.status || 'open';
|
||||
this.draftNote[report.id] = report.reviewerNote || '';
|
||||
});
|
||||
} catch (error) {
|
||||
showApiError(this, error, this.$t('admin.moderationReports.loadError'));
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async applyStatus(report) {
|
||||
try {
|
||||
await apiClient.post(`/api/admin/moderation/reports/${report.id}/status`, {
|
||||
status: this.draftStatus[report.id] || 'open',
|
||||
reviewerNote: this.draftNote[report.id] || '',
|
||||
});
|
||||
showSuccess(this, this.$t('admin.moderationReports.applySuccess'));
|
||||
await this.loadReports();
|
||||
} catch (error) {
|
||||
showApiError(this, error, this.$t('admin.moderationReports.applyError'));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.intro {
|
||||
margin-top: 0;
|
||||
margin-bottom: 12px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.actions-cell {
|
||||
display: grid;
|
||||
grid-template-columns: 9rem 1fr auto;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user