243 lines
7.0 KiB
Vue
243 lines
7.0 KiB
Vue
<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>
|