存量前端:生图分组/灯箱/重生控件/导入文案/驳回横幅+SSE+总核销表

- ImageStrategyGroup/ImageLightbox/RegenControls/ImportTextModal/
  RejectReasonBanner 新建组件
- tasks images/text/new + history + 配置/布局/SSE hooks 存量改动
- 总核销表进度更新

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
yangqianqian
2026-06-18 11:17:53 +08:00
parent d85dcd401b
commit 25f86b2f4a
20 changed files with 741 additions and 60 deletions

View File

@@ -9,6 +9,7 @@ interface TextCandidateCardProps {
candidate: TextCandidate;
selected: boolean;
onToggle: (id: number) => void;
onSaveEdit?: (id: number, content: string) => Promise<void>;
}
const BANNED_STATUS_STYLES = {
@@ -24,8 +25,27 @@ const VERDICT_STYLES: Record<string, string> = {
'不合格':'text-status-error',
};
export function TextCandidateCard({ candidate, selected, onToggle }: TextCandidateCardProps) {
export function TextCandidateCard({ candidate, selected, onToggle, onSaveEdit }: TextCandidateCardProps) {
const [expanded, setExpanded] = useState(false);
const [editing, setEditing] = useState(false);
const [draft, setDraft] = useState(candidate.content);
const [saving, setSaving] = useState(false);
const [justSaved, setJustSaved] = useState(false);
async function handleSave(e: React.MouseEvent) {
e.stopPropagation();
if (!onSaveEdit || saving || draft.trim() === '') return;
setSaving(true);
try {
await onSaveEdit(candidate.candidate_id, draft);
setEditing(false);
setJustSaved(true); // 飞轮节点反馈:改稿=最强信号已喂回
setTimeout(() => setJustSaved(false), 4000);
} finally {
setSaving(false);
}
}
return (
<article
onClick={() => onToggle(candidate.candidate_id)}
@@ -52,18 +72,62 @@ export function TextCandidateCard({ candidate, selected, onToggle }: TextCandida
)}
<div className="mt-1">
<span className="text-xs font-medium text-brand-orange mb-1 block">{candidate.angle_label}</span>
<p className={clsx(
'text-sm text-text-primary whitespace-pre-wrap',
!expanded && 'line-clamp-4'
)}>{candidate.content}</p>
<button
type="button"
onClick={(e) => { e.stopPropagation(); setExpanded((v) => !v); }}
className="mt-1 text-xs text-brand-orange hover:underline"
>
{expanded ? '收起' : '展开全文'}
</button>
<span className="text-xs font-medium text-brand-orange mb-1 block">
{candidate.angle_label}
{(candidate.edited || justSaved) && (
<span className="ml-1.5 text-[10px] text-brand-green-dark bg-brand-green-light px-1.5 py-0.5 rounded-full">稿</span>
)}
</span>
{editing ? (
<textarea
value={draft}
onChange={(e) => setDraft(e.target.value)}
onClick={(e) => e.stopPropagation()}
rows={6}
className="w-full text-sm border border-brand-orange/50 rounded-lg p-2 focus:outline-none focus:ring-1 focus:ring-brand-orange"
aria-label="改稿编辑框"
/>
) : (
<>
<p className={clsx(
'text-sm text-text-primary whitespace-pre-wrap',
!expanded && 'line-clamp-4'
)}>{candidate.content}</p>
<button
type="button"
onClick={(e) => { e.stopPropagation(); setExpanded((v) => !v); }}
className="mt-1 text-xs text-brand-orange hover:underline"
>
{expanded ? '收起' : '展开全文'}
</button>
</>
)}
{/* D1 改稿入口(飞轮最强信号 text_edit +5 */}
{onSaveEdit && (
<div className="mt-2 flex items-center gap-2">
{editing ? (
<>
<button type="button" onClick={handleSave} disabled={saving}
className="text-xs bg-brand-orange text-white rounded-md px-2.5 py-1 hover:bg-orange-600 disabled:opacity-50">
{saving ? '保存中…' : '保存改稿'}
</button>
<button type="button" onClick={(e) => { e.stopPropagation(); setEditing(false); setDraft(candidate.content); }}
className="text-xs text-text-secondary hover:underline"></button>
</>
) : (
<button type="button" onClick={(e) => { e.stopPropagation(); setDraft(candidate.content); setEditing(true); }}
className="text-xs text-brand-orange hover:underline"> 稿</button>
)}
</div>
)}
{/* 飞轮节点反馈:改稿即时提示已学习 */}
{justSaved && (
<p role="status" aria-live="polite" className="mt-1.5 text-[11px] text-brand-green-dark">
🌿
</p>
)}
</div>
{/* 违禁词状态 */}