feat: add batch content report processing
This commit is contained in:
@@ -276,5 +276,19 @@ export const ContentService = {
|
||||
reason
|
||||
}
|
||||
});
|
||||
},
|
||||
async batchProcessContentReports({ report_ids, action, content_action, reason } = {}) {
|
||||
if (!Array.isArray(report_ids) || report_ids.length === 0) {
|
||||
throw new Error('report_ids is required');
|
||||
}
|
||||
return requestJson('/super/v1/content-reports/process/batch', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
report_ids,
|
||||
action,
|
||||
content_action,
|
||||
reason
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -79,6 +79,7 @@ const reportHandledAtFrom = ref(null);
|
||||
const reportHandledAtTo = ref(null);
|
||||
const reportSortField = ref('created_at');
|
||||
const reportSortOrder = ref(-1);
|
||||
const selectedReports = ref([]);
|
||||
|
||||
const commentDeleteDialogVisible = ref(false);
|
||||
const commentDeleteLoading = ref(false);
|
||||
@@ -92,6 +93,13 @@ const reportProcessContentAction = ref('unpublish');
|
||||
const reportProcessReason = ref('');
|
||||
const reportProcessTarget = ref(null);
|
||||
|
||||
const reportBatchDialogVisible = ref(false);
|
||||
const reportBatchSubmitting = ref(false);
|
||||
const reportBatchAction = ref('approve');
|
||||
const reportBatchContentAction = ref('unpublish');
|
||||
const reportBatchReason = ref('');
|
||||
const reportBatchTargetIDs = ref([]);
|
||||
|
||||
const reviewDialogVisible = ref(false);
|
||||
const reviewSubmitting = ref(false);
|
||||
const reviewAction = ref('approve');
|
||||
@@ -176,6 +184,11 @@ function getContentID(row) {
|
||||
return Number.isFinite(id) ? id : 0;
|
||||
}
|
||||
|
||||
function getReportID(row) {
|
||||
const id = Number(row?.id ?? 0);
|
||||
return Number.isFinite(id) ? id : 0;
|
||||
}
|
||||
|
||||
function formatDate(value) {
|
||||
if (!value) return '-';
|
||||
if (String(value).startsWith('0001-01-01')) return '-';
|
||||
@@ -369,9 +382,12 @@ function getReportActionLabel(value) {
|
||||
}
|
||||
|
||||
const selectedCount = computed(() => selectedContents.value.length);
|
||||
const reportSelectedCount = computed(() => selectedReports.value.length);
|
||||
const reviewTargetCount = computed(() => reviewTargetIDs.value.length);
|
||||
const batchStatusTargetCount = computed(() => batchStatusTargetIDs.value.length);
|
||||
const reportProcessNeedsContentAction = computed(() => reportProcessAction.value === 'approve');
|
||||
const reportBatchTargetCount = computed(() => reportBatchTargetIDs.value.length);
|
||||
const reportBatchNeedsContentAction = computed(() => reportBatchAction.value === 'approve');
|
||||
|
||||
async function loadContents() {
|
||||
loading.value = true;
|
||||
@@ -632,6 +648,7 @@ async function loadReports() {
|
||||
sortOrder: reportSortOrder.value
|
||||
});
|
||||
reports.value = result.items;
|
||||
selectedReports.value = [];
|
||||
reportTotalRecords.value = result.total;
|
||||
} catch (error) {
|
||||
toast.add({ severity: 'error', summary: '加载失败', detail: error?.message || '无法加载举报列表', life: 4000 });
|
||||
@@ -699,6 +716,60 @@ async function confirmReportProcess() {
|
||||
}
|
||||
}
|
||||
|
||||
function openReportBatchDialog(action) {
|
||||
if (!selectedReports.value.length) {
|
||||
toast.add({ severity: 'warn', summary: '请先选择举报', detail: '至少选择 1 条举报进行处理', life: 3000 });
|
||||
return;
|
||||
}
|
||||
const invalid = selectedReports.value.filter((row) => row?.status !== 'pending');
|
||||
if (invalid.length) {
|
||||
toast.add({ severity: 'warn', summary: '选择包含已处理举报', detail: '仅可批量处理待处理举报', life: 3000 });
|
||||
return;
|
||||
}
|
||||
const ids = selectedReports.value.map((row) => getReportID(row)).filter((id) => id > 0);
|
||||
if (!ids.length) {
|
||||
toast.add({ severity: 'warn', summary: '选择无效', detail: '未识别到可处理的举报', life: 3000 });
|
||||
return;
|
||||
}
|
||||
reportBatchTargetIDs.value = ids;
|
||||
reportBatchAction.value = action || 'approve';
|
||||
reportBatchContentAction.value = 'unpublish';
|
||||
reportBatchReason.value = '';
|
||||
reportBatchDialogVisible.value = true;
|
||||
}
|
||||
|
||||
async function confirmReportBatchProcess() {
|
||||
const ids = reportBatchTargetIDs.value.filter((id) => id > 0);
|
||||
if (!ids.length) return;
|
||||
|
||||
const action = reportBatchAction.value;
|
||||
const contentAction = action === 'approve' ? reportBatchContentAction.value : 'ignore';
|
||||
const reason = reportBatchReason.value.trim();
|
||||
if (action === 'reject' && !reason) {
|
||||
toast.add({ severity: 'warn', summary: '请输入原因', detail: '驳回举报时需填写原因', life: 3000 });
|
||||
return;
|
||||
}
|
||||
|
||||
reportBatchSubmitting.value = true;
|
||||
try {
|
||||
await ContentService.batchProcessContentReports({
|
||||
report_ids: ids,
|
||||
action,
|
||||
content_action: contentAction,
|
||||
reason: reason || undefined
|
||||
});
|
||||
toast.add({ severity: 'success', summary: '已处理', detail: `已处理 ${ids.length} 条举报`, life: 3000 });
|
||||
reportBatchDialogVisible.value = false;
|
||||
reportBatchTargetIDs.value = [];
|
||||
selectedReports.value = [];
|
||||
await loadReports();
|
||||
} catch (error) {
|
||||
toast.add({ severity: 'error', summary: '处理失败', detail: error?.message || '无法处理举报', life: 4000 });
|
||||
} finally {
|
||||
reportBatchSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const unpublishDialogVisible = ref(false);
|
||||
const unpublishLoading = ref(false);
|
||||
const unpublishItem = ref(null);
|
||||
@@ -1072,7 +1143,12 @@ watch(
|
||||
<span class="font-medium">举报列表</span>
|
||||
<span class="text-muted-color">跨租户内容举报处理</span>
|
||||
</div>
|
||||
<Button label="刷新" icon="pi pi-refresh" severity="secondary" @click="loadReports" :disabled="reportLoading" />
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-muted-color">已选 {{ reportSelectedCount }} 条</span>
|
||||
<Button label="批量通过" icon="pi pi-check" severity="success" :disabled="reportSelectedCount === 0" @click="openReportBatchDialog('approve')" />
|
||||
<Button label="批量驳回" icon="pi pi-times" severity="danger" :disabled="reportSelectedCount === 0" @click="openReportBatchDialog('reject')" />
|
||||
<Button label="刷新" icon="pi pi-refresh" severity="secondary" @click="loadReports" :disabled="reportLoading" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
@@ -1133,6 +1209,7 @@ watch(
|
||||
<DataTable
|
||||
:value="reports"
|
||||
dataKey="id"
|
||||
v-model:selection="selectedReports"
|
||||
:loading="reportLoading"
|
||||
lazy
|
||||
:paginator="true"
|
||||
@@ -1151,6 +1228,7 @@ watch(
|
||||
scrollHeight="640px"
|
||||
responsiveLayout="scroll"
|
||||
>
|
||||
<Column selectionMode="multiple" headerStyle="width: 3rem" />
|
||||
<Column field="id" header="举报ID" sortable style="min-width: 8rem" />
|
||||
<Column header="内容" style="min-width: 20rem">
|
||||
<template #body="{ data }">
|
||||
@@ -1326,6 +1404,34 @@ watch(
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:visible="reportBatchDialogVisible" :modal="true" :style="{ width: '560px' }">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium">批量处理举报</span>
|
||||
<span class="text-muted-color">共 {{ reportBatchTargetCount }} 条</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<label class="block font-medium mb-2">处理动作</label>
|
||||
<Select v-model="reportBatchAction" :options="reportActionOptions" optionLabel="label" optionValue="value" class="w-full" />
|
||||
</div>
|
||||
<div v-if="reportBatchNeedsContentAction">
|
||||
<label class="block font-medium mb-2">内容处置</label>
|
||||
<Select v-model="reportBatchContentAction" :options="reportContentActionOptions" optionLabel="label" optionValue="value" class="w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block font-medium mb-2">处理说明</label>
|
||||
<InputText v-model="reportBatchReason" placeholder="可选,便于审计与通知" class="w-full" />
|
||||
</div>
|
||||
<div class="text-sm text-muted-color">批量处理后会记录审计,并视情况通知作者。</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button label="取消" icon="pi pi-times" text @click="reportBatchDialogVisible = false" :disabled="reportBatchSubmitting" />
|
||||
<Button label="确认处理" icon="pi pi-check" severity="success" @click="confirmReportBatchProcess" :loading="reportBatchSubmitting" :disabled="reportBatchSubmitting || (reportBatchAction === 'reject' && !reportBatchReason.trim())" />
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:visible="commentDeleteDialogVisible" :modal="true" :style="{ width: '520px' }">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
|
||||
Reference in New Issue
Block a user