feat: 审核体系全面改造 — 多维度评分 + 卖点优先级 + AI 语义匹配 + 品牌方 AI 状态通知

后端:
- 审核结果拆分为 4 个独立维度 (法规合规/平台规则/品牌安全/Brief匹配度)
- 卖点优先级从 required:bool 改为三级 (core/recommended/reference)
- AI 语义匹配卖点覆盖 + AI 整体 Brief 匹配度分析
- BriefMatchDetail 评分详情 (覆盖率+亮点+问题点)
- min_selling_points 代理商可配置最少卖点数 + Alembic 迁移
- AI 语境复核过滤误报
- Brief AI 解析 + 规则 AI 解析
- AI 未配置/异常时通知品牌方
- 种子数据更新 (新格式审核结果+brief_match_detail)

前端:
- 三端审核页面展示四维度评分卡片
- 卖点编辑改为三级优先级选择器
- BriefMatchDetail 展示 (覆盖率进度条+亮点+问题)
- min_selling_points 配置 UI
- AI 配置页未配置时静默处理
- 文件预览/下载/签名 URL 优化

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-02-11 19:11:54 +08:00
co-authored by Claude Opus 4.6
parent 0c59797d5b
commit 0ef7650c09
43 changed files with 3909 additions and 1316 deletions
+67 -44
View File
@@ -477,15 +477,36 @@ export default function AgencyCreatorsPage() {
setOpenMenuId(null)
}
// 确认分配项目
const handleConfirmAssign = () => {
// 确认分配项目(创建任务)
const handleConfirmAssign = async () => {
const projectList = USE_MOCK ? mockProjects : projects
if (assignModal.creator && selectedProject) {
const project = projectList.find(p => p.id === selectedProject)
if (!assignModal.creator || !selectedProject) return
const project = projectList.find(p => p.id === selectedProject)
if (USE_MOCK) {
toast.success(`已将达人「${assignModal.creator.name}」分配到项目「${project?.name}`)
setAssignModal({ open: false, creator: null })
setSelectedProject('')
return
}
setSubmitting(true)
try {
await api.createTask({
project_id: selectedProject,
creator_id: assignModal.creator.creatorId,
})
toast.success(`已将达人「${assignModal.creator.name}」分配到项目「${project?.name}`)
setAssignModal({ open: false, creator: null })
setSelectedProject('')
await fetchData() // 刷新列表
} catch (err) {
const message = err instanceof Error ? err.message : '分配失败'
toast.error(message)
} finally {
setSubmitting(false)
}
setAssignModal({ open: false, creator: null })
setSelectedProject('')
}
// 骨架屏
@@ -761,43 +782,45 @@ export default function AgencyCreatorsPage() {
</td>
<td className="px-6 py-4 text-sm text-text-tertiary">{creator.joinedAt}</td>
<td className="px-6 py-4">
<div className="relative">
<Button
variant="ghost"
size="sm"
onClick={() => setOpenMenuId(openMenuId === creator.id ? null : creator.id)}
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => handleOpenAssign(creator)}
className="px-3 py-1.5 text-xs font-medium text-accent-indigo bg-accent-indigo/10 hover:bg-accent-indigo/20 rounded-lg transition-colors flex items-center gap-1.5"
>
<MoreVertical size={16} />
</Button>
{/* 下拉菜单 */}
{openMenuId === creator.id && (
<div className="absolute right-0 top-full mt-1 w-40 bg-bg-card rounded-xl shadow-lg border border-border-subtle z-10 overflow-hidden">
<button
type="button"
onClick={() => handleOpenRemark(creator)}
className="w-full px-4 py-2.5 text-left text-sm text-text-primary hover:bg-bg-elevated flex items-center gap-2"
>
<MessageSquareText size={14} className="text-text-secondary" />
{creator.remark ? '编辑备注' : '添加备注'}
</button>
<button
type="button"
onClick={() => handleOpenAssign(creator)}
className="w-full px-4 py-2.5 text-left text-sm text-text-primary hover:bg-bg-elevated flex items-center gap-2"
>
<FolderPlus size={14} className="text-text-secondary" />
</button>
<button
type="button"
onClick={() => handleOpenDelete(creator)}
className="w-full px-4 py-2.5 text-left text-sm text-accent-coral hover:bg-accent-coral/10 flex items-center gap-2"
>
<Trash2 size={14} />
</button>
</div>
)}
<FolderPlus size={13} />
</button>
<button
type="button"
onClick={() => handleOpenDelete(creator)}
className="p-1.5 text-text-tertiary hover:text-accent-coral hover:bg-accent-coral/10 rounded-lg transition-colors"
title="移除达人"
>
<Trash2 size={14} />
</button>
<div className="relative">
<button
type="button"
onClick={() => setOpenMenuId(openMenuId === creator.id ? null : creator.id)}
className="p-1.5 text-text-tertiary hover:text-text-primary hover:bg-bg-elevated rounded-lg transition-colors"
title="更多操作"
>
<MoreVertical size={14} />
</button>
{openMenuId === creator.id && (
<div className="absolute right-0 top-full mt-1 w-36 bg-bg-card rounded-xl shadow-lg border border-border-subtle z-10 overflow-hidden">
<button
type="button"
onClick={() => handleOpenRemark(creator)}
className="w-full px-4 py-2.5 text-left text-sm text-text-primary hover:bg-bg-elevated flex items-center gap-2"
>
<MessageSquareText size={14} className="text-text-secondary" />
{creator.remark ? '编辑备注' : '添加备注'}
</button>
</div>
)}
</div>
</div>
</td>
</tr>
@@ -1042,8 +1065,8 @@ export default function AgencyCreatorsPage() {
<Button variant="ghost" onClick={() => { setAssignModal({ open: false, creator: null }); setSelectedProject(''); }}>
</Button>
<Button onClick={handleConfirmAssign} disabled={!selectedProject}>
<FolderPlus size={16} />
<Button onClick={handleConfirmAssign} disabled={!selectedProject || submitting}>
{submitting ? <Loader2 size={16} className="animate-spin" /> : <FolderPlus size={16} />}
</Button>
</div>