feat: 完善养成管理后台功能

This commit is contained in:
richjohnrambo
2026-09-19 17:07:47 +08:00
parent f6766d3dbd
commit c7265252d8
13 changed files with 1257 additions and 61 deletions
+205 -16
View File
@@ -1,25 +1,214 @@
<template>
<div>
<div class="n-layout-page-header"><n-card :bordered="false" title="举报处理">查看物友动态、评论及用户举报并记录处理结果。</n-card></div>
<div class="n-layout-page-header"
><n-card :bordered="false" title="举报处理"
>查看物友动态、评论及用户举报并记录处理结果。</n-card
></div
>
<n-card :bordered="false" class="proCard">
<BasicForm ref="searchFormRef" @register="register" @submit="reloadTable" @reset="reloadTable" />
<BasicTable ref="actionRef" :columns="columns" :request="loadDataTable" :row-key="(row) => row.id" :actionColumn="actionColumn" :scroll-x="1650" />
<BasicForm
ref="searchFormRef"
@register="register"
@submit="reloadTable"
@reset="reloadTable"
/>
<BasicTable
ref="actionRef"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
:actionColumn="actionColumn"
:scroll-x="1650"
/>
</n-card>
<n-modal v-model:show="showHandle" preset="dialog" title="处理举报" positive-text="确认" negative-text="取消" @positive-click="submitHandle">
<n-space vertical><n-select v-model:value="handleForm.action" :options="handleOptions" /><n-input v-model:value="handleForm.handleResult" type="textarea" placeholder="办结或驳回时必须填写处理结果" /></n-space>
<n-modal
v-model:show="showHandle"
preset="dialog"
title="处理举报"
positive-text="确认"
negative-text="取消"
@positive-click="submitHandle"
>
<n-space vertical>
<n-select v-model:value="handleForm.action" :options="handleOptions" />
<n-select
v-model:value="handleForm.contentAction"
:options="contentActionOptions"
:disabled="handleForm.action !== 'resolve'"
placeholder="内容联动处置"
/>
<n-select
v-model:value="handleForm.userAction"
:options="userActionOptions"
:disabled="handleForm.action !== 'resolve'"
placeholder="用户联动处置"
/>
<n-input
v-model:value="handleForm.reason"
type="textarea"
placeholder="联动处置时必须填写原因"
/>
<n-input
v-model:value="handleForm.handleResult"
type="textarea"
placeholder="办结或驳回时必须填写处理结果"
/>
</n-space>
</n-modal>
<n-modal v-model:show="showDetail" preset="card" title="举报详情" class="detail-modal">
<n-spin :show="detailLoading">
<n-descriptions v-if="detail" bordered :column="2">
<n-descriptions-item label="举报人"
>{{ detail.reporterNickname || '-' }}({{ detail.reporterId }})</n-descriptions-item
>
<n-descriptions-item label="被举报人"
>{{ detail.reportedUserNickname || '-' }}({{
detail.reportedUserId
}})</n-descriptions-item
>
<n-descriptions-item label="对象"
>{{ detail.objectType }} / {{ detail.objectId }}</n-descriptions-item
>
<n-descriptions-item label="原因">{{ detail.reasonCode }}</n-descriptions-item>
<n-descriptions-item label="说明" :span="2">{{
detail.description || '-'
}}</n-descriptions-item>
<n-descriptions-item label="证据文件" :span="2">{{
formatEvidence(detail.evidenceFileIds)
}}</n-descriptions-item>
<n-descriptions-item v-if="detail.targetContent" label="目标内容" :span="2">{{
detail.targetContent.contentText || '-'
}}</n-descriptions-item>
</n-descriptions>
<n-divider title-placement="left">该用户近期举报记录</n-divider>
<n-data-table :columns="historyColumns" :data="detail?.history || []" :pagination="false" />
</n-spin>
</n-modal>
</div>
</template>
<script lang="ts" setup>
import { h, onMounted, reactive, ref } from 'vue';
import { useMessage } from 'naive-ui';
import { BasicTable, TableAction } from '@/components/Table'; import { BasicForm, useForm } from '@/components/Form';
import { ReportHandle, ReportList } from '@/api/cultivate/content'; import { columns, loadOptions, ReportState, schemas } from './model';
const actionRef = ref(); const searchFormRef = ref<any>({}); const showHandle = ref(false); const selected = ref<ReportState>(); const message = useMessage();
const handleForm = reactive({ action: 'processing', handleResult: '' }); const handleOptions = [{ label: '标记处理中', value: 'processing' }, { label: '确认处理', value: 'resolve' }, { label: '驳回举报', value: 'reject' }];
const [register] = useForm({ gridProps: { cols: '1 s:1 m:2 l:4' }, labelWidth: 90, schemas }); const loadDataTable = (res) => ReportList({ ...searchFormRef.value?.formModel, ...res }); const reloadTable = () => actionRef.value?.reload();
function openHandle(row: ReportState) { selected.value = row; handleForm.action = row.status === 'submitted' ? 'processing' : 'resolve'; handleForm.handleResult = ''; showHandle.value = true; }
async function submitHandle() { if (!selected.value) return false; if (handleForm.action !== 'processing' && !handleForm.handleResult.trim()) { message.warning('请填写处理结果'); return false; } await ReportHandle({ id: selected.value.id, ...handleForm }); message.success('处理成功'); reloadTable(); return true; }
const actionColumn = reactive({ width: 110, title: '操作', key: 'action', fixed: 'right', render(row: ReportState) { return h(TableAction as any, { style: 'button', actions: [{ label: '处理', onClick: () => openHandle(row), auth: ['/cultivate/reports/handle'], disabled: row.status === 'resolved' || row.status === 'rejected' }] }); } });
onMounted(loadOptions);
import { h, onMounted, reactive, ref, watch } from 'vue';
import { useMessage } from 'naive-ui';
import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, useForm } from '@/components/Form';
import { ReportHandle, ReportList, ReportView } from '@/api/cultivate/content';
import { columns, loadOptions, ReportState, schemas } from './model';
const actionRef = ref();
const searchFormRef = ref<any>({});
const showHandle = ref(false);
const showDetail = ref(false);
const detailLoading = ref(false);
const detail = ref<any>();
const selected = ref<ReportState>();
const message = useMessage();
const handleForm = reactive({
action: 'processing',
handleResult: '',
contentAction: 'none',
userAction: 'none',
reason: '',
});
const handleOptions = [
{ label: '标记处理中', value: 'processing' },
{ label: '确认处理', value: 'resolve' },
{ label: '驳回举报', value: 'reject' },
];
const contentActionOptions = [
{ label: '不处置内容', value: 'none' },
{ label: '下架被举报内容', value: 'take_down' },
];
const userActionOptions = [
{ label: '不处置用户', value: 'none' },
{ label: '冻结用户', value: 'freeze' },
{ label: '封禁用户', value: 'ban' },
];
const historyColumns = [
{ title: '举报ID', key: 'id' },
{ title: '对象类型', key: 'objectType' },
{ title: '原因', key: 'reasonCode' },
{ title: '状态', key: 'status' },
{ title: '提交时间', key: 'createdAt' },
];
watch(
() => handleForm.action,
(action) => {
if (action !== 'resolve') {
handleForm.contentAction = 'none';
handleForm.userAction = 'none';
handleForm.reason = '';
}
}
);
const [register] = useForm({ gridProps: { cols: '1 s:1 m:2 l:4' }, labelWidth: 90, schemas });
const loadDataTable = (res) => ReportList({ ...searchFormRef.value?.formModel, ...res });
const reloadTable = () => actionRef.value?.reload();
function openHandle(row: ReportState) {
selected.value = row;
Object.assign(handleForm, {
action: row.status === 'submitted' ? 'processing' : 'resolve',
handleResult: '',
contentAction: 'none',
userAction: 'none',
reason: '',
});
showHandle.value = true;
}
async function openDetail(row: ReportState) {
showDetail.value = true;
detailLoading.value = true;
try {
detail.value = await ReportView({ id: row.id });
} finally {
detailLoading.value = false;
}
}
function formatEvidence(value: any) {
if (!value) return '-';
if (Array.isArray(value)) return value.join('、');
return typeof value === 'string' ? value : JSON.stringify(value);
}
async function submitHandle() {
if (!selected.value) return false;
if (handleForm.action !== 'processing' && !handleForm.handleResult.trim()) {
message.warning('请填写处理结果');
return false;
}
if (
(handleForm.contentAction !== 'none' || handleForm.userAction !== 'none') &&
!handleForm.reason.trim()
) {
message.warning('请填写联动处置原因');
return false;
}
await ReportHandle({ id: selected.value.id, ...handleForm });
message.success('处理成功');
reloadTable();
return true;
}
const actionColumn = reactive({
width: 160,
title: '操作',
key: 'action',
fixed: 'right',
render(row: ReportState) {
return h(TableAction as any, {
style: 'button',
actions: [
{ label: '详情', onClick: () => openDetail(row), auth: ['/cultivate/reports/view'] },
{
label: '处理',
onClick: () => openHandle(row),
auth: ['/cultivate/reports/handle'],
disabled: row.status === 'resolved' || row.status === 'rejected',
},
],
});
},
});
onMounted(loadOptions);
</script>
<style scoped>
.detail-modal {
width: min(1000px, 92vw);
}
</style>