duoqi-admin/src/components/question/KnowledgeCardFields.tsx
Wang Zhuoxuan 37b936ec52 feat: 对接题目查询接口,统一数据模型与 API 规范
- Question.stem 从 string 改为 { text: string } 对象,匹配 API 多语言题干格式
- 新增 contentType 字段(text/image/video/audio)
- knowledgeCard 从扁平字段重组为嵌套对象 { summary, deepDive?, sourceRef? }
- source/stats 改为可选字段,UI 添加空值回退
- 查询参数对齐:search→keyword, sort→sortBy, order→sortOrder
- QuestionForm 接入 createQuestion/updateQuestion API
- KnowledgeCardFields 组件重命名 props 匹配新结构
2026-04-12 00:34:09 +08:00

179 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from "react"
import type { UseFormRegisterReturn } from "react-hook-form"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
const SUMMARY_MAX = 2000
const DEEP_DIVE_MAX = 300
interface KnowledgeCardFieldsProps {
summaryRegister: UseFormRegisterReturn
deepDiveRegister: UseFormRegisterReturn
sourceRefRegister: UseFormRegisterReturn
summaryError?: string
deepDiveError?: string
watchSummary: string
watchDeepDive: string
}
export function KnowledgeCardFields({
summaryRegister,
deepDiveRegister,
sourceRefRegister,
summaryError,
deepDiveError,
watchSummary,
watchDeepDive,
}: KnowledgeCardFieldsProps) {
const [showPreview, setShowPreview] = useState(false)
const [deepExpanded, setDeepExpanded] = useState(!!watchDeepDive)
const summaryCount = watchSummary.length
const deepCount = watchDeepDive.length
const summaryOver = summaryCount > SUMMARY_MAX
const deepOver = deepCount > DEEP_DIVE_MAX
return (
<div className="space-y-4">
{/* 摘要 */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<Label htmlFor="cardSummary"></Label>
<Badge variant="secondary" className="text-xs">
</Badge>
</div>
<p className="text-sm text-muted-foreground">
2-3
</p>
<Textarea
id="cardSummary"
placeholder="例:唐太宗李世民不仅是千古一帝,还是书法发烧友。他最爱王羲之的字,据说《兰亭集序》真迹被他带进了昭陵。"
rows={3}
{...summaryRegister}
/>
<div className="flex items-center justify-between">
{summaryError && (
<p className="text-sm text-destructive">{summaryError}</p>
)}
<span
className={`ml-auto text-xs ${summaryOver ? "text-destructive font-medium" : "text-muted-foreground"}`}
>
{summaryCount}/{SUMMARY_MAX}
</span>
</div>
</div>
{/* 深度解析 */}
<div className="space-y-2">
<button
type="button"
className="flex items-center gap-2"
onClick={() => setDeepExpanded((prev) => !prev)}
>
<Label className="cursor-pointer"></Label>
<Badge variant="outline" className="text-xs">
Pro
</Badge>
<span className="text-xs text-muted-foreground">
{deepExpanded ? "收起" : "展开"}
</span>
</button>
{deepExpanded && (
<>
<p className="text-sm text-muted-foreground">
Pro
</p>
<Textarea
placeholder="例:王羲之写《兰亭集序》时喝了点酒,一气呵成。后来他多次重写都不满意,感叹「此神助耳,何吾能力致」。唐太宗派萧翼用计从辩才和尚手中骗得真迹,临终遗命将真迹陪葬昭陵。不过近年有学者认为,真迹可能并未入昭陵,而是另有下落……"
rows={5}
{...deepDiveRegister}
/>
<div className="flex items-center justify-between">
{deepDiveError && (
<p className="text-sm text-destructive">{deepDiveError}</p>
)}
<span
className={`ml-auto text-xs ${deepOver ? "text-destructive font-medium" : "text-muted-foreground"}`}
>
{deepCount}/{DEEP_DIVE_MAX}
</span>
</div>
</>
)}
</div>
{/* 来源参考 */}
<div className="space-y-2">
<Label htmlFor="cardSourceRef">
<span className="ml-1 text-muted-foreground font-normal"></span>
</Label>
<Input
id="cardSourceRef"
placeholder="如:《旧唐书·太宗本纪》"
{...sourceRefRegister}
/>
</div>
{/* 预览切换 */}
<Button
type="button"
variant="outline"
size="sm"
onClick={() => setShowPreview((prev) => !prev)}
>
{showPreview ? "关闭预览" : "预览效果"}
</Button>
{showPreview && (
<Card className="border-dashed">
<CardHeader>
<CardTitle className="text-base"></CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
{watchSummary ? (
<p className="text-sm leading-relaxed">{watchSummary}</p>
) : (
<p className="text-sm text-muted-foreground italic">
</p>
)}
{watchDeepDive && (
<>
<div className="border-t pt-3">
<div className="flex items-center gap-1 mb-1">
<Badge variant="outline" className="text-xs">
Pro
</Badge>
<span className="text-xs text-muted-foreground">
</span>
</div>
<p className="text-sm leading-relaxed text-muted-foreground">
{watchDeepDive}
</p>
</div>
</>
)}
</CardContent>
</Card>
)}
</div>
)
}