diff --git a/src/api/modules/studio.ts b/src/api/modules/studio.ts index 8bf2821..028ac0e 100644 --- a/src/api/modules/studio.ts +++ b/src/api/modules/studio.ts @@ -27,6 +27,25 @@ export const fetchArticles = (params: ListParams) => export const fetchSubtopics = (params: ListParams) => api.get>(`${BASE}/subtopics`, params) +export interface SubtopicPayload { + category_id?: string + title?: string + target_audience?: string | null + pain_points?: string | null + content_value?: string | null + expandability_score?: number | null + status?: StudioSubtopic['status'] +} + +export const createSubtopic = (payload: SubtopicPayload) => + api.post>(`${BASE}/subtopics`, payload) + +export const updateSubtopic = (id: string, payload: SubtopicPayload) => + api.put>(`${BASE}/subtopics/${id}`, payload) + +export const deleteSubtopic = (id: string) => + api.delete>(`${BASE}/subtopics/${id}`) + export const fetchCategoryBriefs = (params: ListParams) => api.get>(`${BASE}/category-briefs`, params) diff --git a/src/api/types.gen.ts b/src/api/types.gen.ts index d412541..1d3740f 100644 --- a/src/api/types.gen.ts +++ b/src/api/types.gen.ts @@ -1273,8 +1273,8 @@ export interface paths { */ content_value?: string; /** - * @description 可扩展性评分,0-255。 - * @example 80 + * @description 可扩展性评分,0-10。 + * @example 8 */ expandability_score?: number; /** @@ -3277,8 +3277,8 @@ export interface paths { */ content_value?: string; /** - * @description 可扩展性评分,0-255。 - * @example 90 + * @description 可扩展性评分,0-10。 + * @example 9 */ expandability_score?: number; /** @@ -7685,9 +7685,9 @@ export interface paths { * @example b */ q?: string | null; - /** @example false */ - status?: boolean | null; /** @example true */ + status?: boolean | null; + /** @example false */ is_robot?: boolean | null; /** * @description Must not be greater than 100 characters. diff --git a/src/features/studio/subtopic-edit-sheet.tsx b/src/features/studio/subtopic-edit-sheet.tsx new file mode 100644 index 0000000..ae4b4ef --- /dev/null +++ b/src/features/studio/subtopic-edit-sheet.tsx @@ -0,0 +1,56 @@ +import { useEffect, useState } from 'react' +import { toast } from 'sonner' +import type { StudioSubtopic } from '@/api/types' +import { createSubtopic, updateSubtopic, type SubtopicPayload } from '@/api/modules/studio' +import { handleApiError } from '@/lib/errors' +import { CategoryPicker } from '@/features/categories/category-picker' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet' +import { Textarea } from '@/components/ui/textarea' + +interface Props { subtopic: StudioSubtopic | null; creating: boolean; onOpenChange: (open: boolean) => void; onSaved: () => void } + +export function SubtopicEditSheet({ subtopic, creating, onOpenChange, onSaved }: Props) { + const open = creating || subtopic !== null + const [categoryId, setCategoryId] = useState(null) + const [title, setTitle] = useState('') + const [targetAudience, setTargetAudience] = useState('') + const [painPoints, setPainPoints] = useState('') + const [contentValue, setContentValue] = useState('') + const [score, setScore] = useState('') + const [status, setStatus] = useState('active') + const [saving, setSaving] = useState(false) + + useEffect(() => { + setCategoryId(subtopic?.category_id ?? null); setTitle(subtopic?.title ?? '') + setTargetAudience(subtopic?.target_audience ?? ''); setPainPoints(subtopic?.pain_points ?? '') + setContentValue(subtopic?.content_value ?? ''); setScore(subtopic?.expandability_score?.toString() ?? '') + setStatus(subtopic?.status ?? 'active') + }, [subtopic, creating]) + + const save = async () => { + if (!categoryId || !title.trim()) { toast.error('请选择分类并填写标题'); return } + const numericScore = score === '' ? null : Number(score) + if (numericScore !== null && (!Number.isInteger(numericScore) || numericScore < 0 || numericScore > 10)) { toast.error('可扩展性评分必须是 0–10 的整数'); return } + const payload: SubtopicPayload = { category_id: categoryId, title: title.trim(), target_audience: targetAudience.trim() || null, pain_points: painPoints.trim() || null, content_value: contentValue.trim() || null, expandability_score: numericScore, status } + setSaving(true) + try { + if (subtopic) await updateSubtopic(subtopic.id, payload); else await createSubtopic(payload) + toast.success(subtopic ? '子话题已更新' : '子话题已创建'); onSaved(); onOpenChange(false) + } catch (error) { handleApiError(error) } finally { setSaving(false) } + } + + return {subtopic ? '编辑子话题' : '新建子话题'}
+
+
setTitle(e.target.value)} />
+