feat(studio): manage article series

This commit is contained in:
2026-09-10 14:03:59 +08:00
parent c44ebc8823
commit afe4a4b919
3 changed files with 84 additions and 5 deletions
+18 -5
View File
@@ -1,9 +1,15 @@
import { createFileRoute } from '@tanstack/react-router'
import { PERM, requirePermission } from '@/auth/permissions'
import { useCan } from '@/auth/store'
import { useQueryClient } from '@tanstack/react-query'
import { useState } from 'react'
import type { ColumnDef } from '@tanstack/react-table'
import type { StudioSeries } from '@/api/types'
import { fetchSeries } from '@/api/modules/studio'
import { deleteSeries, fetchSeries } from '@/api/modules/studio'
import { handleApiError } from '@/lib/errors'
import { SeriesEditSheet } from '@/features/studio/series-edit-sheet'
import { useCategoryOptions } from '@/features/categories/use-categories'
import { ConfirmDialog } from '@/components/confirm-dialog'
import { useListPage } from '@/lib/list-page'
import { DetailSheet } from '@/features/studio/detail-sheet'
import { DataTable } from '@/components/data-table/data-table'
@@ -17,10 +23,18 @@ export const Route = createFileRoute('/_authed/studio/series')({
})
function SeriesPage() {
const canManage = useCan(PERM.STUDIO_MANAGE)
const queryClient = useQueryClient()
const [detail, setDetail] = useState<StudioSeries | null>(null)
const [editing, setEditing] = useState<StudioSeries | null>(null)
const [creating, setCreating] = useState(false)
const list = useListPage('studio-series', fetchSeries)
const { nameOf } = useCategoryOptions()
const refresh = () => { void queryClient.invalidateQueries({ queryKey: ['studio-series'] }); void list.query.refetch() }
const remove = async (row: StudioSeries) => { try { await deleteSeries(row.id); refresh() } catch (error) { handleApiError(error) } }
const columns: ColumnDef<StudioSeries>[] = [
{ header: '分类', cell: ({ row }) => nameOf(row.original.category_id) },
{
header: '系列目标',
cell: ({ row }) => (
@@ -39,16 +53,14 @@ function SeriesPage() {
{
header: '操作',
cell: ({ row }) => (
<Button size="sm" variant="ghost" onClick={() => setDetail(row.original)}>
</Button>
<div className="flex gap-1.5"><Button size="sm" variant="ghost" onClick={() => setDetail(row.original)}></Button>{canManage && <Button size="sm" variant="outline" onClick={() => setEditing(row.original)}></Button>}{canManage && <ConfirmDialog trigger={<Button size="sm" variant="destructive"></Button>} title="删除文章系列?" description="如果系列仍被文章引用,服务端可能拒绝删除。" confirmLabel="删除" destructive onConfirm={() => remove(row.original)} />}</div>
),
},
]
return (
<div>
<PageHeader title="文章系列" description="机器人作者的系列规划(目标 / 读者旅程 / 计划列表)" />
<PageHeader title="文章系列" description="机器人作者的系列规划(目标 / 读者旅程 / 计划列表)" actions={canManage ? <Button onClick={() => setCreating(true)}></Button> : undefined} />
<DataTable
columns={columns}
data={list.rows}
@@ -75,6 +87,7 @@ function SeriesPage() {
]}
jsonBlocks={[{ label: '计划文章列表', value: detail?.planned_articles }]}
/>
<SeriesEditSheet series={editing} creating={creating} onOpenChange={(open) => { if (!open) { setEditing(null); setCreating(false) } }} onSaved={refresh} />
</div>
)
}