import { Injectable } from '@angular/core'; import { AiSelectionStage, Activity, BriefScenario, Candidate, CandidateStatus, DeliveryVersion, FieldMapping, GapItem, InteractionLog, LearnedRule, NavItem, NoteDetail, NoteVerdict, Requirement, ResourceSummary, ResourceFile, ResourceIssue, Rule, RuleTestCase, SamplingTask, Step, TaskTimelineItem, TaskRecord, TemplateField, TemplateItem, ViewKey } from './models'; function buildNotes(account: string, styles: string[], startMatch: number): NoteDetail[] { return Array.from({ length: 10 }, (_, index) => { const style = styles[index % styles.length]; const match = Math.max(58, startMatch - index * 2 + (index % 3)); return { id: `${account}-${index + 1}`, title: [ '用了三周的空瓶真实复盘', '通勤包里的急救护肤三件套', '成分党也会喜欢的轻乳霜', '换季敏感肌别再乱叠加', '早八人的极简护肤流程', '这支面霜到底适合谁', '出差三天我只带这几样', '热门配方横评和真实感受', '编辑部最近在用什么', '本周值得看的护肤趋势' ][index], publishTime: `2026-04-${String(28 - index).padStart(2, '0')}`, format: index % 3 === 0 ? '视频' : '图文', style, match, verdict: match >= 82 ? '正向样本' : match >= 76 ? '需复核' : '负向样本', summary: `${account} 近10条内容样本之一,AI 用标题、正文关键词、图片场景和互动反馈判断是否贴合 Brief 的内容风格。`, visualCue: index % 3 === 0 ? '封面为真人出镜或产品实拍,画面信息完整。' : '图片以生活场景和局部细节为主,弱化广告感。', textCue: match >= 80 ? '正文中出现使用周期、肤感、场景、优缺点等真实体验词。' : '正文偏资讯清单或功效罗列,缺少连续使用体验。', mismatch: match >= 80 ? '暂无明显冲突' : '真实体验占比不足,可能和 Brief 的“不要硬广”要求冲突。', keywords: ['真实体验', style, index % 2 === 0 ? '通勤' : '成分', '非硬广'], metrics: { read: `${8 + index * 2}.${index}万`, like: `${1200 + index * 180}`, collect: `${620 + index * 90}`, comment: `${86 + index * 11}` }, aiReason: match >= 80 ? '内容表达自然,场景和产品体验占比高,可作为风格匹配证据。' : '资讯或清单感偏重,真实体验表达不足,建议进入人工复核。' }; }); } function getApiBaseUrl(): string { if (typeof window === 'undefined') { return ''; } const isAngularDevServer = (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') && window.location.port === '4200'; return isAngularDevServer ? 'http://localhost:3000' : ''; } function inferFanTier(fans: string): string { const value = Number.parseFloat(fans); if (fans.includes('万') && value >= 50) { return '50万以上'; } if (fans.includes('万') && value >= 10) { return '10万-50万'; } if (fans.includes('万') && value >= 1) { return '1万-10万'; } return '1万以下'; } function buildCandidate(input: { id: string; platform: string; name: string; category: string; fans: string; price: number; score: number; styleMatch: number; status: CandidateStatus; tags: string[]; reason: string; risk: string; owner: string; supplier: string; dataFreshness: number; priceExpire: string; styles: string[]; startMatch: number; readMedian: string; interactionMedian: string; viralRate: number; replyRate: number; fulfillmentRate: number; naturalTrafficRatio: number; cooperationNoteRatio: number; estimatedCpm: number; audienceProfile: string; contentForms: string[]; recallSources: string[]; serviceLevel: string; brandFit: string; selected?: boolean; }): Candidate { return { id: input.id, platform: input.platform, name: input.name, category: input.category, contentForms: input.contentForms, fanTier: inferFanTier(input.fans), fans: input.fans, price: input.price, score: input.score, styleMatch: input.styleMatch, status: input.status, tags: input.tags, reason: input.reason, risk: input.risk, quota: `${input.platform} 扩展池`, selected: input.selected ?? false, link: `https://demo.tihao.ai/${input.platform}/${input.id}`, owner: input.owner, supplier: input.supplier, dataFreshness: input.dataFreshness, priceExpire: input.priceExpire, audienceProfile: input.audienceProfile, readMedian: input.readMedian, interactionMedian: input.interactionMedian, viralRate: input.viralRate, replyRate: input.replyRate, fulfillmentRate: input.fulfillmentRate, naturalTrafficRatio: input.naturalTrafficRatio, cooperationNoteRatio: input.cooperationNoteRatio, estimatedCpm: input.estimatedCpm, cooperationHistory: '近 12 个月有 2-5 次同类客户合作记录', serviceLevel: input.serviceLevel, brandFit: input.brandFit, recallSources: input.recallSources, ruleHits: ['资源库召回', '内容标签匹配', input.serviceLevel, input.brandFit], scoreBreakdown: [ { label: '需求匹配', value: Math.min(99, input.score + 1) }, { label: '内容表现', value: Math.min(99, input.styleMatch + 2) }, { label: '预算健康', value: input.price <= 8000 ? 92 : input.price <= 15000 ? 82 : 72 }, { label: '服务履约', value: input.fulfillmentRate } ], recentNotes: buildNotes(input.name, input.styles, input.startMatch) }; } @Injectable({ providedIn: 'root' }) export class DemoStateService { activeView: ViewKey = 'workspaceFlow'; currentStep = 1; simpleMode = true; showAdvancedWorkspace = false; uploadedFileName = '某品牌种草合作Brief.docx'; resourcePath = 'D:\\公司优秀媒体资源库\\4月份资源库'; feedbackText = '客户反馈:小红书希望减少泛美妆号,优先生活方式、通勤穿搭、真实测评风格;抖音达人预算再压低15%,保留爆文率高的账号。'; exportState = '待生成'; apiState = 'PostgreSQL/RAG 检索'; tempRuleTitle = '母婴/护肤场景中,近30天评论含“真实有用”的账号加权'; tempRuleImpact = '预计强推荐增加 6 个,需复核减少 2 个'; selectedPlatform = '全部'; selectedStatus: CandidateStatus | '全部' = '全部'; budgetMax = 18000; creatorQuery = '护肤 真实测评 通勤生活'; creatorPlatform = '全部'; creatorCategory = '全部'; creatorContentForm = '全部'; creatorFanTier = '全部'; creatorRisk = '全部'; creatorBudgetMax = 15000; sortMode: '综合分' | '风格匹配' | '报价升序' = '综合分'; selectedCandidateId = 'M-1087'; selectedNoteId = '梨涡测评室-1'; noteVerdictFilter: NoteVerdict | '全部' = '全部'; compareOpen = false; previewAudience: '客户版' | '内部复核版' = '客户版'; selectedResourcePlatform = '小红书'; selectedTaskId = 'TH-202605-001'; selectedRuleId = 'R-003'; ruleSandboxText = '客户要求:真实体验、通勤生活,不要硬广,预算尽量控制在 1 万以内。'; ruleSandboxWeight = 80; ruleSimulationState = '尚未试算'; templatePreviewMode: '客户版' | '内部复核版' | '复盘版' = '客户版'; briefingNote = '目标客户想看的是“工具能不能把经验留下来”,所以 Demo 里要突出可确认、可追溯、可学习,而不是只展示一张漂亮名单。'; selectedLearningScope: '本任务' | '单客户' | '行业' | '长期规则' = '本任务'; templateUploadState = '等待上传,当前使用系统内置推荐表模板'; deliveryChecklist = [ { label: '客户版隐藏供应商与内部风险', detail: '对客只展示平台、账号、报价、推荐理由和内容证据', done: true }, { label: '需复核账号已处理', detail: '导出前强推荐和备选账号不能含未解释风险', done: false }, { label: '报价与档期已由媒介确认', detail: '价格过期或未绑定供应商的账号只能进内部版', done: false }, { label: '反馈学习入口已准备', detail: '客户驳回原因可回流到规则库,不丢失原文', done: true } ]; learningScopes: Array<'本任务' | '单客户' | '行业' | '长期规则'> = ['本任务', '单客户', '行业', '长期规则']; noteVerdicts: Array = ['全部', '正向样本', '需复核', '负向样本']; feedbackPresets = [ '客户临时要求:小红书主推生活方式,不要泛美妆资讯号。', '商务复核:报价超过预算上限的账号先放备选,除非爆文率明显高。', '媒介反馈:近期未更新价格的账号需要先联系供应商确认档期。', '客户偏好:保留真实空瓶、通勤场景、素人感强的内容样本。' ]; creatorQuickPrompts = [ '找小红书护肤生活方式博主,2k-8k,真实测评,不要硬广', '保留 B站专业背书,补 3 个长视频成分科普账号', '抖音预算压低 15%,只保留爆文率高且报价健康的账号', '参考梨涡测评室,扩展 10 个相似风格中腰部博主' ]; aiSelectionStages: AiSelectionStage[] = [ { title: '需求理解', subtitle: '从 Brief 和临时反馈抽取可执行条件', input: '客户、品牌、平台数量、预算、内容风格、排除项、参考博主', aiJudgement: '识别出“小红书主推真实测评/通勤生活,预算 2k-18k,硬广降级”。', humanControl: '商务可编辑需求点、关闭低置信度推断、追加临时需求。', output: '6 个确认需求点,2 个待复核推断', status: '完成', accounts: ['客户A / 新锐护肤品牌'] }, { title: '候选召回', subtitle: '像蒲公英一样从多入口找博主', input: '资源库、关键词、相似博主、历史采纳、平台筛选', aiJudgement: '合并 5 类召回来源,去重后形成 324 个候选账号。', humanControl: '媒介可指定参考账号、锁定平台比例、加入手工收藏账号。', output: '小红书 182、抖音 72、B站 28、微信 18、微博 24', status: '完成', accounts: ['梨涡测评室', '晚间护肤手账', '通勤包里有什么'] }, { title: '硬性过滤', subtitle: '先处理不能推荐或必须降级的账号', input: '黑名单、报价、供应商绑定、报价有效期、近期活跃、合作禁区', aiJudgement: '剔除黑名单和低活跃账号 31 个,报价过期账号进入备选。', humanControl: '媒介可临时放行,但必须填写放行理由。', output: '293 个账号进入评分池', status: '完成', accounts: ['美妆情报局', '护肤趋势观察'] }, { title: '内容采样', subtitle: '抽取近 10 条内容做风格和硬广判断', input: '标题、正文关键词、图片/视频场景、互动质量、评论反馈', aiJudgement: '真实体验、通勤、成分拆解命中率高的账号加权,资讯/硬广感降权。', humanControl: '单条笔记可标为正向、需复核或负向样本。', output: '已完成 14 个账号采样,4 个仍待媒介确认', status: '进行中', accounts: ['梨涡测评室', '敏感肌自救记录', '阿森护肤实验室'] }, { title: '多目标评分', subtitle: '把单账号质量变成可解释分数', input: '需求匹配、内容表现、粉丝画像、预算健康、数据新鲜度、服务履约、风险扣分', aiJudgement: '晚间护肤手账因风格和粉丝画像高匹配进入强推荐,微博资讯号降级。', humanControl: '可调整权重并记录为本次任务规则。', output: 'Top 20 推荐池和 Top 60 备选池', status: '进行中', accounts: ['晚间护肤手账', '配方师小简', '打工人变美清单'] }, { title: '组合配比', subtitle: '避免整份名单同质化', input: '平台比例、头腰尾比例、内容类目、价格带、主推/备选数量', aiJudgement: '控制小红书生活方式占比,保留 B站专业背书和低预算补充位。', humanControl: '可锁定账号、换一批、批量强推/备选/剔除。', output: '客户版 16 个主推 + 12 个备选', status: '待执行', accounts: ['通勤包里有什么', '配方师小简', '周末家居护肤'] }, { title: '交付与学习', subtitle: '输出客户版,并把反馈变成待确认规则', input: '最终名单、推荐理由、风险说明、客户反馈', aiJudgement: '客户反馈只生成待确认规则,不自动污染长期规则库。', humanControl: '商务确认后入库,可限制规则只对单客户生效。', output: '名单 v1.0 + 待确认规则 3 条', status: '待执行', accounts: ['反馈学习规则'] } ]; deliveryVersions: DeliveryVersion[] = [ { version: 'v0.1', title: 'AI 初稿', owner: '系统', time: '2026-05-12 01:20', summary: '按 Brief 生成 104 个候选,输出 Top 20 初稿。', status: '已生成' }, { version: 'v0.3', title: '媒介复核版', owner: '媒介-阿敏', time: '2026-05-12 02:05', summary: '剔除 4 个硬广风险账号,补入 B站专业背书账号。', status: '复核中' }, { version: 'v1.0', title: '客户交付版', owner: '商务-王一', time: '待导出', summary: '隐藏内部风险字段,只保留推荐理由、报价、内容样本和排期建议。', status: '待交付' } ]; deliveryFields = [ { label: '平台', customer: true, internal: true, selected: true }, { label: '账号昵称', customer: true, internal: true, selected: true }, { label: '粉丝量', customer: true, internal: true, selected: true }, { label: '报价', customer: true, internal: true, selected: true }, { label: '推荐理由', customer: true, internal: true, selected: true }, { label: '内容样本', customer: true, internal: true, selected: true }, { label: '供应商', customer: false, internal: true, selected: this.previewAudience === '内部复核版' }, { label: '风险提示', customer: false, internal: true, selected: this.previewAudience === '内部复核版' }, { label: '规则命中', customer: false, internal: true, selected: this.previewAudience === '内部复核版' } ]; resourceHealthItems = [ { label: '报价有效率', value: 82, detail: '报价未过期且供应商可确认' }, { label: '字段完整率', value: 78, detail: '昵称、链接、报价、互动、供应商字段齐全' }, { label: '近30天更新率', value: 69, detail: '本地资源表最近一次更新时间' }, { label: '可联系比例', value: 88, detail: '已绑定媒介负责人或供应商联系人' } ]; resourceFiles: ResourceFile[] = [ { platform: '小红书', name: '小红书4月优质种草资源.xlsx', updateTime: '2026-05-10 18:20', rows: 4286, owner: '媒介-阿敏', missingFields: ['报价有效期'] }, { platform: '抖音', name: '抖音星图护肤达人池.xlsx', updateTime: '2026-05-09 21:10', rows: 2130, owner: '媒介-阿宁', missingFields: ['供应商联系人', '档期'] }, { platform: 'B站', name: 'B站长视频评测UP主.xlsx', updateTime: '2026-05-08 11:30', rows: 684, owner: '媒介-六月', missingFields: ['近30天播放中位数'] }, { platform: '微信', name: '公众号图文资源库.xlsx', updateTime: '2026-04-29 16:45', rows: 516, owner: '媒介-小七', missingFields: ['主页链接', '供应商联系人'] }, { platform: '微博', name: '微博话题扩散账号.xlsx', updateTime: '2026-05-06 14:05', rows: 902, owner: '媒介-小满', missingFields: ['报价有效期', '历史配合度'] } ]; resourceIssues: ResourceIssue[] = [ { id: 'RI-001', platform: '小红书', level: '中', title: '报价有效期缺失', detail: '部分资源只有图文报价,缺少报价有效期,导出前需要媒介确认。', action: '批量标为备选并提醒媒介确认', resolved: false }, { id: 'RI-002', platform: '抖音', level: '高', title: '高报价账号需要预算复核', detail: '客户压预算后,部分抖音账号仍接近预算上限。', action: '进入预算沙盘,优先保留爆文率高账号', resolved: false }, { id: 'RI-003', platform: '微信', level: '高', title: '主页链接不完整', detail: '缺主页链接会影响近10条图文采样和风格比对。', action: '要求资源库补齐链接后再进入强推荐', resolved: false } ]; ruleConflicts = [ { title: '低预算优先 vs 保留头部背书', detail: '客户压预算后,B站专业背书账号报价偏高,需要保留为备选并标注价值。', level: '中' }, { title: '真实体验优先 vs 话题扩散', detail: '微博资讯号适合扩散,但和“不要硬广”存在冲突,客户版建议隐藏。', level: '高' } ]; ruleTestCases: RuleTestCase[] = [ { id: 'RTC-001', label: '命中真实体验', condition: '标题/正文出现空瓶、用了三周、真实反馈等词', action: '风格分 +8,推荐理由补充真实体验证据', enabled: true }, { id: 'RTC-002', label: '命中硬广表达', condition: '正文偏优惠、强卖点、购买引导,缺少使用过程', action: '降级为需复核,客户版隐藏', enabled: true }, { id: 'RTC-003', label: '预算保护', condition: '报价低于预算上限 60%,且互动中位数高于同价位', action: '综合分 +5,进入性价比候选', enabled: false } ]; briefScenarios: BriefScenario[] = [ { id: 'budget-down', label: '客户压预算 15%', description: '报价接近上限的账号降级,低预算高匹配账号加权。', selected: false, impact: '抖音高报价账号转备选,小红书中腰部账号上浮' }, { id: 'life-style', label: '小红书偏生活方式', description: '通勤、生活方式、真实分享类账号优先。', selected: false, impact: '通勤/生活方式标签加权,泛美妆资讯降权' }, { id: 'strict-style', label: '严格排除硬广', description: '近10条内容匹配低于80%的账号进入复核池。', selected: false, impact: '资讯感强、清单感强的账号不进入强推荐' }, { id: 'keep-bilibili', label: '保留 B站专业背书', description: '即使数量少,也保留长视频成分科普账号。', selected: false, impact: 'B站成分科普账号进入备选或强推荐候选' } ]; interactionLogs: InteractionLog[] = [ { time: '10:12', action: 'Brief 解析', detail: '已识别平台、数量、预算和内容风格 6 个需求点。' }, { time: '10:18', action: '规则确认', detail: '启用 5 条经验规则,保留 1 条同质化限制待确认。' }, { time: '10:24', action: '账号复核', detail: '梨涡测评室被标记为强推荐,近10条样本 9 条符合。' } ]; navItems: NavItem[] = [ { key: 'workspaceFlow', label: '提号工作台', note: '上传Brief→导出推荐表' }, { key: 'workspace', label: '提号流程(完整)', note: 'Brief 到推荐名单' }, { key: 'creatorSearch', label: '找博主', note: '像蒲公英一样筛人' }, { key: 'delivery', label: '名单交付', note: '客户版与内部版' }, { key: 'tasks', label: '任务记录', note: '客户需求与版本' }, { key: 'resources', label: '资源库', note: '本地媒体表管理' }, { key: 'rules', label: '规则库', note: '经验与提炼规则' }, { key: 'learning', label: '反馈学习', note: '复盘与自我提升' }, { key: 'templates', label: '模板中心', note: 'Brief 与推荐表' }, { key: 'progressBoard', label: '进度看板', note: '人×任务×进度实时' } ]; simpleNavKeys: ViewKey[] = ['workspaceFlow', 'workspace', 'creatorSearch', 'delivery', 'tasks', 'progressBoard']; businessShortcuts = [ { id: 'fast-list', title: '一键生成可看名单', detail: '自动确认需求、运行规则、完成采样,先拿到可讨论的名单。' }, { id: 'review-risk', title: '只看待处理问题', detail: '跳到资源缺口和需复核账号,适合媒介补信息。' }, { id: 'client-preview', title: '客户版预览', detail: '隐藏内部风险字段,查看对客话术和推荐理由。' }, { id: 'learn-feedback', title: '反馈变规则', detail: '把客户反馈转成待确认规则,保留人工审核。' } ]; businessChecklist = [ { key: 'brief', label: 'Brief 已解析', detail: '客户、平台、预算、风格已抽取' }, { key: 'demand', label: '需求已确认', detail: '低置信度内容可编辑' }, { key: 'rule', label: '规则已选好', detail: '默认启用核心经验规则' }, { key: 'sample', label: '内容已采样', detail: '近10条笔记用于风格比对' }, { key: 'export', label: '名单可导出', detail: '客户版和内部版分开输出' } ]; stepGuides = [ { objective: '把客户 Brief 变成可解析任务', operator: '上传或替换 Brief 文件', output: '客户、平台、预算、风格字段' }, { objective: '确认 AI 抽取是否准确', operator: '勾选、编辑、补充需求点', output: '可执行需求清单' }, { objective: '决定本次提号怎么筛', operator: '启用经验规则或补临时规则', output: '本次任务规则组合' }, { objective: '从资源库召回并评分', operator: '运行提号和近10条内容采样', output: '带证据的候选池' }, { objective: '复核账号并形成名单', operator: '筛选、标记、查看账号详情', output: '客户版推荐名单' }, { objective: '把反馈沉淀成能力', operator: '提炼、确认或驳回规则', output: '可复用规则与记忆' } ]; steps: Step[] = [ { label: '上传 Brief', status: 'active' }, { label: '确认需求', status: 'todo' }, { label: '选择规则', status: 'todo' }, { label: '生成推荐', status: 'todo' }, { label: '导出推荐表', status: 'todo' }, { label: '反馈学习', status: 'todo' } ]; templates: TemplateItem[] = [ { name: '达人Brief.xlsx', type: 'Brief模板', useCase: '客户标准化需求录入', selected: false }, { name: '某品牌种草合作Brief.docx', type: 'Brief模板', useCase: '非结构化需求解析', selected: false }, { name: '某品牌达人推广规划.xlsx', type: '规划模板', useCase: '多平台组合拆解', selected: false }, { name: '【机构名称】客户+品牌-需求推荐名单.xlsx', type: '推荐表模板', useCase: '对客输出名单', selected: true }, { name: '内部复核名单-含风险字段.xlsx', type: '复核模板', useCase: '媒介确认报价、供应商、风险和档期', selected: false }, { name: '客户反馈复盘表.xlsx', type: '复盘模板', useCase: '沉淀客户偏好、排除项和长期规则', selected: false } ]; requirements: Requirement[] = [ { label: '客户/品牌', value: '客户A / 新锐护肤品牌', confidence: 96, selected: true, editable: false, evidence: 'Brief 标题:新锐护肤品牌种草合作需求' }, { label: '投放目标', value: '新品种草、真实测评、站内转化蓄水', confidence: 92, selected: true, editable: false, evidence: 'Brief 目标:新品首发,希望突出真实使用体验' }, { label: '平台需求', value: '小红书 20人、抖音 8人、B站 4人', confidence: 90, selected: true, editable: false, evidence: '达人推广规划 Sheet:平台配比' }, { label: '预算边界', value: '单号 2,000-18,000,优先性价比', confidence: 84, selected: true, editable: false, evidence: '预算列存在多个空值,AI 按已填区间推断' }, { label: '内容风格', value: '真实体验、通勤生活、轻专业成分党', confidence: 88, selected: true, editable: false, evidence: 'Brief 原文:不要硬广,要像真实分享' }, { label: '排除项', value: '硬广感强、近30天低活跃、黑名单供应商', confidence: 82, selected: true, editable: false, evidence: '规则库默认风控项 + Brief 排除描述' } ]; rules: Rule[] = [ { id: 'R-001', title: '按 Brief 平台优先提号', description: '先满足 Brief 明确指定的平台,再用相邻平台补足备选池。', category: '平台响应', source: '经验规则', selected: true, impact: '确保客户要求不被泛化' }, { id: 'R-002', title: '默认推荐数量不少于需求 3 倍', description: '当 Brief 写明数量时,候选池按需求数量的 3 倍以上生成,便于商务二次筛选。', category: '数量策略', source: '经验规则', selected: true, impact: '小红书候选池 60 个,抖音 24 个,B站 12 个' }, { id: 'R-003', title: '风格匹配低于 80% 自动降级', description: '需要风格比对时,采样账号近 10 篇内容,图文/标题/标签综合匹配不足 80% 标记为需复核。', category: '内容风格', source: '经验规则', selected: true, impact: '减少客户返工' }, { id: 'R-004', title: '黑名单与低配合度剔除', description: '历史拉黑、配合度低、供应商关系未绑定的媒体默认不进入强推荐。', category: '风险控制', source: '经验规则', selected: true, impact: '降低执行风险' }, { id: 'R-005', title: '优先近期采集有效价格', description: '报价有效期已过或采集超过 45 天的资源进入备选,并提示媒介复核。', category: '价格有效性', source: 'AI提炼', selected: true, impact: '避免报价失真' }, { id: 'R-006', title: '同质化账号限额', description: '同一平台同一内容类目强推荐不超过 40%,避免名单看起来重复。', category: '组合均衡', source: '用户反馈', selected: false, impact: '提升名单丰富度' } ]; candidates: Candidate[] = [ { id: 'M-1087', platform: '小红书', name: '梨涡测评室', fans: '18.6万', price: 6800, score: 94, styleMatch: 91, status: '强推荐', tags: ['真实测评', '成分党', '图文强'], reason: '近10篇中 9 篇为真实体验或成分拆解,互动中位数高于同价位账号 28%。', risk: '视频内容偏少,可作为图文主推。', quota: '小红书 1/60', selected: true, link: 'https://www.xiaohongshu.com/user/profile/demo1087', owner: '媒介-阿敏', supplier: '杭州某MCN', dataFreshness: 92, priceExpire: '2026-05-31', ruleHits: ['平台优先', '风格>80%', '报价有效', '供应商已绑定'], scoreBreakdown: [ { label: '需求匹配', value: 95 }, { label: '历史表现', value: 89 }, { label: '风险健康', value: 96 } ], recentNotes: buildNotes('梨涡测评室', ['真实测评', '通勤生活', '成分拆解'], 94) }, { id: 'M-2214', platform: '小红书', name: '通勤包里有什么', fans: '9.4万', price: 3600, score: 89, styleMatch: 86, status: '强推荐', tags: ['生活方式', '通勤', '性价比'], reason: '内容场景和 Brief 的通勤生活需求贴合,历史合作反馈为配合度高。', risk: '粉丝量中腰部,需要搭配高粉账号。', quota: '小红书 2/60', selected: true, link: 'https://www.xiaohongshu.com/user/profile/demo2214', owner: '媒介-小徐', supplier: '上海生活方式MCN', dataFreshness: 87, priceExpire: '2026-05-25', ruleHits: ['数量池补足', '生活方式加权', '低预算友好'], scoreBreakdown: [ { label: '需求匹配', value: 88 }, { label: '历史表现', value: 82 }, { label: '风险健康', value: 92 } ], recentNotes: buildNotes('通勤包里有什么', ['通勤生活', '真实分享', '生活方式'], 90) }, { id: 'M-3309', platform: '抖音', name: '林小林认真护肤', fans: '42.1万', price: 12800, score: 87, styleMatch: 82, status: '备选', tags: ['口播', '测评', '转化'], reason: '爆文率和完播率表现稳定,预算接近上限,建议作为抖音头部备选。', risk: '单条报价较高,需确认档期。', quota: '抖音 1/24', selected: true, link: 'https://www.douyin.com/user/demo3309', owner: '媒介-阿宁', supplier: '星图服务商', dataFreshness: 78, priceExpire: '2026-05-18', ruleHits: ['平台优先', '爆文率保护', '预算接近上限'], scoreBreakdown: [ { label: '需求匹配', value: 84 }, { label: '历史表现', value: 91 }, { label: '风险健康', value: 78 } ], recentNotes: buildNotes('林小林认真护肤', ['口播测评', '真实测评', '科普'], 84) }, { id: 'M-4402', platform: 'B站', name: '阿澈的成分笔记', fans: '13.2万', price: 9800, score: 84, styleMatch: 88, status: '备选', tags: ['长视频', '成分科普', '深度测评'], reason: '长视频讲解能力强,适合承接品牌专业背书。', risk: '制作周期较长。', quota: 'B站 1/12', selected: true, link: 'https://space.bilibili.com/demo4402', owner: '媒介-六月', supplier: '独立UP主', dataFreshness: 74, priceExpire: '2026-06-05', ruleHits: ['B站补充背书', '成分党加权', '制作周期提示'], scoreBreakdown: [ { label: '需求匹配', value: 85 }, { label: '历史表现', value: 80 }, { label: '风险健康', value: 84 } ], recentNotes: buildNotes('阿澈的成分笔记', ['成分科普', '深度测评', '科普'], 91) }, { id: 'M-5520', platform: '微信', name: '美妆情报局', fans: '7.8万', price: 5200, score: 78, styleMatch: 73, status: '需复核', tags: ['图文', '资讯', '价格友好'], reason: '预算友好,但近10篇资讯感较重,和真实测评风格存在偏差。', risk: '风格匹配不足 80%,需人工确认。', quota: '补充平台', selected: false, link: 'https://mp.weixin.qq.com/demo5520', owner: '媒介-小七', supplier: '公众号资源组', dataFreshness: 61, priceExpire: '2026-05-12', ruleHits: ['价格友好', '风格不足80%', '需复核'], scoreBreakdown: [ { label: '需求匹配', value: 72 }, { label: '历史表现', value: 76 }, { label: '风险健康', value: 69 } ], recentNotes: buildNotes('美妆情报局', ['资讯', '清单', '趋势'], 73) }, buildCandidate({ id: 'M-6101', platform: '小红书', name: '晚间护肤手账', category: '护肤', fans: '26.3万', price: 8200, score: 91, styleMatch: 93, status: '强推荐', tags: ['空瓶', '真实体验', '生活方式'], reason: '长期做空瓶和敏感肌日记,内容语气自然,适合新品真实种草。', risk: '档期较满,需要提前 10 天锁定。', owner: '媒介-小乔', supplier: '华东生活方式资源组', dataFreshness: 91, priceExpire: '2026-06-08', styles: ['空瓶复盘', '真实测评', '生活方式'], startMatch: 93, readMedian: '9.8万', interactionMedian: '4860', viralRate: 18, replyRate: 94, fulfillmentRate: 96, naturalTrafficRatio: 82, cooperationNoteRatio: 22, estimatedCpm: 84, audienceProfile: '女性 25-34 岁,一二线城市占比 58%,护肤消费兴趣高', contentForms: ['图文', '视频'], recallSources: ['关键词召回', '相似博主召回', '历史采纳召回'], serviceLevel: '高配合度', brandFit: '护肤新品高匹配', selected: true }), buildCandidate({ id: 'M-6102', platform: '小红书', name: '打工人变美清单', category: '美妆', fans: '11.7万', price: 4800, score: 88, styleMatch: 87, status: '强推荐', tags: ['通勤', '清单', '性价比'], reason: '通勤场景稳定,报价健康,可补足中腰部生活方式账号。', risk: '清单型内容较多,需要脚本里强化单品体验。', owner: '媒介-小徐', supplier: '上海通勤内容组', dataFreshness: 89, priceExpire: '2026-06-01', styles: ['通勤生活', '清单', '真实分享'], startMatch: 88, readMedian: '6.2万', interactionMedian: '2510', viralRate: 14, replyRate: 91, fulfillmentRate: 94, naturalTrafficRatio: 76, cooperationNoteRatio: 28, estimatedCpm: 77, audienceProfile: '女性 22-30 岁,新一线/二线城市白领占比高', contentForms: ['图文'], recallSources: ['条件筛选召回', '关键词召回'], serviceLevel: '高配合度', brandFit: '预算友好' }), buildCandidate({ id: 'M-6103', platform: '小红书', name: '敏感肌自救记录', category: '护肤', fans: '7.2万', price: 3900, score: 86, styleMatch: 90, status: '备选', tags: ['敏感肌', '真实测评', '素人感'], reason: '素人感强,评论区真实问题多,适合作为可信样本补充。', risk: '粉丝规模偏小,建议搭配高互动账号。', owner: '媒介-阿敏', supplier: '独立博主', dataFreshness: 84, priceExpire: '2026-05-29', styles: ['敏感肌', '真实测评', '素人分享'], startMatch: 89, readMedian: '4.1万', interactionMedian: '1790', viralRate: 12, replyRate: 86, fulfillmentRate: 88, naturalTrafficRatio: 88, cooperationNoteRatio: 12, estimatedCpm: 69, audienceProfile: '女性 20-32 岁,敏感肌与成分党标签集中', contentForms: ['图文', '视频'], recallSources: ['关键词召回', '内容样本召回'], serviceLevel: '稳定履约', brandFit: '风格高匹配' }), buildCandidate({ id: 'M-6201', platform: '抖音', name: '七七成分开麦', category: '护肤', fans: '68.5万', price: 18500, score: 83, styleMatch: 85, status: '备选', tags: ['口播', '成分科普', '爆文率高'], reason: '完播和爆文率强,适合作为抖音声量位,但预算压力较大。', risk: '报价超过当前建议上限,需谈价或只保留备选。', owner: '媒介-阿宁', supplier: '星图服务商', dataFreshness: 86, priceExpire: '2026-05-26', styles: ['成分科普', '口播测评', '转化'], startMatch: 86, readMedian: '46万', interactionMedian: '1.8万', viralRate: 22, replyRate: 82, fulfillmentRate: 89, naturalTrafficRatio: 63, cooperationNoteRatio: 41, estimatedCpm: 146, audienceProfile: '女性 24-35 岁,护肤功效兴趣强,三线以上城市占比 64%', contentForms: ['视频'], recallSources: ['平台筛选召回', '历史表现召回'], serviceLevel: '稳定履约', brandFit: '专业背书' }), buildCandidate({ id: 'M-6202', platform: '抖音', name: '小麦真实开箱', category: '美妆', fans: '23.9万', price: 7600, score: 85, styleMatch: 81, status: '备选', tags: ['开箱', '测评', '转化'], reason: '开箱测评节奏好,价格在预算中段,适合补转化素材。', risk: '护肤深度不足,需要提供产品体验脚本。', owner: '媒介-南南', supplier: '短视频资源组', dataFreshness: 79, priceExpire: '2026-05-20', styles: ['开箱', '口播测评', '真实分享'], startMatch: 82, readMedian: '18万', interactionMedian: '6900', viralRate: 16, replyRate: 84, fulfillmentRate: 91, naturalTrafficRatio: 68, cooperationNoteRatio: 35, estimatedCpm: 96, audienceProfile: '女性 18-28 岁,彩妆/护肤兴趣重叠', contentForms: ['视频'], recallSources: ['条件筛选召回', '预算策略召回'], serviceLevel: '稳定履约', brandFit: '转化补充' }), buildCandidate({ id: 'M-6203', platform: '抖音', name: '阿森护肤实验室', category: '护肤', fans: '15.8万', price: 6800, score: 82, styleMatch: 78, status: '需复核', tags: ['实验', '功效', '男性视角'], reason: '专业表达强,但画面偏实验室感,和生活方式 Brief 有偏差。', risk: '风格匹配低于 80%,客户版需谨慎呈现。', owner: '媒介-六月', supplier: '独立达人', dataFreshness: 72, priceExpire: '2026-05-19', styles: ['实验测评', '成分科普', '功效'], startMatch: 79, readMedian: '11万', interactionMedian: '4200', viralRate: 11, replyRate: 78, fulfillmentRate: 86, naturalTrafficRatio: 71, cooperationNoteRatio: 32, estimatedCpm: 91, audienceProfile: '男女比例接近,功效护肤兴趣高', contentForms: ['视频'], recallSources: ['内容标签召回'], serviceLevel: '需沟通确认', brandFit: '需风格复核' }), buildCandidate({ id: 'M-6301', platform: 'B站', name: '配方师小简', category: '护肤', fans: '31.4万', price: 16800, score: 90, styleMatch: 92, status: '强推荐', tags: ['长视频', '成分科普', '专业背书'], reason: '长视频解释能力强,适合承担品牌专业背书和搜索留存。', risk: '制作周期 14 天,需先锁定脚本窗口。', owner: '媒介-六月', supplier: '独立UP主', dataFreshness: 82, priceExpire: '2026-06-12', styles: ['成分科普', '长视频测评', '专业拆解'], startMatch: 92, readMedian: '13万', interactionMedian: '5300', viralRate: 13, replyRate: 88, fulfillmentRate: 92, naturalTrafficRatio: 79, cooperationNoteRatio: 24, estimatedCpm: 126, audienceProfile: '女性 23-35 岁,成分党、学生党与白领混合', contentForms: ['长视频', '图文'], recallSources: ['相似博主召回', '历史采纳召回'], serviceLevel: '稳定履约', brandFit: '专业背书高匹配', selected: true }), buildCandidate({ id: 'M-6302', platform: 'B站', name: '护肤避坑研究员', category: '护肤', fans: '8.9万', price: 7200, score: 81, styleMatch: 84, status: '备选', tags: ['避坑', '深度测评', '学生党'], reason: '评论区信任度高,适合做备选口碑内容。', risk: '更新频率一般,需要确认排期。', owner: '媒介-小七', supplier: '独立UP主', dataFreshness: 69, priceExpire: '2026-05-30', styles: ['避坑', '深度测评', '成分科普'], startMatch: 85, readMedian: '5.7万', interactionMedian: '2100', viralRate: 9, replyRate: 80, fulfillmentRate: 84, naturalTrafficRatio: 83, cooperationNoteRatio: 18, estimatedCpm: 102, audienceProfile: '女性 18-26 岁,学生党与初入职场占比较高', contentForms: ['长视频'], recallSources: ['关键词召回', '平台策略召回'], serviceLevel: '需沟通确认', brandFit: '备选口碑' }), buildCandidate({ id: 'M-6401', platform: '微信', name: '成分慢读', category: '护肤', fans: '12.5万', price: 9600, score: 79, styleMatch: 80, status: '备选', tags: ['公众号', '深度图文', '专业'], reason: '适合承接长图文解释,补足私域和搜索沉淀。', risk: '平台不在主需求中,需要客户确认是否保留。', owner: '媒介-小七', supplier: '公众号资源组', dataFreshness: 65, priceExpire: '2026-05-22', styles: ['深度图文', '成分科普', '趋势'], startMatch: 81, readMedian: '3.4万', interactionMedian: '980', viralRate: 6, replyRate: 76, fulfillmentRate: 87, naturalTrafficRatio: 74, cooperationNoteRatio: 29, estimatedCpm: 132, audienceProfile: '女性 28-40 岁,高线城市和高消费力用户偏多', contentForms: ['图文'], recallSources: ['资源库召回'], serviceLevel: '稳定履约', brandFit: '补充平台' }), buildCandidate({ id: 'M-6501', platform: '微博', name: '护肤趋势观察', category: '美妆', fans: '54.6万', price: 11800, score: 76, styleMatch: 72, status: '需复核', tags: ['趋势', '话题扩散', '资讯'], reason: '适合话题扩散,但内容资讯感强,不适合作为主推真实测评。', risk: '硬广和资讯感风险,需要从客户版隐藏或降级。', owner: '媒介-阿宁', supplier: '微博资源组', dataFreshness: 70, priceExpire: '2026-06-02', styles: ['趋势', '资讯', '话题'], startMatch: 74, readMedian: '22万', interactionMedian: '3600', viralRate: 10, replyRate: 73, fulfillmentRate: 82, naturalTrafficRatio: 58, cooperationNoteRatio: 44, estimatedCpm: 112, audienceProfile: '泛美妆兴趣人群,话题扩散强但转化弱', contentForms: ['图文', '短内容'], recallSources: ['平台补充召回'], serviceLevel: '需沟通确认', brandFit: '话题扩散' }), buildCandidate({ id: 'M-6601', platform: '小红书', name: '妈妈的浴室柜', category: '母婴', fans: '16.9万', price: 6200, score: 84, styleMatch: 83, status: '备选', tags: ['母婴', '家庭场景', '温和护肤'], reason: '家庭和温和护肤场景自然,适合敏感肌线索扩展。', risk: '主目标不是母婴,需要确认是否纳入多场景名单。', owner: '媒介-小乔', supplier: '母婴生活资源组', dataFreshness: 88, priceExpire: '2026-06-04', styles: ['家庭场景', '温和护肤', '真实分享'], startMatch: 84, readMedian: '7.3万', interactionMedian: '2900', viralRate: 13, replyRate: 89, fulfillmentRate: 93, naturalTrafficRatio: 81, cooperationNoteRatio: 19, estimatedCpm: 86, audienceProfile: '女性 25-38 岁,妈妈人群和敏感肌标签重叠', contentForms: ['图文', '视频'], recallSources: ['场景扩展召回'], serviceLevel: '高配合度', brandFit: '场景扩展' }), buildCandidate({ id: 'M-6701', platform: '小红书', name: '周末家居护肤', category: '家居', fans: '6.4万', price: 3100, score: 80, styleMatch: 82, status: '备选', tags: ['家居', '生活方式', '低预算'], reason: '生活方式感强,价格友好,可作为低预算补充。', risk: '护肤内容占比不足,需要限定选题。', owner: '媒介-小徐', supplier: '生活方式资源组', dataFreshness: 83, priceExpire: '2026-05-28', styles: ['家居生活', '生活方式', '真实分享'], startMatch: 83, readMedian: '3.6万', interactionMedian: '1320', viralRate: 8, replyRate: 87, fulfillmentRate: 90, naturalTrafficRatio: 84, cooperationNoteRatio: 15, estimatedCpm: 72, audienceProfile: '女性 23-34 岁,家居和通勤生活兴趣高', contentForms: ['图文'], recallSources: ['相似场景召回'], serviceLevel: '稳定履约', brandFit: '低预算补充' }) ]; private candidateBaseline = new Map( this.candidates.map((candidate) => [ candidate.id, { score: candidate.score, styleMatch: candidate.styleMatch, status: candidate.status, selected: candidate.selected, risk: candidate.risk, reason: candidate.reason, ruleHits: [...candidate.ruleHits] } ]) ); resources: ResourceSummary[] = [ { platform: '小红书', total: 4286, fresh: 86, matched: 182, status: '主力平台' }, { platform: '抖音', total: 2130, fresh: 79, matched: 72, status: '预算复核' }, { platform: 'B站', total: 684, fresh: 73, matched: 28, status: '补充背书' }, { platform: '微信', total: 516, fresh: 61, matched: 18, status: '待人工确认' }, { platform: '微博', total: 902, fresh: 69, matched: 24, status: '话题扩散' } ]; fieldMappings: FieldMapping[] = [ { source: '达人昵称', target: 'accountName', confidence: 98, confirmed: true }, { source: '平台ID/主页链接', target: 'platformId / link', confidence: 94, confirmed: true }, { source: '报价-图文', target: 'imagePrice', confidence: 91, confirmed: true }, { source: '近30天互动中位数', target: 'interactionMedian', confidence: 88, confirmed: false }, { source: '供应商联系人', target: 'supplierContact', confidence: 84, confirmed: false }, { source: '备注/特殊要求', target: 'riskNote', confidence: 71, confirmed: false } ]; activities: Activity[] = [ { title: '解析 Brief 表格/文档', detail: '识别客户、品牌、平台、数量、预算、内容风格与排除项', status: '完成' }, { title: '读取本地媒体资源库', detail: '模拟扫描 D 盘资源库,多平台字段完成统一映射', status: '完成' }, { title: '执行规则与评分', detail: '数量、价格、风格、风险、供应商关系进入综合评分', status: '进行中' }, { title: '后台实时采样', detail: '需要登录平台采集近10篇内容时进入异步任务队列', status: '待执行' } ]; samplingTasks: SamplingTask[] = [ { account: '梨涡测评室', platform: '小红书', progress: 100, status: '完成', result: '9/10 篇符合真实测评或成分拆解' }, { account: '通勤包里有什么', platform: '小红书', progress: 100, status: '完成', result: '8/10 篇符合通勤生活场景' }, { account: '林小林认真护肤', platform: '抖音', progress: 70, status: '进行中', result: '已采样 7 条视频,风格暂定 82%' }, { account: '美妆情报局', platform: '微信', progress: 40, status: '待执行', result: '等待公众号图文采样' } ]; learnedRules: LearnedRule[] = [ { title: '客户反馈中的明确偏好要先转成排除和加分项', description: '当反馈出现“减少/不要/优先/保留”等词时,提炼为可确认规则,不直接静默生效。', confidence: 91, action: '生成待确认规则', approved: false } ]; gapItems: GapItem[] = [ { title: '需求点需要可编辑和可追溯', current: '原 Demo 只有勾选,缺少原文证据和修正入口。', suggestion: '补充编辑状态、证据来源、手动新增需求。', done: true }, { title: '资源库需要展示字段映射', current: '只展示各平台数量,客户看不到本地表如何被 AI 理解。', suggestion: '增加字段映射确认、资源目录检测和同步动作。', done: true }, { title: '风格比对需要有过程', current: '只展示结果分,缺少近10篇内容采样的可解释过程。', suggestion: '增加后台采样队列、样本标题、风格命中率。', done: true }, { title: '推荐名单需要二次筛选', current: '只展示卡片,缺少平台/状态/预算过滤和账号详情。', suggestion: '增加筛选、排序、详情侧栏、状态调整。', done: true }, { title: '自我提升需要审核机制', current: '反馈提炼后直接加入规则,容易让客户担心 AI 乱学。', suggestion: '保留“待确认-入库”的人工审核链路。', done: true } ]; taskRecords: TaskRecord[] = [ { id: 'TH-202605-001', brand: '客户A / 新锐护肤品牌', status: '提号中', owner: '商务-王一', version: '名单 v0.3', lastAction: '已生成候选池,等待商务复核' }, { id: 'TH-202604-028', brand: '客户B / 食饮新品', status: '已交付', owner: '商务-李想', version: '名单 v1.2', lastAction: '客户反馈已提炼 2 条规则' }, { id: 'TH-202604-019', brand: '客户C / 家清系列', status: '待确认', owner: '商务-陈晨', version: '需求 v0.1', lastAction: '等待确认平台数量' }, { id: 'TH-202604-033', brand: '客户D / 母婴洗护', status: '待复盘', owner: '商务-周周', version: '反馈 v0.2', lastAction: '客户要求减少硬广达人,规则待确认入库' }, { id: 'TH-202605-006', brand: '客户E / 家居香氛', status: '资源同步', owner: '商务-林夕', version: '资源 v0.1', lastAction: '正在映射小红书和微信资源字段' }, { id: 'TH-202605-009', brand: '客户F / 数码配件', status: '待采样', owner: '商务-阿树', version: '名单 v0.1', lastAction: 'B站长视频账号等待近10条内容采样' }, { id: 'TH-202605-012', brand: '客户G / 轻食新品', status: '已交付', owner: '商务-小满', version: '名单 v1.0', lastAction: '客户版名单已导出,等待投放结果回填' } ]; taskTimeline: TaskTimelineItem[] = [ { taskId: 'TH-202605-001', time: '2026-05-12 09:18', title: 'Brief 上传并解析', detail: '识别客户、品牌、平台数量、预算和内容风格。' }, { taskId: 'TH-202605-001', time: '2026-05-12 09:31', title: '生成候选池', detail: '从资源库召回 324 个账号,去重后 293 个进入评分池。' }, { taskId: 'TH-202605-001', time: '2026-05-12 10:05', title: '媒介复核', detail: '剔除硬广风险账号 4 个,补入 B站专业背书账号。' }, { taskId: 'TH-202604-028', time: '2026-04-28 16:40', title: '客户反馈回收', detail: '客户要求减少泛美食资讯号,已提炼 2 条行业偏好规则。' }, { taskId: 'TH-202605-006', time: '2026-05-12 08:55', title: '资源同步', detail: '正在映射小红书和微信资源字段,等待供应商联系人补齐。' } ]; templateFields: TemplateField[] = [ { label: '平台', source: '资源库/Brief', customer: true, internal: true, required: true, selected: true }, { label: '账号昵称', source: '资源库', customer: true, internal: true, required: true, selected: true }, { label: '主页链接', source: '资源库', customer: true, internal: true, required: true, selected: true }, { label: '粉丝量', source: '资源库', customer: true, internal: true, required: true, selected: true }, { label: '图文/视频报价', source: '资源库', customer: true, internal: true, required: true, selected: true }, { label: '推荐理由', source: 'AI生成+人工复核', customer: true, internal: true, required: true, selected: true }, { label: '近10条样本', source: '后台采样', customer: true, internal: true, required: false, selected: true }, { label: '供应商/联系人', source: '资源库', customer: false, internal: true, required: true, selected: true }, { label: '风险提示', source: '规则库', customer: false, internal: true, required: false, selected: true }, { label: '客户采纳结果', source: '复盘回填', customer: false, internal: true, required: false, selected: false } ]; get selectedRulesCount(): number { return this.rules.filter((rule) => rule.selected).length; } get selectedCandidatesCount(): number { return this.filteredCandidates.filter((candidate) => candidate.selected).length; } get visibleNavItems(): NavItem[] { if (!this.simpleMode) { return this.navItems; } const simpleKeys = new Set([...this.simpleNavKeys, this.activeView]); return this.navItems.filter((item) => simpleKeys.has(item.key)); } get activeNavItem(): NavItem { return this.navItems.find((item) => item.key === this.activeView) ?? this.navItems[0]; } get taskDisplayName(): string { return '客户A / 新锐护肤品牌种草合作'; } get deliveryStatusLabel(): string { if (this.currentStep < 4) { return '名单待生成'; } if (this.currentStep === 4) { return '推荐生成中'; } return `交付准备度 ${this.exportReadiness}%`; } get nextAction(): { title: string; detail: string } { if (this.currentStep <= 1) { return { title: '解析 Brief', detail: '识别客户、平台、数量、预算、内容风格和排除项。' }; } if (this.currentStep === 2) { return { title: '确认需求', detail: '保留已勾选需求,进入规则选择和提号。' }; } if (this.currentStep === 3) { return { title: '生成推荐池', detail: '按已选规则从资源库召回账号,进入采样和评分。' }; } if (this.currentStep === 4 && this.samplingTasks.some((task) => task.status !== '完成')) { return { title: '完成近10条采样', detail: '把风格比对证据补齐,降低客户返工风险。' }; } if (this.currentStep === 4) { return { title: '查看推荐名单', detail: '进入账号复核和最近10条内容证据。' }; } if (this.currentStep === 5) { return { title: '进入客户版交付', detail: '隐藏内部风险字段,预览对客版本并准备导出。' }; } return { title: '提炼反馈规则', detail: '把客户反馈生成待确认规则,人工确认后入库。' }; } get currentStepGuide(): { objective: string; operator: string; output: string } { return this.stepGuides[this.currentStep - 1] ?? this.stepGuides[0]; } get checklistDoneCount(): number { return this.businessChecklist.filter((item) => this.isChecklistDone(item.key)).length; } get selectedCandidateList(): Candidate[] { return this.candidates.filter((candidate) => candidate.selected && candidate.status !== '已剔除'); } get reviewQueueCandidates(): Candidate[] { return this.candidates.filter((candidate) => candidate.status === '需复核' || candidate.status === '已剔除').slice(0, 5); } get creatorShortlistCandidates(): Candidate[] { return this.creatorFilteredCandidates.slice(0, 8); } get compareCandidates(): Candidate[] { return this.selectedCandidateList.slice(0, 4); } get activeScenarioCount(): number { return this.briefScenarios.filter((scenario) => scenario.selected).length; } get exportReadiness(): number { const mapping = (this.confirmedMappingsCount / this.fieldMappings.length) * 25; const rules = (this.selectedRulesCount / this.rules.length) * 25; const candidates = Math.min(1, this.selectedCandidateList.length / 4) * 25; const sampling = (this.samplingTasks.filter((task) => task.status === '完成').length / this.samplingTasks.length) * 25; return Math.round(mapping + rules + candidates + sampling); } get strongRecommendationCount(): number { return this.candidates.filter((candidate) => candidate.status === '强推荐').length; } get totalMatchedCandidates(): number { return this.resources.reduce((sum, resource) => sum + resource.matched, 0); } get workspacePipelineStages(): AiSelectionStage[] { return this.aiSelectionStages.slice(1, 6); } get platforms(): string[] { return ['全部', ...Array.from(new Set(this.candidates.map((candidate) => candidate.platform)))]; } get categories(): string[] { return ['全部', ...Array.from(new Set(this.candidates.map((candidate) => candidate.category ?? candidate.tags[0] ?? '未分类')))]; } get contentForms(): string[] { const forms = this.candidates.flatMap((candidate) => candidate.contentForms ?? [candidate.platform === 'B站' ? '长视频' : '图文']); return ['全部', ...Array.from(new Set(forms))]; } get fanTiers(): string[] { return ['全部', '1万以下', '1万-10万', '10万-50万', '50万以上']; } get creatorFilteredCandidates(): Candidate[] { const queryTokens = this.creatorQuery .split(/\s+/) .map((token) => token.trim()) .filter(Boolean); return this.candidates .filter((candidate) => this.creatorPlatform === '全部' || candidate.platform === this.creatorPlatform) .filter((candidate) => this.creatorCategory === '全部' || candidate.category === this.creatorCategory || candidate.tags.includes(this.creatorCategory)) .filter((candidate) => this.creatorContentForm === '全部' || (candidate.contentForms ?? []).includes(this.creatorContentForm)) .filter((candidate) => this.creatorFanTier === '全部' || (candidate.fanTier ?? inferFanTier(candidate.fans)) === this.creatorFanTier) .filter((candidate) => candidate.price <= this.creatorBudgetMax) .filter((candidate) => { if (this.creatorRisk === '只看安全') { return candidate.status !== '需复核' && candidate.status !== '已剔除'; } if (this.creatorRisk === '只看需复核') { return candidate.status === '需复核'; } return true; }) .filter((candidate) => { if (queryTokens.length === 0) { return true; } const haystack = [ candidate.name, candidate.platform, candidate.category ?? '', candidate.reason, candidate.risk, candidate.audienceProfile ?? '', ...(candidate.tags ?? []), ...(candidate.recallSources ?? []) ].join(' '); return queryTokens.some((token) => haystack.includes(token)); }) .sort((a, b) => b.score - a.score); } get creatorAiSummary(): string { const count = this.creatorFilteredCandidates.length; const strong = this.creatorFilteredCandidates.filter((candidate) => candidate.status === '强推荐').length; const review = this.creatorFilteredCandidates.filter((candidate) => candidate.status === '需复核').length; return `命中 ${count} 个博主,其中强推荐 ${strong} 个、需复核 ${review} 个;AI 会优先保留风格匹配高、报价健康、服务履约稳定的账号。`; } get deliveryChecklistDoneCount(): number { return this.deliveryChecklist.filter((item) => item.done).length; } get selectedDeliveryFields(): string[] { return this.deliveryFields .filter((field) => field.selected) .filter((field) => this.previewAudience === '内部复核版' || field.customer) .map((field) => field.label); } get deliveryGaps(): string[] { const gaps: string[] = []; if (this.confirmedMappingsCount < this.fieldMappings.length) { gaps.push('还有字段映射未确认'); } if (this.samplingTasks.some((task) => task.status !== '完成')) { gaps.push('近10条内容采样未全部完成'); } if (this.selectedCandidateList.some((candidate) => candidate.status === '需复核')) { gaps.push('名单里仍有需复核账号'); } if (this.selectedCandidateList.length < 8) { gaps.push('客户版主推账号数量偏少'); } return gaps; } get resourceFixQueue(): ResourceIssue[] { return this.resourceIssues.filter((issue) => !issue.resolved).slice(0, 4); } get taskAttentionItems(): Array<{ title: string; detail: string; action: string; view: ViewKey }> { return [ { title: '补齐微信主页链接', detail: '影响近10条内容采样,建议先到资源库处理高风险字段。', action: '去资源库', view: 'resources' }, { title: '确认需复核账号', detail: `${this.reviewQueueCandidates.length} 个账号仍有风险或剔除原因,需要业务确认。`, action: '打开名单', view: 'workspace' }, { title: '客户反馈待学习', detail: '把本次驳回原因转成待确认规则,后续同类客户可复用。', action: '去学习', view: 'learning' } ]; } get learningScopeEffect(): string { if (this.selectedLearningScope === '本任务') { return '只影响当前推荐名单,适合临时口径或单次活动偏好。'; } if (this.selectedLearningScope === '单客户') { return '后续同一客户 Brief 会默认带入,但仍保留人工确认。'; } if (this.selectedLearningScope === '行业') { return '同品类客户会获得弱加权,避免把单个客户口味过度放大。'; } return '进入全局规则库前需要二次确认,命中后会影响所有新任务。'; } get filteredCandidates(): Candidate[] { return this.candidates .filter((candidate) => this.selectedPlatform === '全部' || candidate.platform === this.selectedPlatform) .filter((candidate) => this.selectedStatus === '全部' || candidate.status === this.selectedStatus) .filter((candidate) => candidate.price <= this.budgetMax) .sort((a, b) => { if (this.sortMode === '风格匹配') { return b.styleMatch - a.styleMatch; } if (this.sortMode === '报价升序') { return a.price - b.price; } return b.score - a.score; }); } get selectedCandidate(): Candidate { return this.candidates.find((candidate) => candidate.id === this.selectedCandidateId) ?? this.candidates[0]; } get selectedNote(): NoteDetail { return ( this.selectedCandidate.recentNotes.find((note) => note.id === this.selectedNoteId) ?? this.selectedCandidate.recentNotes[0] ); } get filteredNotes(): NoteDetail[] { return this.selectedCandidate.recentNotes.filter( (note) => this.noteVerdictFilter === '全部' || note.verdict === this.noteVerdictFilter ); } get confirmedMappingsCount(): number { return this.fieldMappings.filter((mapping) => mapping.confirmed).length; } get selectedResource(): ResourceSummary { return this.resources.find((resource) => resource.platform === this.selectedResourcePlatform) ?? this.resources[0]; } get selectedResourceFiles(): ResourceFile[] { return this.resourceFiles.filter((file) => file.platform === this.selectedResourcePlatform); } get selectedResourceIssues(): ResourceIssue[] { return this.resourceIssues.filter((issue) => issue.platform === this.selectedResourcePlatform); } get selectedResourceCandidates(): Candidate[] { return this.candidates.filter((candidate) => candidate.platform === this.selectedResourcePlatform).slice(0, 6); } get selectedRule(): Rule { return this.rules.find((rule) => rule.id === this.selectedRuleId) ?? this.rules[0]; } get enabledRuleTestCases(): RuleTestCase[] { return this.ruleTestCases.filter((item) => item.enabled); } get selectedTask(): TaskRecord { return this.taskRecords.find((task) => task.id === this.selectedTaskId) ?? this.taskRecords[0]; } get selectedTaskTimeline(): TaskTimelineItem[] { const timeline = this.taskTimeline.filter((item) => item.taskId === this.selectedTaskId); if (timeline.length > 0) { return timeline; } return [ { taskId: this.selectedTaskId, time: '待补充', title: '暂无详细时间线', detail: '正式版会记录 Brief、资源同步、规则调整、名单导出和客户反馈。' } ]; } get selectedTemplate(): TemplateItem { return this.templates.find((template) => template.selected) ?? this.templates[0]; } get visibleTemplateFields(): TemplateField[] { return this.templateFields.filter((field) => { if (this.templatePreviewMode === '客户版') { return field.customer && field.selected; } if (this.templatePreviewMode === '复盘版') { return field.selected || field.label.includes('采纳') || field.source.includes('复盘'); } return field.internal && field.selected; }); } isChecklistDone(key: string): boolean { if (key === 'brief') { return this.currentStep > 1; } if (key === 'demand') { return this.currentStep > 2 && this.requirements.some((item) => item.selected); } if (key === 'rule') { return this.currentStep > 3 && this.selectedRulesCount >= 3; } if (key === 'sample') { return this.currentStep > 4 && this.samplingTasks.every((task) => task.status === '完成'); } if (key === 'export') { return this.currentStep >= 5 && (this.exportReadiness >= 80 || this.exportState.includes('导出')); } return false; } toggleSimpleMode(): void { this.simpleMode = !this.simpleMode; if (this.simpleMode && !this.simpleNavKeys.includes(this.activeView)) { this.activeView = 'workspace'; } this.appendLog('视图模式', this.simpleMode ? '已切换为商务简洁模式。' : '已切换为完整业务模式。'); } toggleAdvancedWorkspace(): void { this.showAdvancedWorkspace = !this.showAdvancedWorkspace; this.appendLog('工作台细节', this.showAdvancedWorkspace ? '已展开规则、采样和试算细节。' : '已收起高级业务细节。'); } backToWorkflow(): void { this.activeView = 'workspace'; this.appendLog('导航', `已返回提号流程 Step ${this.currentStep}。`); } runNextAction(): void { if (this.currentStep <= 1) { this.analyzeBrief(); this.appendLog('下一步', '已解析 Brief。'); return; } if (this.currentStep === 2) { this.confirmDemand(); this.appendLog('下一步', '已确认需求。'); return; } if (this.currentStep === 3) { this.generateRecommendations(); this.appendLog('下一步', '已生成推荐名单。'); return; } if (this.currentStep === 4 && this.samplingTasks.some((task) => task.status !== '完成')) { this.runSampling(); this.currentStep = 5; this.updateSteps(); this.appendLog('下一步', '已完成近10条内容采样。'); return; } if (this.currentStep === 4) { this.currentStep = 5; this.updateSteps(); this.appendLog('下一步', '已进入推荐名单复核。'); return; } if (this.currentStep === 5) { this.previewAudience = '客户版'; this.activeView = 'delivery'; this.updateSteps(); this.appendLog('下一步', '已进入客户版交付预览。'); return; } this.extractRules(); this.appendLog('下一步', '已从反馈中提炼待确认规则。'); } runBusinessShortcut(id: string): void { if (id === 'fast-list') { this.analyzeBrief(); this.confirmDemand(); this.generateRecommendations(); this.runSampling(); this.currentStep = 5; this.previewAudience = '客户版'; this.exportState = '已生成可讨论名单,等待客户版预览'; this.showAdvancedWorkspace = false; this.updateSteps(); this.appendLog('快捷操作', '已一键生成可看名单。'); return; } if (id === 'review-risk') { this.selectedStatus = '需复核'; this.selectedResourcePlatform = '微信'; this.activeView = 'resources'; this.appendLog('快捷操作', '已切到待处理问题和需复核账号。'); return; } if (id === 'client-preview') { this.previewAudience = '客户版'; this.templatePreviewMode = '客户版'; this.currentStep = 5; this.activeView = 'delivery'; this.updateSteps(); this.appendLog('快捷操作', '已进入客户版名单预览。'); return; } if (id === 'learn-feedback') { this.extractRules(); this.activeView = 'learning'; this.appendLog('快捷操作', '已进入反馈学习并提炼规则。'); } } addVisibleCreatorsToPool(): void { const visibleIds = new Set(this.creatorShortlistCandidates.map((candidate) => candidate.id)); this.candidates = this.candidates.map((candidate) => visibleIds.has(candidate.id) ? { ...candidate, selected: true, status: candidate.status === '已剔除' ? '备选' : candidate.status } : candidate ); this.exportState = `已将 ${visibleIds.size} 个当前筛选博主加入候选池`; this.appendLog('找博主', `已批量加入 ${visibleIds.size} 个当前筛选账号。`); } openRecommendationReview(candidate?: Candidate): void { if (candidate) { this.openCandidate(candidate); } this.currentStep = 5; this.activeView = 'workspace'; this.updateSteps(); this.appendLog('名单复核', candidate ? `已打开 ${candidate.name} 的内容证据。` : '已打开推荐名单复核视图。'); } toggleDeliveryChecklist(item: { done: boolean; label: string }): void { item.done = !item.done; this.appendLog('交付检查', `${item.label} 已标记为${item.done ? '完成' : '待处理'}。`); } setLearningScope(scope: '本任务' | '单客户' | '行业' | '长期规则'): void { this.selectedLearningScope = scope; this.appendLog('学习范围', `反馈规则生效范围已设为${scope}。`); } uploadTemplateMock(): void { this.templateUploadState = '已上传《客户A-推荐名单字段模板.xlsx》,字段预览已刷新'; this.templates = [ { name: '客户A-推荐名单字段模板.xlsx', type: '推荐表模板', useCase: '客户A 专属字段顺序和对客话术', selected: true }, ...this.templates.map((template) => ({ ...template, selected: false })) ]; this.appendLog('模板上传', '已模拟上传客户A专属推荐表模板。'); } useCreatorPrompt(prompt: string): void { this.creatorQuery = prompt; this.appendLog('找博主', `已按提示词更新搜索:${prompt}`); } setCreatorFilters(platform: string, category = '全部'): void { this.creatorPlatform = platform; this.creatorCategory = category; this.appendLog('找博主筛选', `平台 ${platform},类目 ${category}。`); } addCreatorToPool(candidate: Candidate): void { candidate.selected = true; candidate.status = candidate.status === '已剔除' ? '备选' : candidate.status; this.selectedCandidateId = candidate.id; this.selectedNoteId = candidate.recentNotes[0]?.id ?? ''; this.exportState = `${candidate.name} 已加入候选池`; this.appendLog('候选池', `${candidate.name} 从找博主页加入本次名单。`); } toggleScenario(scenario: BriefScenario): void { scenario.selected = !scenario.selected; this.recalculateScenario(); this.appendLog('情景试算', `${scenario.label}:${scenario.selected ? '已启用' : '已关闭'}。`); } selectVisibleCandidates(): void { const visibleIds = new Set(this.filteredCandidates.map((candidate) => candidate.id)); this.candidates = this.candidates.map((candidate) => ({ ...candidate, selected: visibleIds.has(candidate.id) ? true : candidate.selected })); this.appendLog('名单筛选', `已勾选当前筛选下的 ${visibleIds.size} 个账号。`); } clearSelectedCandidates(): void { this.candidates = this.candidates.map((candidate) => ({ ...candidate, selected: false })); this.appendLog('名单筛选', '已清空当前推荐名单勾选。'); } bulkStatus(status: CandidateStatus): void { const count = this.selectedCandidateList.length; this.candidates = this.candidates.map((candidate) => candidate.selected ? { ...candidate, status, selected: status !== '已剔除' } : candidate ); this.appendLog('批量复核', `已将 ${count} 个勾选账号标记为${status}。`); } toggleCompare(): void { this.compareOpen = !this.compareOpen; this.appendLog('对比视图', this.compareOpen ? '已打开账号横向对比。' : '已收起账号横向对比。'); } switchPreviewAudience(): void { this.previewAudience = this.previewAudience === '客户版' ? '内部复核版' : '客户版'; this.appendLog('预览模式', `已切换为${this.previewAudience}。`); } setPreviewAudience(audience: '客户版' | '内部复核版'): void { if (this.previewAudience === audience) { return; } this.previewAudience = audience; this.appendLog('预览模式', `已切换为${this.previewAudience}。`); } setNoteVerdictFilter(verdict: NoteVerdict | '全部'): void { this.noteVerdictFilter = verdict; const nextNote = this.filteredNotes[0] ?? this.selectedCandidate.recentNotes[0]; this.selectedNoteId = nextNote.id; } markNote(note: NoteDetail, verdict: NoteVerdict): void { note.verdict = verdict; if (verdict === '正向样本') { note.match = Math.min(99, note.match + 3); this.selectedCandidate.styleMatch = Math.min(99, this.selectedCandidate.styleMatch + 1); } if (verdict === '负向样本') { note.match = Math.max(45, note.match - 5); this.selectedCandidate.styleMatch = Math.max(50, this.selectedCandidate.styleMatch - 2); this.selectedCandidate.status = '需复核'; } this.appendLog('笔记标注', `${this.selectedCandidate.name}《${note.title}》被标记为${verdict}。`); } addCandidateReview(text: string): void { this.feedbackText = `${this.feedbackText}\n商务复核:${this.selectedCandidate.name} - ${text}`; this.appendLog('复核意见', `${this.selectedCandidate.name}:${text}`); } appendFeedbackPreset(text: string): void { this.feedbackText = `${this.feedbackText}\n${text}`; this.appendLog('反馈补充', text); } selectResourcePlatform(platform: string): void { this.selectedResourcePlatform = platform; this.appendLog('资源库', `已查看 ${platform} 资源详情。`); } rescanSelectedResource(): void { this.resources = this.resources.map((resource) => resource.platform === this.selectedResourcePlatform ? { ...resource, fresh: Math.min(99, resource.fresh + 6), matched: resource.matched + 9, status: '刚刚同步' } : resource ); this.resourceHealthItems = this.resourceHealthItems.map((item, index) => ({ ...item, value: Math.min(98, item.value + (index === 1 ? 5 : 3)) })); this.appendLog('资源同步', `${this.selectedResourcePlatform} 已重新扫描,匹配数和字段完整率已更新。`); } resolveResourceIssue(issue: ResourceIssue): void { issue.resolved = true; this.appendLog('资源问题', `${issue.platform}:${issue.title} 已标记处理。`); } confirmMapping(mapping: FieldMapping): void { mapping.confirmed = true; mapping.confidence = Math.min(99, mapping.confidence + 4); this.appendLog('字段映射', `${mapping.source} -> ${mapping.target} 已确认。`); } selectRule(rule: Rule): void { this.selectedRuleId = rule.id; this.appendLog('规则沙盘', `已选中 ${rule.title}。`); } toggleRuleTestCase(item: RuleTestCase): void { item.enabled = !item.enabled; } runRuleSandbox(): void { const enabledCount = this.enabledRuleTestCases.length; const rule = this.selectedRule; const impact = Math.round((this.ruleSandboxWeight / 100) * (enabledCount + 1) * 3); this.ruleSimulationState = `${rule.title} 试算完成:预计强推荐 +${Math.max(1, impact)},需复核 -${Math.max(1, enabledCount)}。`; this.candidates = this.candidates.map((candidate, index) => index < 4 ? { ...candidate, score: Math.min(99, candidate.score + Math.max(1, enabledCount)), ruleHits: Array.from(new Set([...candidate.ruleHits, rule.category])) } : candidate ); this.appendLog('规则试算', this.ruleSimulationState); } selectTask(task: TaskRecord): void { this.selectedTaskId = task.id; this.appendLog('任务查看', `已打开 ${task.id}:${task.brand}。`); } copyTask(task: TaskRecord): void { const nextId = `TH-202605-${String(this.taskRecords.length + 20).padStart(3, '0')}`; const copied: TaskRecord = { ...task, id: nextId, status: '待确认', owner: '商务-王一', version: '需求 v0.1', lastAction: `从 ${task.id} 复制行业规则和模板,等待上传新 Brief` }; this.taskRecords = [copied, ...this.taskRecords]; this.selectedTaskId = nextId; this.taskTimeline = [ { taskId: nextId, time: '刚刚', title: '复制为新任务', detail: `复用 ${task.brand} 的规则组合、导出模板和复盘偏好。` }, ...this.taskTimeline ]; this.appendLog('任务复制', `${task.id} 已复制为 ${nextId}。`); } sendTaskFeedbackToLearning(): void { const text = `${this.selectedTask.brand}:${this.selectedTask.lastAction}`; this.feedbackText = `${this.feedbackText}\n任务复盘:${text}`; this.activeView = 'learning'; this.appendLog('任务复盘', `${this.selectedTask.id} 的最近动作已加入反馈学习。`); } setTemplatePreviewMode(mode: '客户版' | '内部复核版' | '复盘版'): void { this.templatePreviewMode = mode; this.appendLog('模板预览', `已切换为${mode}。`); } toggleTemplateField(field: TemplateField): void { if (field.required) { field.selected = true; return; } field.selected = !field.selected; } applyTemplateFieldsToDelivery(): void { const active = new Set(this.templateFields.filter((field) => field.selected).map((field) => field.label)); this.deliveryFields = this.deliveryFields.map((field) => ({ ...field, selected: active.has(field.label) || field.selected })); this.exportState = `已应用 ${this.visibleTemplateFields.length} 个模板字段到导出配置`; this.appendLog('模板应用', `${this.selectedTemplate.name} 的字段配置已同步到名单交付。`); } setView(view: ViewKey): void { this.activeView = view; } goStep(step: number): void { this.currentStep = step; this.updateSteps(); } onFileSelected(event: Event): void { const input = event.target as HTMLInputElement; const file = input.files?.[0]; if (!file) { return; } this.uploadedFileName = file.name; this.currentStep = 1; this.exportState = 'Brief 已上传,等待解析'; this.updateSteps(); } analyzeBrief(): void { this.currentStep = 2; this.requirements = this.requirements.map((item, index) => ({ ...item, confidence: Math.min(99, item.confidence + (index % 2 === 0 ? 2 : 1)) })); this.activities[0] = { ...this.activities[0], status: '完成' }; this.exportState = '已抽取 6 个关键需求点'; this.updateSteps(); } confirmDemand(): void { this.currentStep = 3; this.exportState = '需求已确认,等待规则选择'; this.updateSteps(); } toggleRequirementEdit(item: Requirement): void { item.editable = !item.editable; } addRequirement(): void { this.requirements = [ ...this.requirements, { label: '临时补充', value: '优先选择历史合作中客户反馈好、执行配合度高的账号', confidence: 100, selected: true, editable: true, evidence: '商务手动补充' } ]; } addTemporaryRule(): void { const next = String(this.rules.length + 1).padStart(3, '0'); this.rules = [ ...this.rules, { id: `R-${next}`, title: this.tempRuleTitle, description: '本次任务临时生效,可在复盘后保存为长期规则。', category: '临时策略', source: '临时补充', selected: true, impact: this.tempRuleImpact } ]; this.exportState = '临时规则已加入本次提号'; } runResourceSync(): void { this.resources = this.resources.map((resource, index) => ({ ...resource, fresh: Math.min(96, resource.fresh + 4 + index), matched: resource.matched + 6 + index })); this.fieldMappings = this.fieldMappings.map((mapping, index) => ({ ...mapping, confidence: Math.min(99, mapping.confidence + 3), confirmed: index < 4 ? true : mapping.confirmed })); this.activities[1] = { ...this.activities[1], status: '完成' }; this.exportState = '资源库已重新同步并更新字段映射'; } confirmAllMappings(): void { this.fieldMappings = this.fieldMappings.map((mapping) => ({ ...mapping, confirmed: true })); } generateRecommendations(): void { this.currentStep = 4; this.exportState = `已生成 ${this.totalMatchedCandidates} 个候选,当前展示 Top ${Math.min(12, this.candidates.length)}`; this.activities = this.activities.map((activity, index) => ({ ...activity, status: index < 3 ? '完成' : '进行中' })); this.candidates = this.candidates.map((candidate, index) => ({ ...candidate, score: Math.min(97, candidate.score + (index < 2 ? 2 : 0)), selected: index < 4, status: candidate.status === '已剔除' ? '备选' : candidate.status })); this.updateSteps(); } runSampling(): void { this.currentStep = 4; this.samplingTasks = this.samplingTasks.map((task) => ({ ...task, progress: 100, status: '完成', result: task.result.includes('等待') ? '已完成近10篇内容采样,等待商务复核' : task.result })); this.activities[3] = { ...this.activities[3], status: '完成' }; this.candidates = this.candidates.map((candidate) => ({ ...candidate, styleMatch: candidate.status === '需复核' ? Math.min(79, candidate.styleMatch + 4) : Math.min(96, candidate.styleMatch + 2) })); this.exportState = '近10篇内容采样已完成'; this.updateSteps(); } toggleRule(rule: Rule): void { rule.selected = !rule.selected; } toggleCandidate(candidate: Candidate): void { candidate.selected = !candidate.selected; } openCandidate(candidate: Candidate): void { this.selectedCandidateId = candidate.id; this.selectedNoteId = candidate.recentNotes[0]?.id ?? ''; } selectNote(note: NoteDetail): void { this.selectedNoteId = note.id; } updateCandidateStatus(candidate: Candidate, status: CandidateStatus): void { candidate.status = status; candidate.selected = status !== '已剔除'; } rotateCandidate(): void { this.candidates = this.candidates.map((candidate, index) => { if (candidate.status === '需复核') { return { ...candidate, status: '备选', score: candidate.score + 5, selected: true }; } return index === 0 ? { ...candidate, score: Math.min(99, candidate.score + 1) } : candidate; }); this.exportState = '已按当前规则换一批并补足备选'; } extractRules(): void { const suggestions: LearnedRule[] = [ { title: '小红书减少泛美妆号,优先生活方式和通勤场景', description: '当 Brief 或反馈中出现“真实测评、通勤、生活方式”时,生活方式标签加权 +12,泛美妆标签降权 -10。', confidence: 93, action: '建议加入长期规则', approved: false }, { title: '抖音预算压低时保留爆文率高账号', description: '抖音预算下调时,先按报价降序剔除,但爆文率、完播率进入保护分。', confidence: 87, action: '建议加入预算策略', approved: false }, { title: '客户明确不要硬广时,营销资讯类账号自动进入复核池', description: '当反馈出现“硬广、资讯感、种草不自然”等表述时,资讯类账号风格分扣 8 分。', confidence: 82, action: '建议加入风格风控', approved: false } ]; this.learnedRules = suggestions; this.currentStep = 6; this.updateSteps(); } approveLearnedRule(item: LearnedRule): void { item.approved = true; this.rules = [ ...this.rules, { id: `R-${String(this.rules.length + 1).padStart(3, '0')}`, title: item.title, description: item.description, category: '反馈学习', source: 'AI提炼', selected: true, impact: `置信度 ${item.confidence}%` } ]; this.exportState = '反馈规则已确认入库'; } rejectLearnedRule(item: LearnedRule): void { this.learnedRules = this.learnedRules.filter((rule) => rule.title !== item.title); } selectTemplate(template: TemplateItem): void { this.templates = this.templates.map((item) => ({ ...item, selected: item.name === template.name })); this.exportState = `已选择模板:${template.name}`; } async exportRecommendations(): Promise { this.currentStep = 5; this.updateSteps(); const selected = this.candidates.filter((candidate) => candidate.selected && candidate.status !== '已剔除'); try { const response = await fetch(`${getApiBaseUrl()}/api/recommendations/export`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ candidates: selected }) }); if (!response.ok) { throw new Error('API unavailable'); } const blob = await response.blob(); this.triggerDownload(blob, '客户A-新锐护肤品牌-需求推荐名单.xls'); this.apiState = 'Node API 已生成导出文件'; this.exportState = '推荐名单已导出'; return; } catch { const html = this.buildExcelHtml(selected); const blob = new Blob([html], { type: 'application/vnd.ms-excel;charset=utf-8' }); this.triggerDownload(blob, '客户A-新锐护肤品牌-需求推荐名单.xls'); this.apiState = '前端兜底生成导出文件'; this.exportState = '推荐名单已导出'; } } private recalculateScenario(): void { const activeIds = new Set(this.briefScenarios.filter((scenario) => scenario.selected).map((scenario) => scenario.id)); this.candidates = this.candidates.map((candidate) => { const base = this.candidateBaseline.get(candidate.id); if (!base) { return candidate; } let score = base.score; let styleMatch = base.styleMatch; let status = base.status; const ruleHits = [...base.ruleHits]; let risk = base.risk; let reason = base.reason; let selected = candidate.selected; if (activeIds.has('budget-down')) { if (candidate.price > 12000) { score -= 6; status = '备选'; risk = `${base.risk} 客户压预算后需谈价或替换。`; } else if (candidate.price <= 7000) { score += 4; ruleHits.push('预算压缩保护'); } } if (activeIds.has('life-style')) { const lifestyleHit = candidate.tags.some((tag) => ['生活方式', '通勤', '真实测评'].includes(tag)); if (lifestyleHit) { score += 5; styleMatch += 4; ruleHits.push('生活方式加权'); } else if (candidate.platform === '微信') { score -= 5; status = '需复核'; risk = '客户偏生活方式后,资讯号需人工复核。'; } } if (activeIds.has('strict-style')) { if (styleMatch < 80) { score -= 8; status = '需复核'; ruleHits.push('硬广风格复核'); } else { score += 2; } } if (activeIds.has('keep-bilibili') && candidate.platform === 'B站') { score += 5; selected = true; status = status === '已剔除' ? '备选' : status; reason = `${base.reason} 当前情景要求保留专业背书账号。`; ruleHits.push('B站背书保留'); } return { ...candidate, score: Math.max(50, Math.min(99, score)), styleMatch: Math.max(45, Math.min(99, styleMatch)), status, selected, risk, reason, ruleHits: Array.from(new Set(ruleHits)) }; }); this.exportState = this.activeScenarioCount > 0 ? `已按 ${this.activeScenarioCount} 个客户情景试算名单` : '已恢复默认提号策略'; } private appendLog(action: string, detail: string): void { const now = new Date(); const time = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`; this.interactionLogs = [{ time, action, detail }, ...this.interactionLogs].slice(0, 6); } private updateSteps(): void { this.steps = this.steps.map((step, index) => { const position = index + 1; if (position < this.currentStep) { return { ...step, status: 'done' }; } if (position === this.currentStep) { return { ...step, status: 'active' }; } return { ...step, status: 'todo' }; }); } private triggerDownload(blob: Blob, filename: string): void { const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; link.click(); URL.revokeObjectURL(url); } private buildExcelHtml(candidates: Candidate[]): string { const rows = candidates .map( (candidate) => ` ${candidate.platform} ${candidate.name} ${candidate.fans} ${candidate.price} ${candidate.score} ${candidate.styleMatch}% ${candidate.status} ${candidate.owner} ${candidate.supplier} ${candidate.reason} ${candidate.risk} ` ) .join(''); return ` ${rows}
平台账号粉丝量报价综合分风格匹配推荐状态责任媒介供应商推荐理由风险提示
`; } }