feat(studio): manage article plans

This commit is contained in:
2026-09-10 14:17:30 +08:00
parent 1fbc6cab5b
commit ecdbf4d768
4 changed files with 81 additions and 21 deletions
+17 -5
View File
@@ -1,9 +1,14 @@
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 { StudioArticle } from '@/api/types'
import { fetchArticles } from '@/api/modules/studio'
import { deleteArticle, fetchArticles } from '@/api/modules/studio'
import { handleApiError } from '@/lib/errors'
import { ArticleEditSheet } from '@/features/studio/article-edit-sheet'
import { ConfirmDialog } from '@/components/confirm-dialog'
import { useListPage } from '@/lib/list-page'
import { ArticlePreviewSheet } from '@/features/studio/article-preview-sheet'
import { DetailSheet } from '@/features/studio/detail-sheet'
@@ -27,14 +32,20 @@ export const Route = createFileRoute('/_authed/studio/articles')({
const STATUSES = ['planned', 'drafting', 'drafted', 'published', 'scheduled'] as const
function ArticlesPage() {
const canManage = useCan(PERM.STUDIO_MANAGE)
const queryClient = useQueryClient()
const [status, setStatus] = useState('all')
const [detail, setDetail] = useState<StudioArticle | null>(null)
const [preview, setPreview] = useState<StudioArticle | null>(null)
const [editing, setEditing] = useState<StudioArticle | null>(null)
const [creating, setCreating] = useState(false)
const list = useListPage(
'studio-articles',
fetchArticles,
status === 'all' ? {} : { status },
)
const refresh = () => { void queryClient.invalidateQueries({ queryKey: ['studio-articles'] }); void list.query.refetch() }
const remove = async (row: StudioArticle) => { try { await deleteArticle(row.id); refresh() } catch (error) { handleApiError(error) } }
const columns: ColumnDef<StudioArticle>[] = [
{
@@ -83,6 +94,8 @@ function ArticlesPage() {
<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="已创建的 Dimzou 内容不会由此操作级联删除。" confirmLabel="删除" destructive onConfirm={() => remove(row.original)} />}
</div>
),
},
@@ -93,8 +106,7 @@ function ArticlesPage() {
<PageHeader
title="文章"
description="正文存于 Dimzou 草稿(块修订即编辑历史);此处为计划元数据"
actions={
<Select value={status} onValueChange={setStatus}>
actions={<div className="flex gap-2">{canManage && <Button onClick={() => setCreating(true)}></Button>}<Select value={status} onValueChange={setStatus}>
<SelectTrigger size="sm" className="w-32">
<SelectValue />
</SelectTrigger>
@@ -106,8 +118,7 @@ function ArticlesPage() {
</SelectItem>
))}
</SelectContent>
</Select>
}
</Select></div>}
/>
<DataTable
columns={columns}
@@ -157,6 +168,7 @@ function ArticlesPage() {
/>
<ArticlePreviewSheet article={preview} onOpenChange={(open) => !open && setPreview(null)} />
<ArticleEditSheet article={editing} creating={creating} onOpenChange={(open) => { if (!open) { setEditing(null); setCreating(false) } }} onSaved={refresh} />
</div>
)
}