diff --git a/DevelopmentPlan.md b/DevelopmentPlan.md index 234fbd3..75c118b 100644 --- a/DevelopmentPlan.md +++ b/DevelopmentPlan.md @@ -401,8 +401,8 @@ sequenceDiagram | --- | --- | --- | | `brands` | 品牌方 | id, name, settings_json | | `agencies` | 代理商 | id, brand_id, name | -| `creators` | 达人 | id, agency_id, credit_score, appeal_tokens | -| `tasks` | 审核任务 | id, brand_id, agency_id, creator_id, status, platform | +| `creators` | 达人 | id, agency_id, credit_score | +| `tasks` | 审核任务 | id, brand_id, agency_id, creator_id, status, platform, appeal_remaining, appeal_used | | `briefs` | Brief 规则 | id, task_id, raw_file_url, parsed_rules_json | | `videos` | 视频文件 | id, task_id, version, file_url, duration | | `reports` | 审核报告 | id, video_id, ai_result_json, human_decision, created_at | diff --git a/FeatureSummary.md b/FeatureSummary.md index a5e7f9c..a5e2742 100644 --- a/FeatureSummary.md +++ b/FeatureSummary.md @@ -589,32 +589,69 @@ | 功能编号 | 功能名称 | 优先级 | 用户故事 | 使用角色 | | --- | --- | --- | --- | --- | | F-24 | 发起申诉 | P1 | - | 达人 | -| F-25 | 申诉令牌管理 | P1 | - | 系统 | +| F-25 | 申诉次数管理 | P1 | - | 达人/代理商 | +| F-25-A | 申请增加申诉次数 | P1 | - | 达人 | +| F-25-B | 处理申诉次数请求 | P1 | - | 代理商 | | F-26 | 人工仲裁 | P1 | - | 代理商 | | F-27 | 申诉结果通知 | P1 | - | 达人 | #### F-24 发起申诉 -**功能描述:** 达人可对每条报错发起申诉。 +**功能描述:** 达人可对每条报错发起申诉,消耗该任务的申诉次数。 **操作要求:** - 提供理由输入框(必填,≥ 10 字) - 可上传补充证据(截图、链接等) -- 消耗申诉令牌 +- 消耗该任务的1次申诉次数 +- 若申诉次数为0,提示"申诉次数不足"并引导申请增加 **界面映射:** 达人端 → 任务详情-审核结果区 → [申诉] 按钮 --- -#### F-25 申诉令牌管理 +#### F-25 申诉次数管理 -**功能描述:** 基于达人信用评分分配令牌配额,申诉成功后令牌返还。 +**功能描述:** 每个任务独立拥有申诉次数,初始为1次,达人可向代理商申请增加。 **规则说明:** -- 历史表现越好,配额越高 -- 申诉成功后令牌自动返还 +- 每个任务初始申诉次数:**1次** +- 申诉次数按任务独立计算,不同任务互不影响 +- 达人可在个人中心查看所有任务的申诉次数 +- 代理商可为达人的任务增加申诉次数(无上限) -**界面映射:** 达人端 → 任务详情-审核结果区 → 申诉弹窗(显示剩余令牌) +**界面映射:** +- 达人端 → 个人中心 → 申诉次数(查看所有任务的申诉次数列表) +- 达人端 → 任务详情-审核结果区 → 申诉弹窗(显示当前任务剩余次数) + +--- + +#### F-25-A 申请增加申诉次数 + +**功能描述:** 达人可为特定任务向代理商申请增加申诉次数。 + +**申请流程:** +1. 达人在个人中心 → 申诉次数页面,点击某任务的 `[申请增加]` 按钮 +2. 无需填写理由,直接发送申请 +3. 代理商在消息中心收到通知 +4. 等待代理商处理(同意/拒绝/忽略) + +**界面映射:** 达人端 → 个人中心 → 申诉次数 → [申请增加] 按钮 + +--- + +#### F-25-B 处理申诉次数请求 + +**功能描述:** 代理商处理达人的申诉次数增加请求。 + +**处理方式:** +- **消息中心入口:** 收到通知"达人【XXX】申请增加【XXX任务】的申诉次数",点击可处理 +- **达人管理入口:** 在达人管理页面可主动为任意达人的任务增加申诉次数 +- **操作选项:** 同意(+1次)/ 拒绝 / 不处理(忽略) +- **增加上限:** 无上限 + +**界面映射:** +- 代理商端 → 消息中心 → 申诉次数请求通知 +- 代理商端 → 达人管理 → 达人详情 → 任务申诉次数管理 --- diff --git a/PRD.md b/PRD.md index 0a4d6f8..2d23afb 100644 --- a/PRD.md +++ b/PRD.md @@ -131,7 +131,7 @@ - **舆情风控雷达:** 针对"油腻感"、"价值观风险"、"错别字"的专项检测 - **交互式审核台:** 支持时间戳打点、风险高亮、版本比对 (Diff) 的 Web 界面 - **移动端支持:** 响应式 H5 覆盖达人/代理商/品牌方(可作为小程序 WebView 承载) -- **信用与申诉体系:** 包含申诉令牌管理和人工仲裁流程 +- **信用与申诉体系:** 包含按任务独立的申诉次数管理和人工仲裁流程 - **规则库管理与版本控制:** 支持平台规则库更新、品牌私有规则与白名单配置 - **权限与多租户隔离:** 支持品牌/代理/达人不同角色的权限与数据隔离 - **证据链导出:** 支持导出可追溯的审核证据链报告 @@ -255,7 +255,9 @@ ### 6.6 申诉与仲裁 **P1** -- 申诉令牌管理与工单流转 +- 按任务独立的申诉次数管理(每任务初始1次) +- 达人申请增加申诉次数流程 +- 代理商处理申诉次数请求(同意/拒绝) - 人工仲裁流程与记录 - 操作记录完整可追溯 @@ -351,7 +353,10 @@ 15. 当日 00:00 后,已通过任务自动归入历史记录 **申诉流程:** -- 对任意审核结论可触发申诉(消耗申诉令牌) +- 对任意审核结论可触发申诉(消耗该任务的申诉次数) +- 每个任务初始申诉次数为 **1次**,不同任务独立计算 +- 申诉次数不足时,可向代理商申请增加(无需理由) +- 代理商可在消息中心或达人管理页面处理申请(同意/拒绝/忽略) --- @@ -374,7 +379,7 @@ - **规则集**:平台规则 + 品牌私有规则 + 白名单 + 规则版本记录 - **审核记录**:风险项、时间戳、证据片段、风险等级(红/黄/绿) - **人工决策**:确认/驳回/强制通过 + 操作人 + 操作时间 -- **申诉记录**:申诉原因、仲裁结论、令牌消耗 +- **申诉记录**:申诉原因、仲裁结论、申诉次数变化(任务维度) ### 9.2 审计要求 [US-12] diff --git a/RequirementsDoc.md b/RequirementsDoc.md index 650d64a..cf461b8 100644 --- a/RequirementsDoc.md +++ b/RequirementsDoc.md @@ -131,7 +131,7 @@ 3. **分区执法逻辑:** 智能区分“广告段”与“剧情段”,应用不同的审核尺度。 4. **舆情风控雷达:** 针对“油腻感”、“价值观风险”、“错别字”的专项检测模型。 5. **交互式审核台:** 支持时间戳打点、风险高亮、版本比对 (Diff) 的 Web 界面。 -6. **信用与申诉体系:** 包含申诉令牌管理和人工仲裁流程。 +6. **信用与申诉体系:** 包含按任务独立的申诉次数管理(每任务初始1次,可向代理商申请增加)和人工仲裁流程。 7. **规则库管理与版本控制:** 支持平台规则库更新、品牌私有规则与白名单配置。 8. **权限与多租户隔离:** 支持品牌/代理/达人不同角色的权限与数据隔离。 9. **审计日志与报告导出:** 支持导出可追溯的审核证据链。 diff --git a/UIDesign.md b/UIDesign.md index 2f7e972..91c4825 100644 --- a/UIDesign.md +++ b/UIDesign.md @@ -640,8 +640,8 @@ font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", │ │ │ │ │ │ ─────────────────────────────────────────────────── │ │ │ │ │ │ -│ │ 💡 剩余申诉令牌:2 次 │ │ -│ │ 申诉成功后令牌将自动返还 │ │ +│ │ 💡 本任务剩余申诉次数:1 次 │ │ +│ │ 每个任务独立计算,次数不足可向代理商申请增加 │ │ │ │ │ │ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ │ │ 提交申诉 │ │ │ diff --git a/UIDesignSpec.md b/UIDesignSpec.md index db3176b..c36fc66 100644 --- a/UIDesignSpec.md +++ b/UIDesignSpec.md @@ -282,34 +282,97 @@ ### 4.2 代理商端 (Agency) -| 页面名称 | 设备 | 优先级 | 设计稿节点ID | +> **侧边栏导航顺序:** 工作台 → 审核台 → Brief配置 → 达人管理 → 数据报表 → 消息中心 + +#### 4.2.1 Desktop 端页面 + +| 页面名称 | 优先级 | 设计稿节点ID | 备注 | | --- | --- | --- | --- | -| 工作台 | Desktop | P0 | RX8V9 | -| 审核决策台 | Desktop | P0 | 2u8Bq | -| Brief配置中心 | Desktop | P0 | djd2K | -| 达人管理 | Desktop | P1 | 5cFMX | -| 数据报表 | Desktop | P1 | An8gw | -| 版本比对 | Desktop | P2 | NDmYh | -| 工作台 | Mobile | P0 | VuH3F | -| 快捷审核 | Mobile | P0 | lrHaj | -| 任务列表 | Mobile | P1 | c6SPa | -| 消息中心 | Mobile | P1 | 9Us9g | -| 个人中心 | Mobile | P2 | 8OCZ3 | +| 工作台 | P0 | RX8V9 | 待办统计+快捷入口,默认首页 | +| 项目详情 | P0 | C7wfV | 项目数据和达人列表 | +| 审核台(列表页) | P0 | zjiCT | 脚本/视频待审列表 | +| 脚本审核决策台 | P0 | f8HX9 | 简单模式(文件图标+预览按钮) | +| 脚本审核(预览模式) | P0 | Wct5R | 展开脚本内容+AI分析 | +| 视频审核决策台 | P0 | 2u8Bq | 视频播放+问题标记+决策 | +| Brief配置中心(列表页) | P0 | Nicby | 待配置/已配置列表 | +| Brief配置详情(待配置) | P0 | jRsW5 | 上传Brief+配置规则 | +| Brief配置详情(已配置) | P0 | b06fU | 查看/编辑配置 | +| 达人管理 | P1 | 5cFMX | 达人列表+邀请 | +| 邀请达人弹窗 | P1 | ADN10 | 邀请达人模态框 | +| 数据报表 | P1 | An8gw | 项目数据统计 | +| 消息中心 | P1 | PfMR0 | 通知列表 | + +#### 4.2.2 Mobile 端页面 + +| 页面名称 | 优先级 | 设计稿节点ID | 备注 | +| --- | --- | --- | --- | +| 工作台 | P0 | VuH3F | 紧急待办概览 | +| 快捷审核 | P0 | lrHaj | 外出场景审核 | +| 任务列表 | P1 | c6SPa | 任务筛选 | +| 消息中心 | P1 | 9Us9g | 通知列表 | +| 个人中心 | P2 | 8OCZ3 | 个人设置 | + +#### 4.2.3 页面跳转关系 + +``` +侧边栏导航 +├── 工作台 ───────────► 我的项目 [查看] → 项目详情 +│ 紧急待办 [审核] → 审核决策台 +├── 审核台 ───────────► 审核台(列表页) +│ ├── 脚本任务 ────► 脚本审核决策台 ─► 预览脚本按钮 → 预览模式 +│ └── 视频任务 ────► 视频审核决策台 +├── Brief配置 ────────► Brief配置中心(列表页) +│ ├── 待配置项目 ──► Brief配置详情(待配置) ─► 保存 → 已配置列表 +│ └── 已配置项目 ──► Brief配置详情(已配置) +├── 达人管理 ─────────► 邀请按钮 → 邀请达人弹窗 +├── 数据报表 ─────────► 数据报表页 +└── 消息中心 ─────────► 消息中心页 +``` ### 4.3 品牌方端 (Brand) -| 页面名称 | 设备 | 优先级 | 设计稿节点ID | +> **侧边栏导航顺序:** 项目看板 → 创建项目 → 终审台 → 代理商管理 → 规则配置 → AI配置 → 系统设置 + +#### 4.3.1 Desktop 端页面 + +| 页面名称 | 优先级 | 设计稿节点ID | 备注 | | --- | --- | --- | --- | -| 数据看板 | Desktop | P0 | xUM9m | -| AI服务配置 | Desktop | P0 | 4ppiJ | -| 规则配置 | Desktop | P0 | nhHSF | -| 代理商管理 | Desktop | P1 | 2jnnO | -| 终审台 | Desktop | P0 | aePi5 | -| 系统设置 | Desktop | P2 | 4nVj4 | -| 数据看板 | Mobile | P0 | lpVdV | -| 审批中心 | Mobile | P1 | OueOe | -| 消息中心 | Mobile | P2 | 1w9xC | -| 我的 | Mobile | P2 | OJBbT | +| 项目看板 | P0 | xUM9m | 项目列表与数据概览,默认首页 | +| 项目详情数据看板 | P1 | D1O6f | 单项目数据分析 | +| 创建项目 | P0 | fP5rY | 新建项目表单 | +| 终审台(列表页) | P0 | afJEU | 脚本/视频待终审列表 | +| 脚本终审决策台 | P0 | Sw2hw | 简单模式(文件图标+预览按钮) | +| 脚本终审(预览模式) | P0 | cp5CE | 展开脚本内容审核 | +| 视频终审决策台 | P0 | aePi5 | 视频播放+问题标记+决策 | +| 代理商管理 | P1 | 2jnnO | 代理商列表与邀请 | +| 邀请代理商弹窗 | P1 | GyUlM | 邀请代理商模态框 | +| 规则配置 | P0 | nhHSF | 黑名单/白名单管理 | +| AI服务配置 | P0 | 4ppiJ | AI模型与参数配置 | +| 系统设置 | P2 | 4nVj4 | 通用设置、安全、数据导出 | + +#### 4.3.2 Mobile 端页面 + +| 页面名称 | 优先级 | 设计稿节点ID | 备注 | +| --- | --- | --- | --- | +| 数据看板 | P0 | lpVdV | 关键指标概览 | +| 审批中心 | P1 | OueOe | 终审处理 | +| 消息中心 | P2 | 1w9xC | 通知列表 | +| 我的 | P2 | OJBbT | 个人设置 | + +#### 4.3.3 页面跳转关系 + +``` +侧边栏导航 +├── 项目看板 ─────────► 点击项目卡片 → 项目详情数据看板 +├── 创建项目 ─────────► 填写表单 → 保存 → 项目看板 +├── 终审台 ───────────► 终审台(列表页) +│ ├── 脚本任务 ────► 脚本终审决策台 ─► 预览脚本按钮 → 预览模式 +│ └── 视频任务 ────► 视频终审决策台 +├── 代理商管理 ───────► 邀请按钮 → 邀请代理商弹窗 +├── 规则配置 ─────────► 黑名单/白名单管理 +├── AI配置 ───────────► AI服务配置页 +└── 系统设置 ─────────► 系统设置页 +``` --- @@ -407,7 +470,7 @@ module.exports = { --- **文档维护者**: Claude -**最后更新**: 2026-02-05 +**最后更新**: 2026-02-06 --- @@ -417,3 +480,4 @@ module.exports = { | --- | --- | --- | --- | | V1.0 | 2026-02-03 | Claude | 初稿:设计令牌、组件规范、页面清单 | | V1.1 | 2026-02-05 | Claude | **明确两阶段审核页面**:细化达人端页面清单,按脚本阶段/视频阶段分类;新增脚本品牌方不通过(NeF4L)、视频AI不通过(6EX4Z)页面 | +| V1.2 | 2026-02-06 | Claude | **完善品牌方端和代理商端页面清单**:更新侧边栏导航顺序;新增规则配置、脚本终审(预览模式)等页面;补充页面跳转关系图 | diff --git a/User_Role_Interfaces.md b/User_Role_Interfaces.md index 385f14e..159116a 100644 --- a/User_Role_Interfaces.md +++ b/User_Role_Interfaces.md @@ -3,8 +3,8 @@ | 文档类型 | **UI/UX Spec (Interface Definitions)** | | --- | --- | | **项目名称** | 秒思智能审核平台 (AI 营销内容合规审核平台) | -| **版本号** | V1.6 | -| **发布日期** | 2026-02-05 | +| **版本号** | V1.8 | +| **发布日期** | 2026-02-06 | | **关联文档** | RequirementsDoc.md, PRD.md, FeatureSummary.md, DevelopmentPlan.md, AIProviderConfig.md, UIDesign.md, tasks.md | | **侧重** | 角色权限、核心页面布局、交互逻辑 | @@ -22,6 +22,8 @@ | V1.4 | 2026-02-03 | Claude | **新增审核流程进度可视化 UI(F-52)**:达人端任务列表状态标签、任务详情-审核结果区进度条 | | V1.5 | 2026-02-03 | Claude | **扩展审核流程进度可视化至全角色**:代理商审核决策台/快捷审核、品牌方审批中心均可查看进度条 | | V1.6 | 2026-02-05 | Claude | **明确两阶段审核流程**:脚本阶段+视频阶段独立状态;完善任务按钮状态逻辑(查看详情→上传视频);细化结果页按钮(重新提交脚本/重新上传视频);添加历史任务归档规则 | +| V1.7 | 2026-02-06 | Claude | **完善三端导航结构**:品牌方端更新侧边栏导航顺序(项目看板→创建项目→终审台→代理商管理→规则配置→AI配置→系统设置)并新增4.0章节详述页面跳转逻辑;代理商端更新侧边栏导航顺序(工作台→审核台→Brief配置→达人管理→数据报表→消息中心)并补充脚本预览模式跳转;规则配置简化为黑白名单管理;补充所有页面的设计稿节点ID映射 | +| V1.8 | 2026-02-06 | Claude | **申诉次数按任务独立**:每个任务初始1次申诉次数;达人可在个人中心查看各任务申诉次数并申请增加(无需理由);代理商在消息中心/达人管理处理请求(同意/拒绝/忽略,增加次数无上限);新增达人端2.6申诉次数管理页;更新代理商端3.5达人管理增加申诉次数管理功能 | --- @@ -38,7 +40,7 @@ | **脚本/视频提交** | ✅ 任务详情内上传 & 修改 [US-03] | ❌ 不可提交 | ❌ 不可提交 | | **查看 AI 报告** | ✅ 仅查看自己的 [US-07] | ✅ 查看所管辖达人的 | ✅ 查看所有 | | **审核决策** | ❌ 无权 | ✅ 初审 (驳回/通过) [US-08] | ✅ 终审(可配置)/ 强制通过 [US-09] | -| **申诉功能** | ✅ 发起申诉 (消耗令牌) | ✅ 仲裁申诉 | ❌ 无需申诉 | +| **申诉功能** | ✅ 发起申诉 (消耗任务申诉次数) / 申请增加次数 | ✅ 仲裁申诉 / 管理达人申诉次数 | ❌ 无需申诉 | | **证据链导出** | ❌ 无权 | ✅ 导出所管辖任务 | ✅ 导出全部 [US-12] | | **数据看板** | 仅看个人任务进度 | 整体进度 / 达人排名 | 全局合规率 / 舆情风控 | | **系统配置** | ❌ 无权 | ❌ 无权 | ✅ 规则库/阈值/白名单/区域合规/审核流程 [US-10A/US-10B] | @@ -82,19 +84,21 @@ │ 工作台 │ 审核 │ 任务 │ 消息 │ 我的│ └─────────┴─────────┴─────────┴─────────┴─────┘ ``` -> 📱 **移动端定位:** 外出场景下的紧急审核处理、进度查看、消息通知,复杂配置操作引导至桌面端完成 +> 📱 **移动端定位:** 外出场景下的紧急审核处理、进度查看、消息通知,复杂配置操作(如 Brief 配置)引导至桌面端完成 ### 品牌方端 (Desktop Sidebar) ``` ┌──────────────────────────────────────────────┐ -│ 📊 数据看板 (Analytics) │ -│ 👥 代理商管理 │ -│ 🤖 AI 配置 │ -│ ✅ 终审台 (Final Review) │ -│ 📁 创建项目 (Create Project) │ +│ 📊 项目看板 (Dashboard) ← 默认首页 │ +│ ➕ 创建项目 (Create Project) │ +│ 🛡️ 终审台 (Final Review) │ +│ 🏢 代理商管理 (Agency) │ +│ 📋 规则配置 (Rules) ← 黑白名单 │ +│ 🤖 AI配置 (AI Settings) │ │ ⚙️ 系统设置 (Settings) │ └──────────────────────────────────────────────┘ ``` +> **导航说明:** 规则配置为独立入口,包含黑名单/白名单管理;终审台仅当终审开启时显示待审任务 ### 品牌方端 (Mobile Tab Bar) 📱 ``` @@ -322,12 +326,12 @@ * **申诉入口:** * 在每一条报错旁边提供 `[ 申诉 ]` 按钮 * **申诉弹窗:** - * 显示剩余令牌数量(如:剩余 2 次) - * 令牌配额基于达人信用评分(历史表现越好,配额越高) + * 显示当前任务剩余申诉次数(如:本任务剩余 1 次) + * 每个任务初始申诉次数为 **1次**,不同任务独立计算 * 提供理由输入框(必填,≥ 10 字) * 可上传补充证据(截图、链接等) * **申诉流程:** 提交 → 代理商仲裁 → 结果通知 - * **令牌返还:** 申诉成功后令牌自动返还 + * **次数不足时:** 显示"申诉次数不足"提示,提供 `[申请增加]` 按钮引导达人向代理商申请 ### 2.4 消息通知中心 (Notification Center) @@ -359,6 +363,8 @@ * 操作按钮:`[同意]` `[拒绝]` * 同意后自动加入该代理商的达人列表,可接收任务分配 * 💬 **申诉结果:** "您的申诉已通过,AI 已学习您的反馈" + * ✅ **申诉次数增加同意:** "代理商【星辰传媒】已同意增加【XX任务】的申诉次数" + * ❌ **申诉次数增加拒绝:** "代理商【星辰传媒】拒绝了【XX任务】的申诉次数增加请求" * **通知渠道:** * H5 内消息中心(必选) @@ -377,6 +383,28 @@ * 平均修改轮次:脚本平均修改次数、视频平均修改次数 * **证书导出:** 支持导出"合规达人"认证徽章(达到一定通过率后解锁) +### 2.6 申诉次数管理页 (Appeal Quota) + +**入口:** 达人端 → 个人中心 → 申诉次数 + +**功能描述:** 达人查看所有任务的申诉次数,并可向代理商申请增加。 + +* **任务申诉次数列表:** + * 显示所有进行中任务的申诉次数 + * 每条记录包含:任务名称、代理商名称、剩余次数、已使用次数 + * 支持按代理商/任务状态筛选 + +* **申请增加次数:** + * 点击某任务的 `[申请增加]` 按钮 + * 无需填写理由,直接发送申请给该任务的代理商 + * 申请状态:待处理 / 已同意 / 已拒绝 + +* **申诉次数规则说明:** + * 每个任务初始申诉次数:**1次** + * 不同任务的申诉次数独立计算 + * 代理商可增加的次数无上限 + * 申诉次数用完时,无法提交新的申诉 + --- ## 3. 界面详解:代理商端 (The Agency Console) @@ -400,48 +428,50 @@ #### 侧边栏导航 ``` 秒思 (Logo) -├── 工作台 ← 默认首页 -├── Brief配置 → Brief配置中心(列表页) -├── 审核台 → 审核台(列表页) -├── 达人管理 → 达人管理(列表页) -├── 数据面板 → 统计报表页 -└── 设置 → 设置页 +├── 工作台 ← 默认首页 (RX8V9) +├── 审核台 → 审核台(列表页) (zjiCT) +├── Brief配置 → Brief配置中心(列表页) (Nicby) +├── 达人管理 → 达人管理页面 (5cFMX) +├── 数据报表 → 数据报表页 (An8gw) +└── 消息中心 → 消息中心页 (PfMR0) ``` #### 页面跳转关系图 **工作台跳转:** ``` -工作台 -├── 我的项目 [查看] ──────────► 项目详情页 +工作台 (RX8V9) +├── 我的项目 [查看] ──────────► 项目详情页 (C7wfV) └── 紧急待办 - ├── 品牌新任务 [查看详情] ────► 项目详情页(待配置Brief) - ├── 脚本审核任务 [审核脚本] ──► 脚本审核决策台 - ├── 视频审核任务 [审核视频] ──► 视频审核决策台 + ├── 品牌新任务 [查看详情] ────► Brief配置详情页(待配置) (jRsW5) + ├── 脚本审核任务 [审核脚本] ──► 脚本审核决策台 (f8HX9) + ├── 视频审核任务 [审核视频] ──► 视频审核决策台 (2u8Bq) └── 申诉仲裁任务 [仲裁] ──────► 审核决策台(带申诉标签) ``` -**Brief配置跳转:** -``` -Brief配置中心(列表页) -├── 待配置列表 -│ └── 项目 ──点击──► 详情页(待配置) ──保存后──► 移到已配置列表 -└── 已配置列表 - └── 项目 ──点击──► 详情页(已配置) ──可更换文件/编辑规则 -``` - **审核台跳转:** ``` -审核台(列表页) +审核台(列表页) (zjiCT) ├── 脚本审核列表 -│ └── 任务 [审核] ──► 脚本审核决策台 ──[通过/驳回]──► 返回列表 +│ └── 任务 [审核] ──► 脚本审核决策台 (f8HX9) +│ ├── [预览脚本] ──► 预览模式 (Wct5R) +│ └── [通过/驳回] ──► 返回列表 └── 视频审核列表 - └── 任务 [审核] ──► 视频审核决策台 ──[通过/驳回/强制通过]──► 返回列表 + └── 任务 [审核] ──► 视频审核决策台 (2u8Bq) ──[通过/驳回/强制通过]──► 返回列表 +``` + +**Brief配置跳转:** +``` +Brief配置中心(列表页) (Nicby) +├── 待配置列表 +│ └── 项目 ──点击──► 详情页(待配置) (jRsW5) ──保存后──► 移到已配置列表 +└── 已配置列表 + └── 项目 ──点击──► 详情页(已配置) (b06fU) ──可更换文件/编辑规则 ``` **项目详情页:** ``` -项目详情页 +项目详情页 (C7wfV) ├── Brief信息 [下载] [预览] ├── 已分配达人列表(显示各达人当前状态) └── [+ 分配达人] ──► 弹窗选择达人 @@ -449,9 +479,9 @@ Brief配置中心(列表页) **达人管理:** ``` -达人管理(列表页) +达人管理 (5cFMX) ├── 达人列表(显示达人信息、粉丝数、平台、状态) -└── [+ 添加达人] ──► 弹窗填写达人信息 +└── [+ 邀请达人] ──► 邀请达人弹窗 (ADN10) ``` ### 3.1 工作台 (Dashboard) @@ -616,6 +646,19 @@ Brief配置中心(列表页) * 批量发送催促通知 * 批量导出达人数据 +* **申诉次数管理:** + * 在达人列表中点击某达人,可展开查看该达人的所有任务 + * 每个任务显示:任务名称、剩余申诉次数、已使用次数 + * 点击 `[+1]` 按钮可直接为该任务增加1次申诉次数 + * 支持批量增加申诉次数 + * **增加次数无上限** + +* **申诉次数请求处理:** + * 当达人申请增加申诉次数时,代理商在消息中心收到通知 + * 通知内容:"达人【XXX】申请增加【XXX任务】的申诉次数" + * 点击通知可直接处理:`[同意]` / `[拒绝]` + * 也可在达人管理页面主动为任意任务增加申诉次数 + ### 3.6 数据报表 (Analytics Reports) * **项目维度:** @@ -764,12 +807,66 @@ Brief配置中心(列表页) **设计目标:** 监管、配置、数据沉淀、风险预警。 **核心设备:** 桌面端 (Desktop Web) 为主,移动端 (Mobile) 为辅。 -### 4.1 数据看板 (Executive Dashboard) ✨ *核心功能* +### 4.0 页面结构与跳转逻辑 -品牌方需要一目了然地掌握整体合规状况。 +#### 侧边栏导航 +``` +秒思 (Logo) +├── 项目看板 ← 默认首页 +├── 创建项目 → 创建项目页面 +├── 终审台 → 终审台(列表页) +├── 代理商管理 → 代理商管理页面 +├── 规则配置 → 规则配置页面 (黑白名单) +├── AI配置 → AI服务配置页面 +└── 系统设置 → 系统设置页面 +``` + +#### 页面跳转关系图 + +**项目看板跳转:** +``` +项目看板 (xUM9m) +├── 项目卡片 [点击] ──────────► 项目详情数据看板 (D1O6f) +└── [+ 创建项目] ────────────► 创建项目页面 (fP5rY) +``` + +**终审台跳转:** +``` +终审台(列表页) (afJEU) +├── 脚本待审列表 +│ └── 任务 [审核] ──► 脚本终审决策台 (Sw2hw) ──[预览脚本]──► 预览模式 (cp5CE) +│ ──[通过/驳回]──► 返回列表 +└── 视频待审列表 + └── 任务 [审核] ──► 视频终审决策台 (aePi5) ──[通过/驳回]──► 返回列表 +``` + +**代理商管理:** +``` +代理商管理 (2jnnO) +├── 代理商列表(显示代理商信息、项目数、状态) +└── [+ 邀请代理商] ──► 邀请代理商弹窗 (GyUlM) +``` + +**规则配置:** +``` +规则配置 (nhHSF) +├── 黑名单管理 ──► 添加/编辑/删除违禁词 +└── 白名单管理 ──► 添加/编辑/删除豁免词 +``` + +### 4.1 项目看板 (Project Dashboard) ✨ *核心功能* + +**界面映射:** `pencil-new.pen` → "品牌方端 - 项目看板" (xUM9m) + +品牌方需要一目了然地掌握整体合规状况和项目进展。 > 对应 PRD 成功指标:人工投入时长、初审通过率、召回率/误报率、舆情一致性 +**项目卡片列表:** +* 展示所有营销项目卡片 +* 每个卡片显示:项目名称、状态、脚本数/视频数、截止日期 +* 点击卡片进入项目详情数据看板 + **顶部指标卡片:** ``` ┌─────────────┬─────────────┬─────────────┬─────────────┬─────────────┐ @@ -792,30 +889,25 @@ Brief配置中心(列表页) * 🟠 **关注:** "达人B连续3次提交未通过,建议沟通" * 🟡 **舆情:** "本周舆情风险拦截数异常上升,建议检查阈值设置" -### 4.2 全局规则配置 (Rule Engine) [US-10A/US-10B] +### 4.2 规则配置 (Rule Configuration) [US-10A/US-10B] -* **黑白名单管理:** - * **禁用词库:** 支持分类管理(广告法 / 平台规则 / 品牌私有) - * **竞品列表:** 上传竞品 Logo 图库,支持相似度阈值设置 - * **白名单:** 允许特定达人/场景豁免某些规则 - * **特例记录:** 查看从审核台记录的豁免条款,支持确认/撤销 +**界面映射:** `pencil-new.pen` → "品牌方端 - 规则配置" (nhHSF) -* **区域合规配置:** - * 支持按投放地区切换法规版本(中国大陆 / 港澳台 / 海外) - * 不同地区规则库独立管理 - * 切换时自动校验现有 Brief 与新规则的兼容性 +**入口位置:** 品牌方端侧边栏 → 规则配置 -* **舆情阈值设置:** - * 调整 AI 对"油腻"、"性感"、"争议话题"的敏感度 - * 支持 High / Medium / Low 三档 - * 支持按平台差异化配置(抖音 vs 小红书) - * ⚠️ **提示:** 舆情风险仅作提示,不作为强制拦截依据 +规则配置页面提供黑名单和白名单的管理功能。 -* **规则版本管理:** - * 规则变更历史可追溯(含变更人、变更时间、变更内容) - * 支持回滚到历史版本 - * 变更需审批生效(防止误操作) - * **平台规则同步:** 抖音/小红书规则变更时,系统在 1 工作日内更新并通知 +* **黑名单管理:** + * **违禁词列表:** 添加/编辑/删除违禁词 + * 支持批量导入导出 + * 显示违禁词条数统计 + +* **白名单管理:** + * **豁免词列表:** 添加/编辑/删除豁免词 + * 支持批量导入导出 + * 显示豁免词条数统计 + +> **说明:** 其他高级规则配置(如区域合规、舆情阈值)在系统设置中进行配置 ### 4.3 创建项目 (Create Project) ⭐ 新增 diff --git a/frontend/app/agency/briefs/[id]/page.tsx b/frontend/app/agency/briefs/[id]/page.tsx new file mode 100644 index 0000000..36d9d76 --- /dev/null +++ b/frontend/app/agency/briefs/[id]/page.tsx @@ -0,0 +1,316 @@ +'use client' + +import { useState } from 'react' +import { useRouter, useParams } from 'next/navigation' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card' +import { Button } from '@/components/ui/Button' +import { SuccessTag, WarningTag, ErrorTag } from '@/components/ui/Tag' +import { + ArrowLeft, + FileText, + Upload, + CheckCircle, + Plus, + X, + Save, + Sparkles, + Target, + Ban, + AlertTriangle +} from 'lucide-react' + +// 模拟 Brief 详情 +const mockBrief = { + id: 'brief-001', + projectName: 'XX品牌618推广', + brandName: 'XX护肤品牌', + status: 'configured', + fileName: 'XX品牌618推广Brief.pdf', + uploadedAt: '2026-02-01', + configuredAt: '2026-02-02', + sellingPoints: [ + { id: 'sp1', content: 'SPF50+ PA++++', required: true }, + { id: 'sp2', content: '轻薄质地,不油腻', required: true }, + { id: 'sp3', content: '延展性好,易推开', required: false }, + { id: 'sp4', content: '适合敏感肌', required: false }, + { id: 'sp5', content: '夏日必备防晒', required: true }, + ], + blacklistWords: [ + { id: 'bw1', word: '最好', reason: '绝对化用语' }, + { id: 'bw2', word: '第一', reason: '绝对化用语' }, + { id: 'bw3', word: '神器', reason: '夸大宣传' }, + { id: 'bw4', word: '完美', reason: '绝对化用语' }, + ], + aiParsedContent: { + productName: 'XX品牌防晒霜', + targetAudience: '18-35岁女性', + contentRequirements: '需展示产品质地、使用效果', + restrictions: '不可提及竞品,不可使用绝对化用语', + }, +} + +export default function BriefConfigPage() { + const router = useRouter() + const params = useParams() + const [brief, setBrief] = useState(mockBrief) + const [newSellingPoint, setNewSellingPoint] = useState('') + const [newBlacklistWord, setNewBlacklistWord] = useState('') + const [isAIParsing, setIsAIParsing] = useState(false) + const [isSaving, setIsSaving] = useState(false) + + const handleAIParse = async () => { + setIsAIParsing(true) + // 模拟 AI 解析 + await new Promise(resolve => setTimeout(resolve, 2000)) + setIsAIParsing(false) + alert('AI 解析完成!') + } + + const addSellingPoint = () => { + if (!newSellingPoint.trim()) return + setBrief(prev => ({ + ...prev, + sellingPoints: [...prev.sellingPoints, { id: `sp${Date.now()}`, content: newSellingPoint, required: false }] + })) + setNewSellingPoint('') + } + + const removeSellingPoint = (id: string) => { + setBrief(prev => ({ + ...prev, + sellingPoints: prev.sellingPoints.filter(sp => sp.id !== id) + })) + } + + const toggleRequired = (id: string) => { + setBrief(prev => ({ + ...prev, + sellingPoints: prev.sellingPoints.map(sp => + sp.id === id ? { ...sp, required: !sp.required } : sp + ) + })) + } + + const addBlacklistWord = () => { + if (!newBlacklistWord.trim()) return + setBrief(prev => ({ + ...prev, + blacklistWords: [...prev.blacklistWords, { id: `bw${Date.now()}`, word: newBlacklistWord, reason: '自定义' }] + })) + setNewBlacklistWord('') + } + + const removeBlacklistWord = (id: string) => { + setBrief(prev => ({ + ...prev, + blacklistWords: prev.blacklistWords.filter(bw => bw.id !== id) + })) + } + + const handleSave = async () => { + setIsSaving(true) + await new Promise(resolve => setTimeout(resolve, 1000)) + setIsSaving(false) + alert('配置已保存!') + } + + return ( +
+ {/* 顶部导航 */} +
+ +
+

{brief.projectName}

+

{brief.brandName}

+
+ +
+ +
+ {/* 左侧:Brief 文件和 AI 解析 */} +
+ {/* Brief 文件 */} + + + + + Brief 文件 + + + +
+
+ +
+

{brief.fileName}

+

上传于 {brief.uploadedAt}

+
+
+
+ + +
+
+
+
+ + {/* AI 解析结果 */} + + + + + + AI 解析结果 + + + + + +
+
+

产品名称

+

{brief.aiParsedContent.productName}

+
+
+

目标人群

+

{brief.aiParsedContent.targetAudience}

+
+
+

内容要求

+

{brief.aiParsedContent.contentRequirements}

+
+
+

限制条件

+

{brief.aiParsedContent.restrictions}

+
+
+
+
+ + {/* 卖点配置 */} + + + + + 卖点配置 + + {brief.sellingPoints.length} 个卖点 + + + + + {brief.sellingPoints.map((sp) => ( +
+ + {sp.content} + +
+ ))} +
+ setNewSellingPoint(e.target.value)} + placeholder="添加新卖点..." + className="flex-1 px-3 py-2 border border-border-subtle rounded-lg bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo" + onKeyDown={(e) => e.key === 'Enter' && addSellingPoint()} + /> + +
+
+
+
+ + {/* 右侧:违禁词配置 */} +
+ + + + + 违禁词配置 + + {brief.blacklistWords.length} 个 + + + + + {brief.blacklistWords.map((bw) => ( +
+
+ 「{bw.word}」 + {bw.reason} +
+ +
+ ))} +
+ setNewBlacklistWord(e.target.value)} + placeholder="添加违禁词..." + className="flex-1 px-3 py-2 border border-border-subtle rounded-lg bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo" + onKeyDown={(e) => e.key === 'Enter' && addBlacklistWord()} + /> + +
+
+
+ + {/* 配置提示 */} +
+
+ +
+

配置说明

+
    +
  • • 必选卖点必须在内容中提及
  • +
  • • 违禁词会触发 AI 审核警告
  • +
  • • 修改配置后需重新保存
  • +
+
+
+
+
+
+
+ ) +} diff --git a/frontend/app/agency/briefs/page.tsx b/frontend/app/agency/briefs/page.tsx new file mode 100644 index 0000000..274c1cb --- /dev/null +++ b/frontend/app/agency/briefs/page.tsx @@ -0,0 +1,215 @@ +'use client' + +import { useState } from 'react' +import Link from 'next/link' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card' +import { Button } from '@/components/ui/Button' +import { SuccessTag, PendingTag, WarningTag } from '@/components/ui/Tag' +import { + FileText, + Search, + Filter, + Clock, + CheckCircle, + AlertTriangle, + ChevronRight, + Settings +} from 'lucide-react' + +// 模拟 Brief 列表 +const mockBriefs = [ + { + id: 'brief-001', + projectName: 'XX品牌618推广', + brandName: 'XX护肤品牌', + status: 'configured', + uploadedAt: '2026-02-01', + configuredAt: '2026-02-02', + creatorCount: 15, + sellingPoints: 5, + blacklistWords: 12, + }, + { + id: 'brief-002', + projectName: '新品口红系列', + brandName: 'XX美妆品牌', + status: 'pending', + uploadedAt: '2026-02-05', + configuredAt: null, + creatorCount: 0, + sellingPoints: 0, + blacklistWords: 0, + }, + { + id: 'brief-003', + projectName: '护肤品秋季活动', + brandName: 'XX护肤品牌', + status: 'configured', + uploadedAt: '2025-09-15', + configuredAt: '2025-09-16', + creatorCount: 10, + sellingPoints: 4, + blacklistWords: 8, + }, +] + +function StatusTag({ status }: { status: string }) { + if (status === 'configured') return 已配置 + if (status === 'pending') return 待配置 + return 处理中 +} + +export default function AgencyBriefsPage() { + const [searchQuery, setSearchQuery] = useState('') + const [statusFilter, setStatusFilter] = useState('all') + + const filteredBriefs = mockBriefs.filter(brief => { + const matchesSearch = brief.projectName.toLowerCase().includes(searchQuery.toLowerCase()) || + brief.brandName.toLowerCase().includes(searchQuery.toLowerCase()) + const matchesStatus = statusFilter === 'all' || brief.status === statusFilter + return matchesSearch && matchesStatus + }) + + const pendingCount = mockBriefs.filter(b => b.status === 'pending').length + const configuredCount = mockBriefs.filter(b => b.status === 'configured').length + + return ( +
+ {/* 页面标题 */} +
+
+

Brief 配置

+

配置项目 Brief,设置审核规则

+
+
+ + {pendingCount} 待配置 + + + {configuredCount} 已配置 + +
+
+ + {/* 搜索和筛选 */} +
+
+ + setSearchQuery(e.target.value)} + className="w-full pl-10 pr-4 py-2 border border-border-subtle rounded-lg bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo" + /> +
+
+ + + +
+
+ + {/* Brief 列表 */} +
+ {filteredBriefs.map((brief) => ( + + + +
+
+
+ {brief.status === 'configured' ? ( + + ) : ( + + )} +
+
+
+

{brief.projectName}

+ +
+
+ {brief.brandName} + + + 上传于 {brief.uploadedAt} + +
+
+
+ +
+ {brief.status === 'configured' && ( +
+
+
{brief.sellingPoints}
+
卖点
+
+
+
{brief.blacklistWords}
+
违禁词
+
+
+
{brief.creatorCount}
+
达人
+
+
+ )} + +
+
+
+
+ + ))} +
+ + {filteredBriefs.length === 0 && ( +
+ +

暂无匹配的 Brief

+
+ )} +
+ ) +} diff --git a/frontend/app/agency/creators/page.tsx b/frontend/app/agency/creators/page.tsx new file mode 100644 index 0000000..434e0f8 --- /dev/null +++ b/frontend/app/agency/creators/page.tsx @@ -0,0 +1,441 @@ +'use client' + +import { useState } from 'react' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card' +import { Button } from '@/components/ui/Button' +import { Modal } from '@/components/ui/Modal' +import { SuccessTag, PendingTag, WarningTag } from '@/components/ui/Tag' +import { + Search, + Plus, + Users, + TrendingUp, + TrendingDown, + Mail, + Copy, + CheckCircle, + Clock, + MoreVertical, + FileText, + Video, + ChevronDown, + ChevronRight, + PlusCircle +} from 'lucide-react' + +// 任务类型 +interface CreatorTask { + id: string + name: string + appealRemaining: number + appealUsed: number + status: 'in_progress' | 'completed' +} + +// 达人类型 +interface Creator { + id: string + name: string + email: string + avatar: null + status: string + projectCount: number + scriptCount: { total: number; passed: number } + videoCount: { total: number; passed: number } + passRate: number + trend: string + joinedAt: string + tasks: CreatorTask[] +} + +// 模拟达人列表 +const mockCreators: Creator[] = [ + { + id: 'creator-001', + name: '小美护肤', + email: 'xiaomei@example.com', + avatar: null, + status: 'active', + projectCount: 5, + scriptCount: { total: 12, passed: 10 }, + videoCount: { total: 10, passed: 8 }, + passRate: 85, + trend: 'up', + joinedAt: '2025-08-15', + tasks: [ + { id: 'task-001', name: '夏日护肤推广', appealRemaining: 1, appealUsed: 0, status: 'in_progress' }, + { id: 'task-002', name: '防晒霜测评', appealRemaining: 0, appealUsed: 1, status: 'in_progress' }, + ], + }, + { + id: 'creator-002', + name: '美妆Lisa', + email: 'lisa@example.com', + avatar: null, + status: 'active', + projectCount: 3, + scriptCount: { total: 8, passed: 7 }, + videoCount: { total: 6, passed: 5 }, + passRate: 80, + trend: 'stable', + joinedAt: '2025-10-20', + tasks: [ + { id: 'task-003', name: '新品口红试色', appealRemaining: 2, appealUsed: 0, status: 'in_progress' }, + ], + }, + { + id: 'creator-003', + name: '健身教练王', + email: 'wang@example.com', + avatar: null, + status: 'active', + projectCount: 2, + scriptCount: { total: 5, passed: 5 }, + videoCount: { total: 4, passed: 4 }, + passRate: 100, + trend: 'up', + joinedAt: '2025-12-01', + tasks: [ + { id: 'task-004', name: '健身器材使用教程', appealRemaining: 1, appealUsed: 0, status: 'in_progress' }, + ], + }, + { + id: 'creator-004', + name: '时尚达人', + email: 'fashion@example.com', + avatar: null, + status: 'pending', + projectCount: 0, + scriptCount: { total: 0, passed: 0 }, + videoCount: { total: 0, passed: 0 }, + passRate: 0, + trend: 'stable', + joinedAt: '2026-02-05', + tasks: [], + }, +] + +function StatusTag({ status }: { status: string }) { + if (status === 'active') return 已激活 + if (status === 'pending') return 待接受 + return 已暂停 +} + +export default function AgencyCreatorsPage() { + const [searchQuery, setSearchQuery] = useState('') + const [showInviteModal, setShowInviteModal] = useState(false) + const [inviteEmail, setInviteEmail] = useState('') + const [inviteLink, setInviteLink] = useState('') + const [expandedCreators, setExpandedCreators] = useState([]) + const [creators, setCreators] = useState(mockCreators) + + const filteredCreators = creators.filter(creator => + creator.name.toLowerCase().includes(searchQuery.toLowerCase()) || + creator.email.toLowerCase().includes(searchQuery.toLowerCase()) + ) + + // 切换展开状态 + const toggleExpand = (creatorId: string) => { + setExpandedCreators(prev => + prev.includes(creatorId) + ? prev.filter(id => id !== creatorId) + : [...prev, creatorId] + ) + } + + // 增加申诉次数 + const handleAddAppealQuota = (creatorId: string, taskId: string) => { + setCreators(prev => prev.map(creator => { + if (creator.id === creatorId) { + return { + ...creator, + tasks: creator.tasks.map(task => { + if (task.id === taskId) { + return { ...task, appealRemaining: task.appealRemaining + 1 } + } + return task + }), + } + } + return creator + })) + } + + const handleInvite = () => { + if (!inviteEmail.trim()) { + alert('请输入达人邮箱') + return + } + const link = `https://miaosi.app/invite/creator/${Date.now()}` + setInviteLink(link) + } + + const handleCopyLink = () => { + navigator.clipboard.writeText(inviteLink) + alert('链接已复制') + } + + const handleSendInvite = () => { + alert(`邀请已发送至 ${inviteEmail}`) + setShowInviteModal(false) + setInviteEmail('') + setInviteLink('') + } + + return ( +
+ {/* 页面标题 */} +
+
+

达人管理

+

管理合作达人,查看达人绩效数据

+
+ +
+ + {/* 统计卡片 */} +
+ + +
+
+

总达人数

+

{mockCreators.length}

+
+
+ +
+
+
+
+ + +
+
+

已激活

+

{mockCreators.filter(c => c.status === 'active').length}

+
+
+ +
+
+
+
+ + +
+
+

总脚本数

+

{mockCreators.reduce((sum, c) => sum + c.scriptCount.total, 0)}

+
+
+ +
+
+
+
+ + +
+
+

总视频数

+

{mockCreators.reduce((sum, c) => sum + c.videoCount.total, 0)}

+
+
+
+
+
+
+
+ + {/* 搜索 */} +
+ + setSearchQuery(e.target.value)} + className="w-full pl-10 pr-4 py-2 border border-border-subtle rounded-lg bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo" + /> +
+ + {/* 达人列表 */} + + + + + + + + + + + + + + + + + {filteredCreators.map((creator) => { + const isExpanded = expandedCreators.includes(creator.id) + const hasActiveTasks = creator.tasks.filter(t => t.status === 'in_progress').length > 0 + + return ( + <> + + + + + + + + + + + {/* 展开的任务申诉次数管理区域 */} + {isExpanded && hasActiveTasks && ( + + + + )} + + ) + })} + +
达人状态项目数脚本视频通过率加入时间操作
+
+ {/* 展开按钮 */} + {hasActiveTasks ? ( + + ) : ( +
+ )} +
+ {creator.name[0]} +
+
+
{creator.name}
+
{creator.email}
+
+
+
+ + {creator.projectCount} + {creator.scriptCount.passed} + /{creator.scriptCount.total} + + {creator.videoCount.passed} + /{creator.videoCount.total} + + {creator.status === 'active' && creator.passRate > 0 ? ( +
+ = 90 ? 'text-accent-green' : creator.passRate >= 80 ? 'text-accent-indigo' : 'text-orange-400'}`}> + {creator.passRate}% + + {creator.trend === 'up' && } + {creator.trend === 'down' && } +
+ ) : ( + - + )} +
{creator.joinedAt} + +
+
+
任务申诉次数管理
+
+ {creator.tasks.filter(t => t.status === 'in_progress').map(task => ( +
+
+ {task.name} +
+ 剩余: {task.appealRemaining} + | + 已用: {task.appealUsed} +
+
+ +
+ ))} +
+
+
+
+
+ + {/* 邀请达人弹窗 */} + { setShowInviteModal(false); setInviteEmail(''); setInviteLink(''); }} title="邀请达人"> +
+

输入达人邮箱,系统将发送邀请链接。

+ +
+ +
+ setInviteEmail(e.target.value)} + placeholder="creator@example.com" + className="flex-1 px-4 py-2 border border-border-subtle rounded-lg bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo" + /> + +
+
+ + {inviteLink && ( +
+
+ 邀请链接 + +
+

{inviteLink}

+
+ )} + +
+ + +
+
+
+
+ ) +} diff --git a/frontend/app/agency/messages/page.tsx b/frontend/app/agency/messages/page.tsx new file mode 100644 index 0000000..7fb6ccb --- /dev/null +++ b/frontend/app/agency/messages/page.tsx @@ -0,0 +1,315 @@ +'use client' + +import { useState } from 'react' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card' +import { Button } from '@/components/ui/Button' +import { SuccessTag, WarningTag, ErrorTag, PendingTag } from '@/components/ui/Tag' +import { + Bell, + CheckCircle, + XCircle, + AlertTriangle, + FileText, + Video, + Users, + Clock, + Check, + MoreVertical, + PlusCircle +} from 'lucide-react' + +// 消息类型 +interface Message { + id: string + type: string + title: string + content: string + time: string + read: boolean + icon: typeof Bell + iconColor: string + bgColor: string + // 申诉次数请求专用字段 + appealRequest?: { + creatorName: string + taskName: string + taskId: string + status: 'pending' | 'approved' | 'rejected' + } +} + +// 模拟消息列表 +const mockMessages: Message[] = [ + { + id: 'msg-001', + type: 'appeal_quota_request', + title: '申诉次数申请', + content: '达人「李小红」申请增加「618美妆推广视频」的申诉次数', + time: '5分钟前', + read: false, + icon: PlusCircle, + iconColor: 'text-accent-amber', + bgColor: 'bg-accent-amber/20', + appealRequest: { + creatorName: '李小红', + taskName: '618美妆推广视频', + taskId: 'task-001', + status: 'pending', + }, + }, + { + id: 'msg-002', + type: 'task_submitted', + title: '新脚本提交', + content: '达人「小美护肤」提交了「夏日护肤推广脚本」,请及时审核。', + time: '10分钟前', + read: false, + icon: FileText, + iconColor: 'text-accent-indigo', + bgColor: 'bg-accent-indigo/20', + }, + { + id: 'msg-003', + type: 'appeal_quota_request', + title: '申诉次数申请', + content: '达人「美妆达人小王」申请增加「双11护肤品种草」的申诉次数', + time: '30分钟前', + read: false, + icon: PlusCircle, + iconColor: 'text-accent-amber', + bgColor: 'bg-accent-amber/20', + appealRequest: { + creatorName: '美妆达人小王', + taskName: '双11护肤品种草', + taskId: 'task-002', + status: 'pending', + }, + }, + { + id: 'msg-004', + type: 'review_complete', + title: '品牌终审通过', + content: '「新品口红试色」视频已通过品牌方终审。', + time: '1小时前', + read: false, + icon: CheckCircle, + iconColor: 'text-accent-green', + bgColor: 'bg-accent-green/20', + }, + { + id: 'msg-005', + type: 'review_rejected', + title: '品牌终审驳回', + content: '「健身器材开箱」视频被品牌方驳回,原因:违禁词使用。', + time: '2小时前', + read: false, + icon: XCircle, + iconColor: 'text-accent-coral', + bgColor: 'bg-accent-coral/20', + }, + { + id: 'msg-006', + type: 'new_project', + title: '新项目邀请', + content: '您被邀请参与「XX品牌新品推广」项目,请配置 Brief。', + time: '昨天', + read: true, + icon: Users, + iconColor: 'text-purple-400', + bgColor: 'bg-purple-500/20', + }, + { + id: 'msg-007', + type: 'warning', + title: '风险预警', + content: '达人「美妆Lisa」连续2次提交被驳回,建议关注。', + time: '昨天', + read: true, + icon: AlertTriangle, + iconColor: 'text-orange-400', + bgColor: 'bg-orange-500/20', + }, + { + id: 'msg-008', + type: 'task_submitted', + title: '新视频提交', + content: '达人「健身教练王」提交了「健身器材使用教程」视频,请及时审核。', + time: '2天前', + read: true, + icon: Video, + iconColor: 'text-purple-400', + bgColor: 'bg-purple-500/20', + }, +] + +export default function AgencyMessagesPage() { + const [messages, setMessages] = useState(mockMessages) + const [filter, setFilter] = useState<'all' | 'unread'>('all') + + const unreadCount = messages.filter(m => !m.read).length + const pendingAppealRequests = messages.filter(m => m.appealRequest?.status === 'pending').length + + const filteredMessages = filter === 'all' ? messages : messages.filter(m => !m.read) + + const markAsRead = (id: string) => { + setMessages(prev => prev.map(m => m.id === id ? { ...m, read: true } : m)) + } + + const markAllAsRead = () => { + setMessages(prev => prev.map(m => ({ ...m, read: true }))) + } + + // 处理申诉次数请求 + const handleAppealRequest = (messageId: string, action: 'approve' | 'reject') => { + setMessages(prev => prev.map(m => { + if (m.id === messageId && m.appealRequest) { + return { + ...m, + read: true, + appealRequest: { + ...m.appealRequest, + status: action === 'approve' ? 'approved' : 'rejected', + }, + } + } + return m + })) + } + + return ( +
+ {/* 页面标题 */} +
+
+

消息中心

+ {unreadCount > 0 && ( + + {unreadCount} 条未读 + + )} +
+ +
+ + {/* 筛选 */} +
+ + +
+ + {/* 消息列表 */} +
+ {filteredMessages.map((message) => { + const Icon = message.icon + const isAppealRequest = message.type === 'appeal_quota_request' + const appealStatus = message.appealRequest?.status + + return ( + !isAppealRequest && markAsRead(message.id)} + > + +
+
+ +
+
+
+

+ {message.title} +

+ {!message.read && ( + + )} + {/* 申诉请求状态标签 */} + {isAppealRequest && appealStatus === 'approved' && ( + + 已同意 + + )} + {isAppealRequest && appealStatus === 'rejected' && ( + + 已拒绝 + + )} +
+

{message.content}

+

+ + {message.time} +

+ + {/* 申诉次数请求操作按钮 */} + {isAppealRequest && appealStatus === 'pending' && ( +
+ + +
+ )} +
+ {!isAppealRequest && ( + + )} +
+
+
+ ) + })} +
+ + {filteredMessages.length === 0 && ( +
+ +

+ {filter === 'unread' ? '没有未读消息' : '暂无消息'} +

+
+ )} +
+ ) +} diff --git a/frontend/app/agency/reports/page.tsx b/frontend/app/agency/reports/page.tsx new file mode 100644 index 0000000..d21367c --- /dev/null +++ b/frontend/app/agency/reports/page.tsx @@ -0,0 +1,298 @@ +'use client' + +import { useState } from 'react' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card' +import { Button } from '@/components/ui/Button' +import { + BarChart3, + TrendingUp, + TrendingDown, + Download, + Calendar, + FileText, + Video, + Users, + CheckCircle, + XCircle, + Clock +} from 'lucide-react' + +// 模拟数据 +const mockStats = { + totalScripts: 156, + totalVideos: 128, + passRate: 85, + avgReviewTime: 4.2, + trend: { + scripts: '+12%', + videos: '+8%', + passRate: '+3%', + reviewTime: '-15%', + }, +} + +const mockProjectStats = [ + { name: 'XX品牌618推广', scripts: 45, videos: 38, passRate: 92 }, + { name: '新品口红系列', scripts: 32, videos: 28, passRate: 85 }, + { name: '护肤品秋季活动', scripts: 28, videos: 25, passRate: 78 }, + { name: 'XX运动品牌', scripts: 51, videos: 37, passRate: 88 }, +] + +const mockWeeklyData = [ + { day: '周一', submitted: 25, passed: 22, rejected: 3 }, + { day: '周二', submitted: 32, passed: 28, rejected: 4 }, + { day: '周三', submitted: 18, passed: 16, rejected: 2 }, + { day: '周四', submitted: 41, passed: 35, rejected: 6 }, + { day: '周五', submitted: 35, passed: 30, rejected: 5 }, + { day: '周六', submitted: 12, passed: 11, rejected: 1 }, + { day: '周日', submitted: 8, passed: 7, rejected: 1 }, +] + +const mockCreatorRanking = [ + { name: '小美护肤', passRate: 95, total: 28 }, + { name: '健身教练王', passRate: 92, total: 15 }, + { name: '美妆Lisa', passRate: 88, total: 22 }, + { name: '时尚达人', passRate: 82, total: 18 }, + { name: '美食探店', passRate: 78, total: 25 }, +] + +function StatCard({ title, value, unit, trend, icon: Icon, color }: { + title: string + value: number | string + unit?: string + trend?: string + icon: React.ElementType + color: string +}) { + const isPositive = trend?.startsWith('+') || trend?.startsWith('-') && title.includes('时间') + + return ( + + +
+
+

{title}

+
+ {value} + {unit && {unit}} +
+ {trend && ( +
+ {isPositive ? : } + {trend} vs 上周 +
+ )} +
+
+ +
+
+
+
+ ) +} + +export default function AgencyReportsPage() { + const [dateRange, setDateRange] = useState('week') + + return ( +
+ {/* 页面标题 */} +
+
+

数据报表

+

查看审核数据统计和趋势分析

+
+
+
+ + + +
+ +
+
+ + {/* 核心指标 */} +
+ + + + +
+ +
+ {/* 本周趋势 */} + + + + + 审核趋势 + + + +
+ {mockWeeklyData.map((day) => ( +
+
{day.day}
+
+
+
+
+
+
+
+ {day.passed} + / + {day.submitted} +
+
+ ))} +
+
+
+
+ 通过 +
+
+
+ 驳回 +
+
+ + + + {/* 达人排名 */} + + + + + 达人通过率排名 + + + + {mockCreatorRanking.map((creator, index) => ( +
+
+ {index + 1} +
+
+
{creator.name}
+
{creator.total} 条审核
+
+
= 90 ? 'text-accent-green' : creator.passRate >= 80 ? 'text-accent-indigo' : 'text-orange-400'}`}> + {creator.passRate}% +
+
+ ))} +
+
+
+ + {/* 项目统计 */} + + + 项目统计 + + + + + + + + + + + + + + {mockProjectStats.map((project) => ( + + + + + + + + ))} + +
项目名称脚本数视频数通过率通过率分布
{project.name}{project.scripts}{project.videos} + = 90 ? 'text-accent-green' : project.passRate >= 80 ? 'text-accent-indigo' : 'text-orange-400'}`}> + {project.passRate}% + + +
+
= 90 ? 'bg-accent-green' : project.passRate >= 80 ? 'bg-accent-indigo' : 'bg-orange-400'}`} + style={{ width: `${project.passRate}%` }} + /> +
+
+
+
+
+ ) +} diff --git a/frontend/app/agency/review/page.tsx b/frontend/app/agency/review/page.tsx new file mode 100644 index 0000000..e5d57a3 --- /dev/null +++ b/frontend/app/agency/review/page.tsx @@ -0,0 +1,257 @@ +'use client' + +import { useState } from 'react' +import Link from 'next/link' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card' +import { Button } from '@/components/ui/Button' +import { SuccessTag, PendingTag, WarningTag, ErrorTag } from '@/components/ui/Tag' +import { + FileText, + Video, + Search, + Filter, + Clock, + User, + AlertTriangle, + ChevronRight +} from 'lucide-react' + +// 模拟脚本待审列表 +const mockScriptTasks = [ + { + id: 'script-001', + title: '夏日护肤推广脚本', + creatorName: '小美护肤', + projectName: 'XX品牌618推广', + aiScore: 88, + submittedAt: '2026-02-06 14:30', + hasHighRisk: false, + }, + { + id: 'script-002', + title: '新品口红试色脚本', + creatorName: '美妆Lisa', + projectName: 'XX品牌618推广', + aiScore: 72, + submittedAt: '2026-02-06 12:15', + hasHighRisk: true, + }, + { + id: 'script-003', + title: '健身器材推荐脚本', + creatorName: '健身教练王', + projectName: 'XX运动品牌', + aiScore: 95, + submittedAt: '2026-02-06 10:00', + hasHighRisk: false, + }, +] + +// 模拟视频待审列表 +const mockVideoTasks = [ + { + id: 'video-001', + title: '夏日护肤推广', + creatorName: '小美护肤', + projectName: 'XX品牌618推广', + aiScore: 85, + duration: '02:15', + submittedAt: '2026-02-06 15:00', + hasHighRisk: false, + }, + { + id: 'video-002', + title: '新品口红试色', + creatorName: '美妆Lisa', + projectName: 'XX品牌618推广', + aiScore: 68, + duration: '03:42', + submittedAt: '2026-02-06 13:45', + hasHighRisk: true, + }, +] + +function ScoreTag({ score }: { score: number }) { + if (score >= 85) return {score}分 + if (score >= 70) return {score}分 + return {score}分 +} + +function TaskCard({ task, type }: { task: typeof mockScriptTasks[0] | typeof mockVideoTasks[0]; type: 'script' | 'video' }) { + const href = type === 'script' ? `/agency/review/script/${task.id}` : `/agency/review/video/${task.id}` + + return ( + +
+
+
+
+

{task.title}

+ {task.hasHighRisk && ( + + + 高风险 + + )} +
+
+ + + {task.creatorName} + +
+
+ +
+
+ {task.projectName} + + + {task.submittedAt} + +
+ {'duration' in task && ( +
+ 时长: {task.duration} +
+ )} +
+ + ) +} + +export default function AgencyReviewListPage() { + const [searchQuery, setSearchQuery] = useState('') + const [activeTab, setActiveTab] = useState<'all' | 'script' | 'video'>('all') + + const filteredScripts = mockScriptTasks.filter(task => + task.title.toLowerCase().includes(searchQuery.toLowerCase()) || + task.creatorName.toLowerCase().includes(searchQuery.toLowerCase()) + ) + + const filteredVideos = mockVideoTasks.filter(task => + task.title.toLowerCase().includes(searchQuery.toLowerCase()) || + task.creatorName.toLowerCase().includes(searchQuery.toLowerCase()) + ) + + return ( +
+ {/* 页面标题 */} +
+
+

审核台

+

审核达人提交的脚本和视频

+
+
+ 待审核: + + {mockScriptTasks.length} 脚本 + + + {mockVideoTasks.length} 视频 + +
+
+ + {/* 搜索和筛选 */} +
+
+ + setSearchQuery(e.target.value)} + className="w-full pl-10 pr-4 py-2 border border-border-subtle rounded-lg bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo" + /> +
+
+ + + +
+
+ + {/* 任务列表 */} +
+ {/* 脚本待审列表 */} + {(activeTab === 'all' || activeTab === 'script') && ( + + + + + 脚本审核 + + {filteredScripts.length} 条待审 + + + + + {filteredScripts.length > 0 ? ( + filteredScripts.map((task) => ( + + )) + ) : ( +
+ +

暂无待审脚本

+
+ )} +
+
+ )} + + {/* 视频待审列表 */} + {(activeTab === 'all' || activeTab === 'video') && ( + + + + + + + {filteredVideos.length > 0 ? ( + filteredVideos.map((task) => ( + + )) + ) : ( +
+
+ )} +
+
+ )} +
+
+ ) +} diff --git a/frontend/app/agency/review/script/[id]/page.tsx b/frontend/app/agency/review/script/[id]/page.tsx new file mode 100644 index 0000000..417421b --- /dev/null +++ b/frontend/app/agency/review/script/[id]/page.tsx @@ -0,0 +1,358 @@ +'use client' + +import { useState } from 'react' +import { useRouter, useParams } from 'next/navigation' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card' +import { Button } from '@/components/ui/Button' +import { Modal, ConfirmModal } from '@/components/ui/Modal' +import { SuccessTag, WarningTag, ErrorTag } from '@/components/ui/Tag' +import { ReviewSteps, getAgencyReviewSteps } from '@/components/ui/ReviewSteps' +import { + ArrowLeft, + FileText, + CheckCircle, + XCircle, + AlertTriangle, + User, + Clock, + Eye, + Shield +} from 'lucide-react' + +// 模拟脚本任务数据 +const mockScriptTask = { + id: 'script-001', + title: '夏日护肤推广脚本', + creatorName: '小美护肤', + projectName: 'XX品牌618推广', + submittedAt: '2026-02-06 14:30', + aiScore: 88, + status: 'agent_reviewing', + scriptContent: { + opening: '大家好!今天给大家分享一款超级好用的夏日护肤神器~', + productIntro: '这款XX品牌的防晒霜,SPF50+,PA++++,真的是夏天出门必备!质地轻薄不油腻,涂上去清清爽爽的。', + demo: '我先在手背上试一下,大家看,延展性特别好,轻轻一抹就推开了,完全不会搓泥。', + closing: '夏天防晒真的很重要,姐妹们赶紧冲!链接在小黄车1号链接~', + }, + aiAnalysis: { + violations: [ + { id: 'v1', type: '违禁词', content: '神器', suggestion: '建议替换为"好物"或"必备品"', severity: 'medium' }, + ], + complianceChecks: [ + { item: '品牌名称正确', passed: true }, + { item: 'SPF标注准确', passed: true }, + { item: '无绝对化用语', passed: false, note: '"超级好用"建议修改' }, + { item: '引导语规范', passed: true }, + ], + sellingPoints: [ + { point: 'SPF50+ PA++++', covered: true }, + { point: '轻薄质地', covered: true }, + { point: '不油腻', covered: true }, + { point: '延展性好', covered: true }, + ], + }, +} + +function ReviewProgressBar({ taskStatus }: { taskStatus: string }) { + const steps = getAgencyReviewSteps(taskStatus) + const currentStep = steps.find(s => s.status === 'current') + + return ( + + +
+ 审核流程 + + 当前:{currentStep?.label || '代理商审核'} + +
+ +
+
+ ) +} + +export default function AgencyScriptReviewPage() { + const router = useRouter() + const params = useParams() + const [showApproveModal, setShowApproveModal] = useState(false) + const [showRejectModal, setShowRejectModal] = useState(false) + const [showForcePassModal, setShowForcePassModal] = useState(false) + const [rejectReason, setRejectReason] = useState('') + const [forcePassReason, setForcePassReason] = useState('') + const [viewMode, setViewMode] = useState<'simple' | 'preview'>('preview') + + const task = mockScriptTask + + const handleApprove = () => { + setShowApproveModal(false) + alert('已提交品牌方终审!') + router.push('/agency/review') + } + + const handleReject = () => { + if (!rejectReason.trim()) { + alert('请填写驳回原因') + return + } + setShowRejectModal(false) + alert('已驳回') + router.push('/agency/review') + } + + const handleForcePass = () => { + if (!forcePassReason.trim()) { + alert('请填写强制通过原因') + return + } + setShowForcePassModal(false) + alert('已强制通过并提交品牌方终审!') + router.push('/agency/review') + } + + return ( +
+ {/* 顶部导航 */} +
+ +
+

{task.title}

+
+ + + {task.creatorName} + + + + {task.submittedAt} + +
+
+ +
+ + {/* 审核流程进度条 */} + + +
+ {/* 左侧:脚本内容 */} +
+ {viewMode === 'simple' ? ( + + + +

{task.title}

+

点击"展开预览"查看脚本内容

+ +
+
+ ) : ( + + + + + 脚本内容 + + + +
+
开场白
+

{task.scriptContent.opening}

+
+
+
产品介绍
+

{task.scriptContent.productIntro}

+
+
+
使用演示
+

{task.scriptContent.demo}

+
+
+
结尾引导
+

{task.scriptContent.closing}

+
+
+
+ )} +
+ + {/* 右侧:AI 分析面板 */} +
+ {/* AI 评分 */} + + +
+ AI 综合评分 + = 85 ? 'text-accent-green' : task.aiScore >= 70 ? 'text-yellow-400' : 'text-accent-coral'}`}> + {task.aiScore} + +
+
+
+ + {/* 违规检测 */} + + + + + 违规检测 ({task.aiAnalysis.violations.length}) + + + + {task.aiAnalysis.violations.map((v) => ( +
+
+ {v.type} +
+

「{v.content}」

+

{v.suggestion}

+
+ ))} + {task.aiAnalysis.violations.length === 0 && ( +

未发现违规内容

+ )} +
+
+ + {/* 合规检查 */} + + + + + 合规检查 + + + + {task.aiAnalysis.complianceChecks.map((check, idx) => ( +
+ {check.passed ? ( + + ) : ( + + )} +
+ {check.item} + {check.note && ( +

{check.note}

+ )} +
+
+ ))} +
+
+ + {/* 卖点覆盖 */} + + + + + 卖点覆盖 + + + + {task.aiAnalysis.sellingPoints.map((sp, idx) => ( +
+ {sp.covered ? ( + + ) : ( + + )} + {sp.point} +
+ ))} +
+
+
+
+ + {/* 底部决策栏 */} + + +
+
+ 项目:{task.projectName} +
+
+ + + +
+
+
+
+ + {/* 通过确认弹窗 */} + setShowApproveModal(false)} + onConfirm={handleApprove} + title="确认通过" + message="确定要通过此脚本的审核吗?通过后将提交给品牌方进行终审。" + confirmText="确认通过" + /> + + {/* 驳回弹窗 */} + setShowRejectModal(false)} title="驳回审核"> +
+

请填写驳回原因,达人将收到通知并根据您的反馈进行修改。

+
+ +