feat(filex): add admin event monitoring
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useState } from 'react'
|
||||
import type { ColumnDef } from '@tanstack/react-table'
|
||||
import type { FileXAdminEvent } from '@/api/types'
|
||||
import { fetchFileXEvent, fetchFileXEvents } from '@/api/modules/filex'
|
||||
import { PERM, requirePermission } from '@/auth/permissions'
|
||||
import { DataTable } from '@/components/data-table/data-table'
|
||||
import { PageHeader } from '@/components/page-header'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { DetailSheet } from '@/features/studio/detail-sheet'
|
||||
import { useListPage } from '@/lib/list-page'
|
||||
|
||||
export const Route = createFileRoute('/_authed/filex-events')({
|
||||
beforeLoad: () => requirePermission(PERM.FILEX_READ),
|
||||
component: FileXEventsPage,
|
||||
})
|
||||
|
||||
const VISIBILITY_LABELS: Record<string, string> = {
|
||||
public: '公开',
|
||||
friends: '好友',
|
||||
private: '私密',
|
||||
}
|
||||
|
||||
function formatTime(value?: string | null) {
|
||||
return value ? new Date(value).toLocaleString('zh-CN') : '—'
|
||||
}
|
||||
|
||||
function FileXEventsPage() {
|
||||
const [status, setStatus] = useState('all')
|
||||
const [visibility, setVisibility] = useState('all')
|
||||
const [query, setQuery] = useState('')
|
||||
const [topic, setTopic] = useState('')
|
||||
const [authorUid, setAuthorUid] = useState('')
|
||||
const [detailId, setDetailId] = useState<number | null>(null)
|
||||
const filters: Record<string, string> = {}
|
||||
if (status !== 'all') filters.status = status
|
||||
if (visibility !== 'all') filters.visibility = visibility
|
||||
if (query.trim()) filters.q = query.trim()
|
||||
if (topic.trim()) filters.primary_topic = topic.trim()
|
||||
if (authorUid.trim()) filters.user_uid = authorUid.trim()
|
||||
|
||||
const list = useListPage('filex-events', fetchFileXEvents, filters)
|
||||
const detail = useQuery({
|
||||
queryKey: ['filex-event', detailId],
|
||||
queryFn: () => fetchFileXEvent(detailId as number),
|
||||
enabled: detailId !== null,
|
||||
})
|
||||
const event = detail.data?.data
|
||||
|
||||
const columns: ColumnDef<FileXAdminEvent>[] = [
|
||||
{
|
||||
header: '事件',
|
||||
cell: ({ row }) => (
|
||||
<Button
|
||||
variant="link"
|
||||
className="h-auto max-w-80 justify-start p-0 text-left"
|
||||
onClick={() => setDetailId(row.original.id)}
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="truncate font-medium">{row.original.title ?? '—'}</div>
|
||||
<code className="font-mono text-xs text-muted-foreground">#{row.original.id}</code>
|
||||
</div>
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
{ header: '作者', cell: ({ row }) => row.original.author?.display_name ?? '—' },
|
||||
{
|
||||
header: '可见性',
|
||||
cell: ({ row }) => (
|
||||
<Badge variant="secondary">
|
||||
{VISIBILITY_LABELS[row.original.visibility] ?? row.original.visibility}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: '状态',
|
||||
cell: ({ row }) => (
|
||||
<Badge variant={row.original.is_blocked ? 'destructive' : 'outline'}>
|
||||
{row.original.is_blocked ? '已屏蔽' : row.original.deleted_at ? '已删除' : '正常'}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: '互动 / 待处理举报',
|
||||
cell: ({ row }) =>
|
||||
`${row.original.likes_count} 赞 · ${row.original.comments_count} 评论 · ${row.original.open_reports_count} 举报`,
|
||||
},
|
||||
{ header: '创建时间', cell: ({ row }) => formatTime(row.original.created_at) },
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
title="事件监控"
|
||||
description="全站事件、屏蔽状态及互动与举报情况(只读)"
|
||||
actions={
|
||||
<div className="flex flex-wrap items-center justify-end gap-2">
|
||||
<Input className="h-8 w-36" placeholder="搜索标题" value={query} onChange={(event) => setQuery(event.target.value)} />
|
||||
<Input className="h-8 w-32" placeholder="主话题" value={topic} onChange={(event) => setTopic(event.target.value)} />
|
||||
<Input className="h-8 w-52" placeholder="作者 UID" value={authorUid} onChange={(event) => setAuthorUid(event.target.value)} />
|
||||
<Select value={status} onValueChange={setStatus}>
|
||||
<SelectTrigger size="sm" className="w-28"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部状态</SelectItem>
|
||||
<SelectItem value="visible">未屏蔽</SelectItem>
|
||||
<SelectItem value="blocked">已屏蔽</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select value={visibility} onValueChange={setVisibility}>
|
||||
<SelectTrigger size="sm" className="w-28"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部可见性</SelectItem>
|
||||
<SelectItem value="public">公开</SelectItem>
|
||||
<SelectItem value="friends">好友</SelectItem>
|
||||
<SelectItem value="private">私密</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={list.rows}
|
||||
pagination={list.pagination}
|
||||
loading={list.loading}
|
||||
emptyText="没有事件记录"
|
||||
onPageChange={list.setPage}
|
||||
onPageSizeChange={list.setPageSize}
|
||||
/>
|
||||
<DetailSheet
|
||||
open={detailId !== null}
|
||||
onOpenChange={(open) => !open && setDetailId(null)}
|
||||
title={event?.title ?? `事件 #${detailId ?? ''}`}
|
||||
description={event ? `${VISIBILITY_LABELS[event.visibility] ?? event.visibility} · ${event.is_blocked ? '已屏蔽' : event.deleted_at ? '已删除' : '正常'}` : undefined}
|
||||
fields={[
|
||||
{ label: '正文', value: event?.content ?? '—' },
|
||||
{ label: '作者', value: event?.author?.display_name ?? '—' },
|
||||
{ label: '主话题', value: event?.primary_topic ?? '—' },
|
||||
{ label: '事件日期', value: event?.event_day ?? '—' },
|
||||
{ label: '位置', value: event?.location_name ?? event?.location_query ?? '—' },
|
||||
{ label: '经纬度', value: event?.latitude != null && event.longitude != null ? `${event.latitude}, ${event.longitude}` : '—' },
|
||||
{ label: '点赞 / 评论 / 待处理举报', value: event ? `${event.likes_count} / ${event.comments_count} / ${event.open_reports_count}` : '—' },
|
||||
{ label: '屏蔽时间', value: formatTime(event?.blocked_at) },
|
||||
{ label: '删除时间', value: formatTime(event?.deleted_at) },
|
||||
]}
|
||||
jsonBlocks={[
|
||||
{ label: '话题', value: event?.topics?.length ? event.topics : null },
|
||||
{ label: '附件', value: event?.assets?.length ? event.assets : null },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user