diff --git a/src/api/modules/dimzou.ts b/src/api/modules/dimzou.ts index b229a2a..489f210 100644 --- a/src/api/modules/dimzou.ts +++ b/src/api/modules/dimzou.ts @@ -1,6 +1,8 @@ import { api } from '@/api/client' import type { ApiResponse, + DimzouDocument, + DimzouPublication, DimzouTranslation, ListParams, PaginatedResponse, @@ -9,6 +11,18 @@ import type { const BASE = '/api/admin/v1/dimzou' +export function fetchDimzouDocuments(params: ListParams) { + return api.get>(`${BASE}/documents`, params) +} + +export function fetchDimzouDocument(id: number) { + return api.get>(`${BASE}/documents/${id}`) +} + +export function fetchDimzouPublications(params: ListParams) { + return api.get>(`${BASE}/publications`, params) +} + export function fetchDimzouTranslations(params: ListParams) { return api.get>(`${BASE}/translations`, params) } diff --git a/src/routes/_authed/dimzou/documents.tsx b/src/routes/_authed/dimzou/documents.tsx new file mode 100644 index 0000000..c660bcf --- /dev/null +++ b/src/routes/_authed/dimzou/documents.tsx @@ -0,0 +1,171 @@ +import { createFileRoute } from '@tanstack/react-router' +import { PERM, requirePermission } from '@/auth/permissions' +import { useQuery } from '@tanstack/react-query' +import { useState } from 'react' +import type { ColumnDef } from '@tanstack/react-table' +import type { DimzouDocument, DimzouDocumentState } from '@/api/types' +import { fetchDimzouDocument, fetchDimzouDocuments } from '@/api/modules/dimzou' +import { useListPage } from '@/lib/list-page' +import { DataTable } from '@/components/data-table/data-table' +import { PageHeader } from '@/components/page-header' +import { DetailSheet } from '@/features/studio/detail-sheet' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' + +export const Route = createFileRoute('/_authed/dimzou/documents')({ + beforeLoad: () => requirePermission(PERM.DIMZOU_READ), + component: DimzouDocumentsPage, +}) + +const STATE_LABELS: Record = { + active: '活跃', + archived: '已归档', + trashed: '回收站', +} + +function StateBadge({ state }: { state: DimzouDocumentState }) { + if (state === 'active') { + return 活跃 + } + if (state === 'archived') { + return 已归档 + } + return 回收站 +} + +const formatTime = (value?: string | null) => + value ? new Date(value).toLocaleString('zh-CN') : '—' + +function DimzouDocumentsPage() { + const [state, setState] = useState('all') + const [detailId, setDetailId] = useState(null) + + const list = useListPage( + 'dimzou-documents', + fetchDimzouDocuments, + state === 'all' ? {} : { state }, + ) + + const detail = useQuery({ + queryKey: ['dimzou-document', detailId], + queryFn: () => fetchDimzouDocument(detailId as number), + enabled: detailId !== null, + }) + const doc = detail.data?.data + + const columns: ColumnDef[] = [ + { + header: '文档', + cell: ({ row }) => ( + + ), + }, + { + header: '状态', + cell: ({ row }) => , + }, + { + header: '分类', + cell: ({ row }) => row.original.category?.name ?? '—', + }, + { + header: '版本数', + cell: ({ row }) => row.original.versions_count, + }, + { + header: '已发布 / 草稿', + cell: ({ row }) => + `${row.original.published_version ? `v${row.original.published_version}` : '—'} / ${row.original.draft_version ? `v${row.original.draft_version}` : '—'}`, + }, + { + header: '更新时间', + cell: ({ row }) => formatTime(row.original.updated_at), + }, + ] + + return ( +
+ + + + + + 全部状态 + {Object.entries(STATE_LABELS).map(([value, label]) => ( + + {label} + + ))} + + + } + /> + + !open && setDetailId(null)} + title={doc?.title ?? `文档 #${detailId ?? ''}`} + description={doc ? `状态 ${STATE_LABELS[doc.state] ?? doc.state}` : undefined} + fields={[ + { label: '所有者', value: doc?.owner?.display_name ?? '—' }, + { label: '分类', value: doc?.category?.name ?? '—' }, + { label: '版本数', value: doc?.versions_count ?? 0 }, + { label: '待审修订', value: doc?.pending_revisions_count ?? 0 }, + { label: '待审加入申请', value: doc?.pending_join_requests_count ?? 0 }, + { label: '归档时间', value: formatTime(doc?.archived_at) }, + { label: '删除时间', value: formatTime(doc?.deleted_at) }, + { label: '创建时间', value: formatTime(doc?.created_at) }, + ]} + jsonBlocks={[ + { + label: `版本(${doc?.versions?.length ?? 0})`, + value: doc?.versions?.length + ? doc.versions.map((v) => ({ + version: v.version_number, + status: v.status, + visibility: v.visibility, + language: v.language, + published_at: v.published_at, + })) + : null, + }, + { + label: `译文(${doc?.translations?.length ?? 0})`, + value: doc?.translations?.length ? doc.translations : null, + }, + ]} + /> +
+ ) +} diff --git a/src/routes/_authed/dimzou/publications.tsx b/src/routes/_authed/dimzou/publications.tsx new file mode 100644 index 0000000..1ae1ff8 --- /dev/null +++ b/src/routes/_authed/dimzou/publications.tsx @@ -0,0 +1,137 @@ +import { createFileRoute } from '@tanstack/react-router' +import { PERM, requirePermission } from '@/auth/permissions' +import { useState } from 'react' +import type { ColumnDef } from '@tanstack/react-table' +import type { DimzouPublication } from '@/api/types' +import { fetchDimzouPublications } from '@/api/modules/dimzou' +import { useListPage } from '@/lib/list-page' +import { DataTable } from '@/components/data-table/data-table' +import { PageHeader } from '@/components/page-header' +import { DetailSheet } from '@/features/studio/detail-sheet' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' + +export const Route = createFileRoute('/_authed/dimzou/publications')({ + beforeLoad: () => requirePermission(PERM.DIMZOU_READ), + component: DimzouPublicationsPage, +}) + +const formatTime = (value?: string | null) => + value ? new Date(value).toLocaleString('zh-CN') : '—' + +function DimzouPublicationsPage() { + const [visibility, setVisibility] = useState('all') + const [detail, setDetail] = useState(null) + + const list = useListPage( + 'dimzou-publications', + fetchDimzouPublications, + visibility === 'all' ? {} : { visibility }, + ) + + const columns: ColumnDef[] = [ + { + header: '标题', + cell: ({ row }) => ( + + ), + }, + { + header: '可见性', + cell: ({ row }) => + row.original.visibility === 'public' ? ( + 公开 + ) : ( + 仅协作组 + ), + }, + { + header: '语言', + cell: ({ row }) => row.original.language ?? '—', + }, + { + header: '版本', + cell: ({ row }) => `v${row.original.version_number}`, + }, + { + header: '块数', + cell: ({ row }) => row.original.block_count ?? 0, + }, + { + header: '分类', + cell: ({ row }) => row.original.category?.name ?? '—', + }, + { + header: '发布时间', + cell: ({ row }) => formatTime(row.original.published_at), + }, + ] + + return ( +
+ + + + + + 全部可见性 + 公开 + 仅协作组 + + + } + /> + + !open && setDetail(null)} + title={detail?.title ?? ''} + description={detail ? `文档 #${detail.document_id} · v${detail.version_number}` : undefined} + fields={[ + { label: '作者', value: detail?.author?.display_name ?? '—' }, + { label: '可见性', value: detail?.visibility === 'public' ? '公开' : '仅协作组' }, + { label: '语言', value: detail?.language ?? '—' }, + { label: '块数', value: detail?.block_count ?? 0 }, + { label: '分类', value: detail?.category?.name ?? '—' }, + { label: '摘要', value: detail?.summary ?? '—' }, + { label: '发布时间', value: formatTime(detail?.published_at) }, + ]} + jsonBlocks={[ + { label: '关键词', value: detail?.keywords?.length ? detail.keywords : null }, + { label: 'Feed 卡片', value: detail?.feed_card ?? null }, + ]} + /> +
+ ) +}