baseline: Clover 独立仓库首次基线提交
将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。 当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包), M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
116
frontend/src/components/tasks/TextCandidateCard.tsx
Normal file
116
frontend/src/components/tasks/TextCandidateCard.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
'use client';
|
||||
// TextCandidateCard — 文案候选卡,AI评委7维分 + 总评 + 违禁状态 + 导入角标
|
||||
import { useState } from 'react';
|
||||
import { TextCandidate } from '@/types/index';
|
||||
import { ScoreDimBars } from './ScoreDimBars';
|
||||
import { clsx } from 'clsx' ;
|
||||
|
||||
interface TextCandidateCardProps {
|
||||
candidate: TextCandidate;
|
||||
selected: boolean;
|
||||
onToggle: (id: number) => void;
|
||||
}
|
||||
|
||||
const BANNED_STATUS_STYLES = {
|
||||
pass: null,
|
||||
auto_fixed: 'bg-green-50 border-brand-green text-brand-green-dark text-xs px-2 py-0.5 rounded-full',
|
||||
soft_warn: 'bg-yellow-50 border-yellow-400 text-yellow-700 text-xs px-2 py-0.5 rounded-full',
|
||||
hard_block: 'bg-red-50 border-red-400 text-status-error text-xs px-2 py-0.5 rounded-full',
|
||||
};
|
||||
|
||||
const VERDICT_STYLES: Record<string, string> = {
|
||||
'优秀': 'text-status-success font-medium',
|
||||
'合格': 'text-text-secondary',
|
||||
'不合格':'text-status-error',
|
||||
};
|
||||
|
||||
export function TextCandidateCard({ candidate, selected, onToggle }: TextCandidateCardProps) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
return (
|
||||
<article
|
||||
onClick={() => onToggle(candidate.candidate_id)}
|
||||
aria-selected={selected}
|
||||
aria-label={`文案:${candidate.angle_label},总分${candidate.score.total},${selected ? '已选中' : '未选中'}`}
|
||||
className={clsx(
|
||||
'relative border rounded-xl p-4 cursor-pointer transition-all',
|
||||
selected
|
||||
? 'border-brand-orange bg-brand-cream shadow-sm'
|
||||
: 'border-border-default bg-surface-primary hover:border-brand-orange/50'
|
||||
)}
|
||||
>
|
||||
{/* 角标:导入来源 */}
|
||||
{candidate.source === 'import' && (
|
||||
<span className="absolute top-2 right-2 text-xs bg-surface-tertiary text-text-secondary px-2 py-0.5 rounded-full">
|
||||
导入
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* 选中勾 */}
|
||||
{selected && (
|
||||
<span className="absolute top-2 left-2 w-5 h-5 bg-brand-orange text-white rounded-full flex items-center justify-center text-xs"
|
||||
aria-hidden="true">✓</span>
|
||||
)}
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
{/* 违禁词状态 */}
|
||||
{candidate.banned_word_status !== 'pass' && (
|
||||
<div className="mt-2">
|
||||
<span className={BANNED_STATUS_STYLES[candidate.banned_word_status] ?? ''}>
|
||||
{candidate.banned_word_status === 'auto_fixed' && '已自动改写'}
|
||||
{candidate.banned_word_status === 'soft_warn' && '含敏感词,请检查'}
|
||||
{candidate.banned_word_status === 'hard_block' && '含违禁词,已拦截'}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 七维分数条 */}
|
||||
<ScoreDimBars score={candidate.score} passLine={85} />
|
||||
|
||||
{/* AI 评委总评(verdict + summary,旧数据空字符串时不渲染)*/}
|
||||
{candidate.summary && (
|
||||
<p className="mt-2 text-xs text-text-secondary leading-relaxed">
|
||||
{candidate.verdict && (
|
||||
<span className={clsx('mr-1', VERDICT_STYLES[candidate.verdict] ?? 'text-text-secondary')}>
|
||||
[{candidate.verdict}]
|
||||
</span>
|
||||
)}
|
||||
{candidate.summary}
|
||||
</p>
|
||||
)}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
/** 骨架屏占位 */
|
||||
export function TextCandidateCardSkeleton() {
|
||||
return (
|
||||
<div className="border border-border-default rounded-xl p-4 animate-pulse space-y-2" aria-hidden="true">
|
||||
<div className="h-3 bg-surface-tertiary rounded w-1/3" />
|
||||
<div className="h-4 bg-surface-tertiary rounded w-full" />
|
||||
<div className="h-4 bg-surface-tertiary rounded w-5/6" />
|
||||
<div className="h-4 bg-surface-tertiary rounded w-4/6" />
|
||||
<div className="mt-3 space-y-1">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div key={i} className="flex gap-2 items-center">
|
||||
<div className="h-2 bg-surface-tertiary rounded w-12" />
|
||||
<div className="h-1.5 bg-surface-tertiary rounded flex-1" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user