feat(studio): authors/series/articles/subtopics/briefs/style-presets + agent tokens (show-once)
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { useState } from 'react'
|
||||
import type { ColumnDef } from '@tanstack/react-table'
|
||||
import type { StudioArticle } from '@/api/types'
|
||||
import { fetchArticles } from '@/api/modules/studio'
|
||||
import { useListPage } from '@/lib/list-page'
|
||||
import { DetailSheet } from '@/features/studio/detail-sheet'
|
||||
import { DataTable } from '@/components/data-table/data-table'
|
||||
import { PageHeader } from '@/components/page-header'
|
||||
import { StatusPill } from '@/components/status-pill'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
|
||||
export const Route = createFileRoute('/_authed/studio/articles')({
|
||||
component: ArticlesPage,
|
||||
})
|
||||
|
||||
const STATUSES = ['planned', 'drafting', 'drafted', 'published', 'scheduled'] as const
|
||||
|
||||
function ArticlesPage() {
|
||||
const [status, setStatus] = useState('all')
|
||||
const [detail, setDetail] = useState<StudioArticle | null>(null)
|
||||
const list = useListPage(
|
||||
'studio-articles',
|
||||
fetchArticles,
|
||||
status === 'all' ? {} : { status },
|
||||
)
|
||||
|
||||
const columns: ColumnDef<StudioArticle>[] = [
|
||||
{
|
||||
header: '文章',
|
||||
cell: ({ row }) => (
|
||||
<div>
|
||||
<div className="max-w-80 truncate font-medium">
|
||||
{row.original.title ?? '(未命名)'}
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
系列第 {row.original.article_number} 篇
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{ header: '状态', cell: ({ row }) => <StatusPill status={row.original.status} /> },
|
||||
{
|
||||
header: 'Dimzou',
|
||||
cell: ({ row }) =>
|
||||
row.original.dimzou_publication_id ? (
|
||||
<code className="font-mono text-xs">
|
||||
doc #{row.original.dimzou_document_id} · pub #{row.original.dimzou_publication_id}
|
||||
</code>
|
||||
) : row.original.dimzou_document_id ? (
|
||||
<code className="font-mono text-xs">草稿 doc #{row.original.dimzou_document_id}</code>
|
||||
) : (
|
||||
<span className="text-xs text-muted-foreground">未建稿</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: '更新时间',
|
||||
cell: ({ row }) => new Date(row.original.updated_at).toLocaleString('zh-CN'),
|
||||
},
|
||||
{
|
||||
header: '操作',
|
||||
cell: ({ row }) => (
|
||||
<Button size="sm" variant="ghost" onClick={() => setDetail(row.original)}>
|
||||
详情
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
title="文章"
|
||||
description="正文存于 Dimzou 草稿(块修订即编辑历史);此处为计划元数据"
|
||||
actions={
|
||||
<Select value={status} onValueChange={setStatus}>
|
||||
<SelectTrigger size="sm" className="w-32">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部状态</SelectItem>
|
||||
{STATUSES.map((s) => (
|
||||
<SelectItem key={s} value={s}>
|
||||
<StatusPill status={s} />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
}
|
||||
/>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={list.rows}
|
||||
pagination={list.pagination}
|
||||
loading={list.loading}
|
||||
onPageChange={list.setPage}
|
||||
onPageSizeChange={list.setPageSize}
|
||||
/>
|
||||
<DetailSheet
|
||||
open={detail !== null}
|
||||
onOpenChange={(open) => !open && setDetail(null)}
|
||||
title={detail?.title ?? '文章详情'}
|
||||
description={detail?.summary ?? undefined}
|
||||
fields={[
|
||||
{ label: 'ID', value: <code className="font-mono text-xs">{detail?.id}</code> },
|
||||
{ label: '状态', value: detail && <StatusPill status={detail.status} /> },
|
||||
{ label: '系列内序号', value: detail?.article_number },
|
||||
{ label: '标签', value: detail?.tags?.join('、') },
|
||||
{ label: 'SEO 关键词', value: detail?.seo_keywords?.join('、') },
|
||||
{ label: 'CTA', value: detail?.cta },
|
||||
{ label: '封面提示词', value: detail?.cover_image_prompt },
|
||||
{
|
||||
label: 'Dimzou 文档',
|
||||
value: detail?.dimzou_document_id ? `#${detail.dimzou_document_id}` : '未建稿',
|
||||
},
|
||||
{
|
||||
label: 'Dimzou 发布',
|
||||
value: detail?.dimzou_publication_id ? `#${detail.dimzou_publication_id}` : '未发布',
|
||||
},
|
||||
]}
|
||||
jsonBlocks={[
|
||||
{ label: '要点 key_takeaways', value: detail?.key_takeaways },
|
||||
{ label: '大纲 outline', value: detail?.outline },
|
||||
{ label: '计划 plan', value: detail?.plan },
|
||||
{ label: '发布排期', value: detail?.publish_schedule },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user