Files
wordpress-theme-aksrt/assets/admin/src/pages/users/components/ReportDetailDrawer.vue
T

243 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<n-drawer :show="show" :width="720" @update:show="$emit('update:show', $event)">
<n-drawer-content title="举报详情" closable>
<n-spin :show="loading">
<template v-if="detail.report">
<n-descriptions bordered :column="1" size="small" label-placement="left">
<n-descriptions-item label="举报人">
{{ detail.report.reporter?.display_name || `用户 #${detail.report.reporter?.id || 0}` }}
</n-descriptions-item>
<n-descriptions-item label="举报目标">
{{ targetTitle }}
</n-descriptions-item>
<n-descriptions-item label="目标类型">
{{ detail.report.target_type }}
</n-descriptions-item>
<n-descriptions-item label="分类">
{{ detail.report.category || '-' }}
</n-descriptions-item>
<n-descriptions-item label="当前状态">
{{ detail.report.status }}
</n-descriptions-item>
<n-descriptions-item label="提交时间">
{{ detail.report.created_at || '-' }}
</n-descriptions-item>
</n-descriptions>
<div class="aksrt-report-drawer__section">
<div class="aksrt-report-drawer__title">证据链</div>
<n-alert type="info" :show-icon="false">
{{ detail.report.reason || '暂无举报原因' }}
</n-alert>
<n-space class="aksrt-report-drawer__meta" wrap>
<n-tag size="small" round :bordered="false">
目标 ID:{{ detail.report.target_id }}
</n-tag>
<n-tag
v-if="detail.report.target?.status"
size="small"
type="warning"
round
:bordered="false"
>
目标状态:{{ detail.report.target.status }}
</n-tag>
<a
v-if="detail.report.target?.url"
:href="detail.report.target.url"
target="_blank"
rel="noreferrer"
class="aksrt-report-drawer__link"
>
打开目标内容
</a>
</n-space>
</div>
<div class="aksrt-report-drawer__section">
<div class="aksrt-report-drawer__title">处理记录</div>
<div v-if="detail.report.notes?.length" class="aksrt-report-drawer__timeline">
<div v-for="(entry, index) in detail.report.notes" :key="index" class="aksrt-report-drawer__timeline-item">
<div class="aksrt-report-drawer__timeline-head">
<strong>{{ entry.status || '-' }}</strong>
<span>{{ entry.timestamp || '-' }}</span>
</div>
<p>{{ entry.note || '无备注' }}</p>
<small>{{ entry.operator_name || '系统' }} {{ entry.effect || '' }}</small>
</div>
</div>
<n-empty v-else description="暂无处理记录" />
</div>
<div class="aksrt-report-drawer__section">
<div class="aksrt-report-drawer__title">处理工单</div>
<n-space vertical :size="12">
<n-select v-model:value="form.status" :options="detail.options?.statuses || []" />
<n-select v-model:value="form.action_taken" :options="detail.options?.actions || []" />
<n-input
v-model:value="form.note"
type="textarea"
:rows="4"
placeholder="记录处理依据、证据补充或处罚说明"
/>
<n-space>
<n-button type="primary" :loading="submitting" @click="handleSubmit">提交处理</n-button>
<n-button @click="loadDetail">刷新详情</n-button>
</n-space>
</n-space>
</div>
</template>
<n-empty v-else description="暂无举报详情" />
</n-spin>
</n-drawer-content>
</n-drawer>
</template>
<script setup>
import { computed, reactive, ref, watch } from 'vue'
import { useMessage } from 'naive-ui'
import api from '@/api'
const props = defineProps({
show: { type: Boolean, default: false },
reportId: { type: [Number, String], default: null },
})
const emit = defineEmits(['update:show', 'updated'])
const message = useMessage()
const loading = ref(false)
const submitting = ref(false)
const detail = ref({ report: null, options: { statuses: [], actions: [] } })
const form = reactive({
status: 'reviewing',
action_taken: 'none',
note: '',
})
const targetTitle = computed(() => {
const target = detail.value?.report?.target
if (!target) {
return '-'
}
return target.title || target.display_name || target.excerpt || `目标 #${target.id || 0}`
})
watch(
() => [props.show, props.reportId],
async ([visible, id]) => {
if (visible && id) {
await loadDetail()
}
if (!visible) {
detail.value = { report: null, options: { statuses: [], actions: [] } }
resetForm()
}
},
)
function resetForm() {
form.status = 'reviewing'
form.action_taken = 'none'
form.note = ''
}
async function loadDetail() {
if (!props.reportId) {
return
}
loading.value = true
try {
const res = await api.request(`/users/reports/${props.reportId}`)
detail.value = res?.data || { report: null, options: { statuses: [], actions: [] } }
form.status = detail.value?.report?.status || 'reviewing'
form.action_taken = 'none'
form.note = ''
} catch (error) {
message.error(error?.message || '加载举报详情失败')
} finally {
loading.value = false
}
}
async function handleSubmit() {
if (!props.reportId) {
return
}
submitting.value = true
try {
await api.request(`/users/reports/${props.reportId}/process`, {
method: 'POST',
data: {
status: form.status,
action_taken: form.action_taken,
note: form.note,
},
})
message.success('举报工单已处理')
await loadDetail()
emit('updated')
} catch (error) {
message.error(error?.message || '处理失败')
} finally {
submitting.value = false
}
}
</script>
<style scoped>
.aksrt-report-drawer__section {
margin-top: 20px;
}
.aksrt-report-drawer__title {
margin-bottom: 10px;
font-size: 14px;
font-weight: 600;
color: #1f2937;
}
.aksrt-report-drawer__meta {
margin-top: 12px;
}
.aksrt-report-drawer__link {
color: #2563eb;
text-decoration: none;
}
.aksrt-report-drawer__timeline {
display: grid;
gap: 12px;
}
.aksrt-report-drawer__timeline-item {
padding: 12px;
border: 1px solid #e5e7eb;
border-radius: 12px;
background: #f8fafc;
}
.aksrt-report-drawer__timeline-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 6px;
color: #374151;
}
.aksrt-report-drawer__timeline-item p {
margin: 0 0 6px;
color: #111827;
white-space: pre-wrap;
}
.aksrt-report-drawer__timeline-item small {
color: #6b7280;
}
</style>