import React, { useState, useEffect, useRef } from 'react'; import { UploadCloud, Settings, Download, CheckCircle, Loader2, Activity, Ban, Trash2, Plus, AlertTriangle, FileText } from 'lucide-react'; export default function AmazonBidOptimizer() { const [activeTab, setActiveTab] = useState('optimizer'); const [isProcessing, setIsProcessing] = useState(false); const [progressMsg, setProgressMsg] = useState(""); const [fileData, setFileData] = useState(null); const [slimExport, setSlimExport] = useState(true); const fileInputRef = useRef(null); const [extractedSpus, setExtractedSpus] = useState([]); const [activeCampaigns, setActiveCampaigns] = useState([]); const [dedupSet, setDedupSet] = useState(new Set()); const [activeAdGroupIds, setActiveAdGroupIds] = useState(new Set()); const [campaignStartDates, setCampaignStartDates] = useState(new Map()); const [allCampaignMeta, setAllCampaignMeta] = useState([]); const [availablePortfolios, setAvailablePortfolios] = useState([]); const [selectedPortfolioForSpu, setSelectedPortfolioForSpu] = useState('L'); const [spuRules, setSpuRules] = useState([{ id: Date.now(), keyword: '', targetAcos: 25 }]); const [summary, setSummary] = useState(null); const [params, setParams] = useState({ acosBuffer: 10, maxBid: 1.00, minBid: 0.10, clickThreshold: 20, impressionThreshold: 500, increasePcnt: 10, decreasePcnt: 20, increaseTosPcnt: 10, negClickThreshold: 25, negSpendThreshold: 15.0, enableAutoNegatives: true, // 默认开启搜索词拉黑 pauseClickThresholdTarget: 35, pauseSpendThresholdTarget: 20, pauseAcosThresholdTarget: 60, pauseClickThresholdSku: 30, pauseSpendThresholdSku: 20, pauseAcosThresholdSku: 50, pauseSpendThresholdCampaign: 40, pauseSpendThresholdAdGroup: 30, enableTimeBasedPause: false, pauseTimeThresholdDays: 14 }); const [globalNegState, setGlobalNegState] = useState({ spu: '', exact: '', phrase: '', asin: '' }); useEffect(() => { if (typeof window !== 'undefined' && !window.XLSX) { const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js'; script.async = true; document.body.appendChild(script); } }, []); useEffect(() => { if (!allCampaignMeta || allCampaignMeta.length === 0) return; const spuSet = new Set(); allCampaignMeta.forEach(camp => { if (selectedPortfolioForSpu && selectedPortfolioForSpu !== 'ALL' && camp.portfolio !== selectedPortfolioForSpu.toUpperCase()) { return; } if (camp.state !== 'enabled' && camp.state !== '已启用') { return; } const segments = camp.cName.split(/[-_ \/]+/); for (const seg of segments) { if (!/[\u4e00-\u9fa5]/.test(seg) && seg.length >= 4 && /[A-Za-z0-9]/.test(seg)) { spuSet.add(seg.toUpperCase()); break; } } }); const spuArray = Array.from(spuSet).sort(); setExtractedSpus(spuArray); let autoGeneratedRules = spuArray.map((spu, index) => ({ id: Date.now() + index, keyword: spu, targetAcos: 25 })); autoGeneratedRules.push({ id: Date.now() + 10000, keyword: '', targetAcos: 25 }); setSpuRules(autoGeneratedRules); }, [allCampaignMeta, selectedPortfolioForSpu]); const handleParamChange = (e) => { const { name, value, type, checked } = e.target; const finalValue = type === 'checkbox' ? checked : (parseFloat(value) || 0); setParams(prev => ({ ...prev, [name]: finalValue })); setSummary(null); }; const addSpuRule = () => setSpuRules([{ id: Date.now(), keyword: '', targetAcos: 25 }, ...spuRules]); const updateSpuRule = (id, field, value) => { setSpuRules(spuRules.map(r => r.id === id ? { ...r, [field]: value } : r)); setSummary(null); }; const removeSpuRule = (id) => { setSpuRules(spuRules.filter(r => r.id !== id)); setSummary(null); }; const getVal = (row, keys) => { for (let k of keys) { if (row[k] !== undefined && row[k] !== null) return String(row[k]).trim(); } return ""; }; const getMetric = (row, possibleKeys) => { const rowKeys = Object.keys(row); for (let pk of possibleKeys) { const lowerPk = pk.toLowerCase().trim(); const matched = rowKeys.find(k => k.toLowerCase().trim() === lowerPk); if (matched && row[matched] !== undefined && row[matched] !== '') { const val = String(row[matched]).replace(/[,%\$]/g, '').trim(); const parsed = parseFloat(val); return isNaN(parsed) ? 0 : parsed; } } return 0; }; // 增强版日期解析,兼容 2025.4.28 及 2025-04-28 等格式 const parseAmzDate = (dateStr) => { if (!dateStr) return null; dateStr = String(dateStr).trim().replace(/\./g, '/').replace(/-/g, '/'); if (/^\d{8}$/.test(dateStr)) { return new Date(parseInt(dateStr.slice(0,4)), parseInt(dateStr.slice(4,6))-1, parseInt(dateStr.slice(6,8))); } const d = new Date(dateStr); if (!isNaN(d.getTime())) return d; return null; }; const handleFileUpload = (e) => { const file = e.target.files[0]; if (!file) return; setIsProcessing(true); setProgressMsg("正在解析底层数据引擎..."); setSummary(null); setFileData(null); const reader = new FileReader(); reader.onload = (evt) => { setTimeout(() => { try { const data = new Uint8Array(evt.target.result); if (!window.XLSX) { alert("Excel 解析库尚未加载完成,请稍后再试!"); setIsProcessing(false); return; } const workbook = window.XLSX.read(data, { type: 'array' }); const targetSheetName = workbook.SheetNames.find(n => n.includes('Sponsored Products') || n.includes('商品推广')) || workbook.SheetNames[0]; const worksheet = workbook.Sheets[targetSheetName]; const originalHeaders = window.XLSX.utils.sheet_to_json(worksheet, { header: 1 })[0]; // 智能物理隔离报错历史留存列 const errorCols = [ 'original sheet', 'original record #', 'error type', 'error code', 'error message', 'optimizer note', '初始工作表', '初始记录 #', '错误类型', '错误代码', '错误消息' ]; const cleanHeaders = originalHeaders.filter(h => { const hStr = String(h).trim().toLowerCase(); if (!hStr) return false; if (errorCols.includes(hStr)) return false; return true; }); const jsonData = window.XLSX.utils.sheet_to_json(worksheet, { raw: false }); const stSheetName = workbook.SheetNames.find(n => n.includes('Search Term Report') || n.includes('搜索词报告')); const stData = stSheetName ? window.XLSX.utils.sheet_to_json(workbook.Sheets[stSheetName], { raw: false }) : null; const allActiveCampaigns = []; const tempDedupSet = new Set(); const tempActiveAdGroups = new Set(); const spuSet = new Set(); const tempCampStartDates = new Map(); jsonData.forEach(row => { const entity = getVal(row, ['Entity', '实体', '实体层级', 'Record Type']).toLowerCase(); const state = getVal(row, ['State', '状态', 'Status']).toLowerCase(); const cName = getVal(row, ['Campaign Name', '广告活动名称', 'Campaign Name (Informational only)']); const cId = getVal(row, ['Campaign Id', '广告活动编号', 'Campaign ID']); const aId = getVal(row, ['Ad Group Id', '广告组编号', 'Ad Group ID']); const pNameStr = getVal(row, ['Portfolio Name', '广告组合名称', 'Portfolio Name (Informational only)', '广告组合名称(仅供参考)']).toUpperCase(); if (aId && (state === 'enabled' || state === 'paused' || state === '已启用' || state === '已暂停')) { tempActiveAdGroups.add(aId); } if ((entity === 'campaign' || entity === '广告活动') && state !== 'archived' && state !== '已归档') { allActiveCampaigns.push({ cId, cName }); if (pNameStr === selectedPortfolioForSpu.toUpperCase()) { const segments = cName.split(/[-_ \/]+/); for (const seg of segments) { if (!/[\u4e00-\u9fa5]/.test(seg) && seg.length >= 4 && /[A-Za-z0-9]/.test(seg)) { spuSet.add(seg.toUpperCase()); break; } } } const sDateRaw = getVal(row, ['Start Date', '开始日期']); const sDate = parseAmzDate(sDateRaw); if (sDate) tempCampStartDates.set(String(cId).trim(), sDate); } if (entity.includes('negative') || entity.includes('否定')) { const kw = getVal(row, ['Keyword Text', '关键词文本']).toLowerCase(); const expr = getVal(row, ['Product Targeting Expression', '商品投放表达式', 'Product targeting expression']).toLowerCase(); const matchTypeRaw = getVal(row, ['Match Type', '匹配类型']).toLowerCase(); const isCampaignLevel = entity.includes('campaign') || entity.includes('广告活动') || !aId; let matchType = ''; if (matchTypeRaw.includes('exact') || matchTypeRaw.includes('精准')) matchType = 'exact'; else if (matchTypeRaw.includes('phrase') || matchTypeRaw.includes('词组')) matchType = 'phrase'; if (kw) { if (isCampaignLevel) { tempDedupSet.add(`CAMP_${cId}_${kw}_${matchType}`); } else { tempDedupSet.add(`ADG_${cId}_${aId}_${kw}_${matchType}`); } } if (expr) { if (isCampaignLevel) { tempDedupSet.add(`CAMP_${cId}_${expr}`); } else { tempDedupSet.add(`ADG_${cId}_${aId}_${expr}`); } } } }); const spuArray = Array.from(spuSet).sort(); setExtractedSpus(spuArray); setActiveCampaigns(allActiveCampaigns); setDedupSet(tempDedupSet); setActiveAdGroupIds(tempActiveAdGroups); setCampaignStartDates(tempCampStartDates); let autoGeneratedRules = spuArray.map((spu, index) => ({ id: Date.now() + index, keyword: spu, targetAcos: 25 })); autoGeneratedRules.push({ id: Date.now() + 10000, keyword: '', targetAcos: 25 }); setSpuRules(autoGeneratedRules); setFileData({ originalWorkbook: workbook, sheetName: targetSheetName, headers: cleanHeaders, data: jsonData, stData: stData, fileName: file.name }); setIsProcessing(false); } catch (error) { console.error(error); alert("文件解析失败,请确认是否为合法的 Amazon Bulk 文件。"); setIsProcessing(false); } }, 50); }; reader.readAsArrayBuffer(file); }; const handleEngineAProcess = () => { if (!fileData || !fileData.data) return; setIsProcessing(true); setTimeout(() => { let modifiedCount = 0; let negativeAddedCount = 0; let tierCounts = { pausedKw: 0, pausedSku: 0, pausedCampaign: 0, pausedAdGroup: 0, tier1: 0, tier2: 0, tier3: 0, tier4: 0, tosBoost: 0 }; let skipDetails = { notTargeting: 0, notEnabled: 0, parentNotActive: 0, amazonBug: 0, nullBid: 0, spuExcluded: 0, targetMetHold: 0, noConditionMet: 0, parseError: 0, alreadyNegated: 0, parentNotFound: 0, apiError: 0 }; let unifiedLogs = []; let negativeKeywordsToCreate = []; const localDedupSet = new Set(dedupSet); const sortedSpuRules = [...spuRules].sort((a, b) => b.keyword.length - a.keyword.length); const headers = fileData.headers || []; const h_entity = headers.find(h => String(h).trim().toLowerCase() === 'entity' || String(h).trim() === '实体层级' || String(h).trim() === '实体' || String(h).trim() === 'record type') || 'Entity'; const h_state = headers.find(h => String(h).trim().toLowerCase() === 'state' || String(h).trim() === '状态' || String(h).trim() === 'status') || 'State'; const h_bid = headers.find(h => String(h).trim().toLowerCase() === 'bid' || String(h).trim() === '竞价' || String(h).trim() === 'max bid') || 'Bid'; const h_op = headers.find(h => String(h).trim().toLowerCase() === 'operation' || String(h).trim() === '操作') || 'Operation'; const h_percentage = headers.find(h => String(h).trim().toLowerCase().includes('percentage') || String(h).trim().includes('百分比')) || 'Percentage'; const h_campState = headers.find(h => String(h).trim().toLowerCase().includes('campaign state') || String(h).trim().includes('广告活动状态')); const h_adgState = headers.find(h => String(h).trim().toLowerCase().includes('ad group state') || String(h).trim().includes('广告组状态')); // 1. 搜索词拉黑 if (fileData.stData && params.enableAutoNegatives) { const baseRow = fileData.data[0] || {}; const getK = (ch, en) => (ch in baseRow ? ch : en); fileData.stData.forEach(row => { const campId = getVal(row, ['Campaign ID', '广告活动编号', 'Campaign Id']); const adgId = getVal(row, ['Ad Group ID', '广告组编号', 'Ad Group Id']); const campName = getVal(row, ['Campaign Name (Informational only)', '广告活动名称(仅供参考)', 'Campaign Name']); let isMatched = false; for (const rule of sortedSpuRules) { const kw = rule.keyword.trim().toLowerCase(); if (kw === '' || campName.toLowerCase().includes(kw)) { isMatched = true; break; } } if (!isMatched) return; const searchTerm = getVal(row, ['Customer Search Term', '顾客搜索词']); const clicks = parseInt(getMetric(row, ['clicks', '点击量']), 10); const spend = parseFloat(getMetric(row, ['spend', '花费'])); const orders = parseInt(getMetric(row, ['orders', '订单数量', '7天总订单数']), 10); if (!searchTerm || /^[Bb]0[A-Za-z0-9]{8}$/i.test(searchTerm)) return; if (orders === 0 && (clicks >= params.negClickThreshold || spend >= params.negSpendThreshold)) { if (!activeAdGroupIds.has(adgId)) { skipDetails.parentNotFound++; return; } const searchT = searchTerm.toLowerCase().trim(); const campIdStr = String(campId).trim(); const adgIdStr = String(adgId).trim(); const adgKey = `ADG_${campIdStr}_${adgIdStr}_${searchT}_exact`; const campKey = `CAMP_${campIdStr}_${searchT}_exact`; if (localDedupSet.has(adgKey) || localDedupSet.has(campKey)) { skipDetails.alreadyNegated++; return; } localDedupSet.add(adgKey); negativeAddedCount++; unifiedLogs.push({ type: 'negative', campaign: campName, target: searchTerm, action: '精准否定', metrics: `花费: $${spend.toFixed(2)} | 点击: ${clicks}`, result: `+ 新增否定`, reason: `0单且花费或点击超标` }); let newNegRow = {}; newNegRow[getK('产品', 'Product')] = getK('商品推广', 'Sponsored Products'); newNegRow[h_entity] = getK('否定关键词', 'Negative Keyword'); newNegRow[h_op] = 'Create'; newNegRow[getK('广告活动编号', 'Campaign ID')] = campIdStr; newNegRow[getK('广告组编号', 'Ad Group ID')] = adgIdStr; newNegRow[getK('关键词文本', 'Keyword Text')] = searchTerm; newNegRow[getK('匹配类型', 'Match Type')] = getK('精准否定', 'Negative Exact'); newNegRow[h_state] = getK('已启用', 'Enabled'); negativeKeywordsToCreate.push(newNegRow); } }); } // 2. 竞价与熔断处理 const processedData = []; const biddingAdjIndices = []; const campsToBoostTos = new Map(); fileData.data.forEach((originalRow, index) => { const row = { ...originalRow }; row.__changed_state = false; row.__changed_bid = false; row.__changed_percentage = false; try { const rowErrMsg = getVal(row, ['Error message', '错误消息', 'Error type', '错误类型']); if (rowErrMsg && String(rowErrMsg).trim() !== '') { skipDetails.apiError++; processedData.push(row); return; } const entityStr = getVal(row, [h_entity]).toLowerCase(); const stateStr = getVal(row, [h_state]).toLowerCase(); const campStateVal = h_campState ? row[h_campState] : undefined; const adgStateVal = h_adgState ? row[h_adgState] : undefined; const campStateStr = campStateVal ? String(campStateVal).toLowerCase() : ''; const adgStateStr = adgStateVal ? String(adgStateVal).toLowerCase() : ''; if ( campStateStr.includes('archived') || campStateStr.includes('已归档') || campStateStr.includes('paused') || campStateStr.includes('已暂停') || adgStateStr.includes('archived') || adgStateStr.includes('已归档') || adgStateStr.includes('paused') || adgStateStr.includes('已暂停') ) { skipDetails.parentNotActive++; processedData.push(row); return; } if (entityStr === 'bidding adjustment' || entityStr === '竞价方案') { biddingAdjIndices.push(processedData.length); processedData.push(row); return; } const isNegative = entityStr.includes('negative') || entityStr.includes('否定'); const isTargeting = !isNegative && ['keyword', 'product targeting', 'targeting expression', '关键词', '商品投放', '投放表达式'].includes(entityStr); const isProductAd = ['product ad', '商品广告', 'ad', '广告', '商品推广广告'].includes(entityStr); const isCampaign = entityStr === 'campaign' || entityStr === '广告活动'; const isAdGroup = entityStr === 'ad group' || entityStr === '广告组'; if (!isTargeting && !isProductAd && !isCampaign && !isAdGroup) { skipDetails.notTargeting++; processedData.push(row); return; } if (!['enabled', '已启用'].includes(stateStr)) { skipDetails.notEnabled++; processedData.push(row); return; } const exprStr = getVal(row, ['Product Targeting Expression', '商品投放表达式', 'Product targeting expression']).toLowerCase(); if (exprStr.includes('keyword-group')) { skipDetails.amazonBug = (skipDetails.amazonBug || 0) + 1; processedData.push(row); return; } const campName = getVal(row, ['Campaign Name (Informational only)', '广告活动名称(仅供参考)', 'Campaign Name', '广告活动名称']); const campIdStr = String(getVal(row, ['Campaign Id', '广告活动编号', 'Campaign ID'])).trim(); const pNameStr = getVal(row, ['Portfolio Name', '广告组合名称', 'Portfolio Name (Informational only)', '广告组合名称(仅供参考)']).toUpperCase(); if (selectedPortfolioForSpu && selectedPortfolioForSpu !== 'ALL' && pNameStr !== selectedPortfolioForSpu.toUpperCase()) { skipDetails.spuExcluded++; processedData.push(row); return; } let appliedTargetAcos = null; let isMatched = false; for (const rule of sortedSpuRules) { const kw = rule.keyword.trim().toLowerCase(); if (kw === '' || campName.toLowerCase().includes(kw)) { appliedTargetAcos = parseFloat(rule.targetAcos); isMatched = true; break; } } if (!isMatched) { skipDetails.spuExcluded++; processedData.push(row); return; } // 提取 Campaign 存活天数 (若上传的是 Error Report,这里可能会缺失溯源时间) const campStartDateObj = campaignStartDates.get(campIdStr); let diffDays = 0; if (campStartDateObj) { diffDays = Math.ceil(Math.abs(new Date() - campStartDateObj) / (1000 * 60 * 60 * 24)); } const clicks = parseInt(getMetric(row, ['clicks', '点击量']), 10); const impressions = parseInt(getMetric(row, ['impressions', '展示量']), 10); const spend = parseFloat(getMetric(row, ['spend', '花费'])); const sales = parseFloat(getMetric(row, ['sales', '销售额', '7天总销售额', '14天总销售额', '30 day total sales', '14 day total sales', '30-day total sales'])); const orders = parseInt(getMetric(row, ['orders', '订单数量', '订单', '7天总订单数', '14天总订单数', '30 day total orders', '14 day total orders', '30-day total orders']), 10); const acos = sales > 0 ? (spend / sales) * 100 : 0; const pausedStateValue = (h_state === '状态' || h_state === 'Status') ? '已暂停' : 'paused'; let isPaused = false; let action = ''; // 宏观防御网 if (isCampaign || isAdGroup) { if (orders === 0) { if (isCampaign && spend >= params.pauseSpendThresholdCampaign) { isPaused = true; action = `活动级熔断 (0单花费 $${spend} ≥ $${params.pauseSpendThresholdCampaign})`; tierCounts.pausedCampaign++; } else if (isAdGroup && spend >= params.pauseSpendThresholdAdGroup) { isPaused = true; action = `广告组熔断 (0单花费 $${spend} ≥ $${params.pauseSpendThresholdAdGroup})`; tierCounts.pausedAdGroup++; } if (!isPaused && params.enableTimeBasedPause && diffDays >= params.pauseTimeThresholdDays) { isPaused = true; action = `${isCampaign?'活动':'广告组'}存活超时熔断 (运行 ≥${params.pauseTimeThresholdDays}天 且 0单)`; if (isCampaign) tierCounts.pausedCampaign++; if (isAdGroup) tierCounts.pausedAdGroup++; } } if (isPaused) { modifiedCount++; const targetText = isCampaign ? '[整体广告活动]' : `[广告组] ${getVal(row, ['Ad Group Name', '广告组名称'])}`; unifiedLogs.push({ type: 'pause', campaign: campName, target: targetText, action: '宏观拦截暂停', metrics: `花费: $${spend.toFixed(2)} | 订单: ${orders}`, result: `状态 ➔ 已暂停`, reason: action }); row[h_state] = pausedStateValue; row[h_op] = 'Update'; row.__changed_state = true; } else { if (isCampaign) { if (orders > 0 && acos <= appliedTargetAcos) { campsToBoostTos.set(campIdStr, campName); } } skipDetails.noConditionMet++; } processedData.push(row); return; } // 微观防御网 (SKU 层级) if (isProductAd) { if (orders === 0) { if (clicks >= params.pauseClickThresholdSku || spend >= params.pauseSpendThresholdSku) { isPaused = true; action = `SKU 熔断 (0单超${params.pauseClickThresholdSku}点击或$${params.pauseSpendThresholdSku}花费)`; tierCounts.pausedSku++; } else if (params.enableTimeBasedPause && diffDays >= params.pauseTimeThresholdDays) { // 【V9.2铁血熔断】无视有无曝光消耗,只要开启时间锁且超期 0 产出,全域绞杀 isPaused = true; action = `SKU 存活超时熔断 (随活动运行 ≥${params.pauseTimeThresholdDays}天 且 0产出)`; tierCounts.pausedSku++; } } else if (orders > 0 && acos >= params.pauseAcosThresholdSku) { isPaused = true; action = `SKU 熔断 (ACoS ≥ ${params.pauseAcosThresholdSku}%)`; tierCounts.pausedSku++; } if (isPaused) { modifiedCount++; const targetText = getVal(row, ['SKU', 'ASIN (Informational only)', 'ASIN(仅供参考)']) || '未知SKU'; unifiedLogs.push({ type: 'pause', campaign: campName, target: `[商品] ${targetText}`, action: '停用商品(SKU)', metrics: `花费: $${spend.toFixed(2)} | 点击: ${clicks} | ACoS: ${acos > 0 ? acos.toFixed(2)+'%' : '-'}`, result: `状态 ➔ 已暂停`, reason: action }); row[h_state] = pausedStateValue; row[h_op] = 'Update'; row.__changed_state = true; } else { skipDetails.noConditionMet++; } processedData.push(row); return; } // 微观竞价引擎 (投放 Targeting) if (isTargeting) { const sweetSpotThreshold = Math.max(0, appliedTargetAcos - params.acosBuffer); if (orders === 0) { if (clicks >= params.pauseClickThresholdTarget || spend >= params.pauseSpendThresholdTarget) { isPaused = true; action = `投放熔断 (0单超${params.pauseClickThresholdTarget}点击或$${params.pauseSpendThresholdTarget}花费)`; tierCounts.pausedKw++; } else if (params.enableTimeBasedPause && diffDays >= params.pauseTimeThresholdDays) { // 【V9.2铁血熔断】全域微观时间熔断:囊括关键词、ASIN 及自动定向组 isPaused = true; action = `投放存活超时熔断 (随活动运行 ≥${params.pauseTimeThresholdDays}天 且 0产出)`; tierCounts.pausedKw++; } } else if (orders > 0 && acos >= params.pauseAcosThresholdTarget) { isPaused = true; action = `投放熔断 (ACoS ≥ ${params.pauseAcosThresholdTarget}%)`; tierCounts.pausedKw++; } if (isPaused) { modifiedCount++; const targetText = getVal(row, ['Keyword Text', '关键词文本', 'Product Targeting Expression', '商品投放表达式']) || '未知实体'; unifiedLogs.push({ type: 'pause', campaign: campName, target: targetText, action: '暂停投放', metrics: `花费: $${spend.toFixed(2)} | 曝光: ${impressions} | 点击: ${clicks} | ACoS: ${acos > 0 ? acos.toFixed(2)+'%' : '-'}`, result: `状态 ➔ 已暂停`, reason: action }); row[h_state] = pausedStateValue; row[h_op] = 'Update'; row.__changed_state = true; processedData.push(row); return; } const bidStr = getMetric(row, ['bid', '竞价', 'max bid']); if (bidStr <= 0) { skipDetails.nullBid++; processedData.push(row); return; } const currentBid = parseFloat(bidStr); let newBid = currentBid; if (orders === 0 && clicks >= params.clickThreshold) { newBid = currentBid * (1 - (params.decreasePcnt / 100)); action = 'Tier 1 降价'; tierCounts.tier1++; } else if (orders > 0 && acos < sweetSpotThreshold) { newBid = currentBid * (1 + (params.increasePcnt / 100)); action = 'Tier 2 提价'; tierCounts.tier2++; } else if (orders > 0 && acos >= sweetSpotThreshold && acos <= appliedTargetAcos) { skipDetails.targetMetHold++; processedData.push(row); return; } else if (orders > 0 && acos > appliedTargetAcos) { newBid = currentBid * (appliedTargetAcos / acos); action = 'Tier 3 降价'; tierCounts.tier3++; } else if (impressions < params.impressionThreshold && clicks < params.clickThreshold) { // 只要逃过了前置时间熔断的词,曝光为0也会强制提价破冰 newBid = currentBid * 1.05; action = impressions === 0 ? 'Tier 4 冷启动破冰' : 'Tier 4 长尾激活'; tierCounts.tier4++; } else { skipDetails.noConditionMet++; processedData.push(row); return; } if (newBid > params.maxBid) newBid = params.maxBid; if (newBid < params.minBid) newBid = params.minBid; newBid = parseFloat(newBid.toFixed(2)); if (newBid !== currentBid) { modifiedCount++; const targetText = getVal(row, ['Keyword Text', '关键词文本', 'Product Targeting Expression', '商品投放表达式']) || '未知实体'; unifiedLogs.push({ type: 'bid', campaign: campName, target: targetText, action: '竞价调整', metrics: `目标ACoS: ${appliedTargetAcos}% | 实际: ${acos > 0 ? acos.toFixed(2)+'%' : '-'}`, result: `$${currentBid.toFixed(2)} ➔ $${newBid.toFixed(2)}`, reason: action }); row[h_bid] = newBid; row[h_op] = 'Update'; row.__changed_bid = true; } else { skipDetails.noConditionMet++; } processedData.push(row); } } catch (err) { skipDetails.parseError++; processedData.push(row); } }); // Bidding Adjustment 联动溢价 biddingAdjIndices.forEach(idx => { const row = processedData[idx]; const cId = String(getVal(row, ['Campaign Id', '广告活动编号', 'Campaign ID'])).trim(); const placement = getVal(row, ['Placement', '广告位']).toLowerCase(); if (campsToBoostTos.has(cId) && (placement.includes('top') || placement.includes('顶部'))) { let currentPcnt = parseInt(String(row[h_percentage] || '0').replace(/%/g, ''), 10); if (isNaN(currentPcnt)) currentPcnt = 0; let newPcnt = currentPcnt + params.increaseTosPcnt; if (newPcnt > 900) newPcnt = 900; if (newPcnt !== currentPcnt) { const campName = campsToBoostTos.get(cId); unifiedLogs.push({ type: 'bid', campaign: campName, target: '[广告位] 顶部搜索结果 (TOS)', action: '溢价联动提升', metrics: `活动整体ACoS达标,准许放量`, result: `${currentPcnt}% ➔ ${newPcnt}%`, reason: `联动溢价 +${params.increaseTosPcnt}%` }); row[h_percentage] = newPcnt; row[h_op] = 'Update'; row.__changed_percentage = true; modifiedCount++; tierCounts.tosBoost++; } } }); setSummary({ modifiedCount, negativeAddedCount, tierCounts, skipDetails, processedData, negativeKeywordsToCreate, unifiedLogs }); setIsProcessing(false); }, 500); }; const handleEngineAExport = () => { if (!summary || (!summary.processedData && summary.negativeKeywordsToCreate.length === 0)) return; const headers = fileData.headers || []; const h_op = headers.find(h => String(h).trim().toLowerCase() === 'operation' || String(h).trim() === '操作') || 'Operation'; let baseExportData = summary.processedData; if (slimExport) { baseExportData = summary.processedData.filter(row => { const opVal = row[h_op] || getVal(row, ['Operation', '操作']); return String(opVal).trim().toLowerCase() === 'update' || String(opVal).trim().toLowerCase() === 'create'; }); if (baseExportData.length === 0 && summary.negativeKeywordsToCreate.length === 0) { alert("当前没有发生任何调价修改,无需导出。"); return; } } let rawDataToExport = baseExportData.concat(summary.negativeKeywordsToCreate); // 【V9.2 神盾级黄金比例数据架构】 const isIdCol = (hLower) => hLower.includes('id') || hLower.includes('编号'); const isNumericCol = (hLower) => hLower === 'bid' || hLower === '竞价' || hLower === 'percentage' || hLower === '百分比' || hLower === 'max bid'; // V3 Update 必填基础列 + 实体三剑客(文本/匹配/表达式) const allowedUpdateCols = [ 'product', '产品', 'entity', '实体层级', '实体', 'record type', 'operation', '操作', 'campaign id', '广告活动编号', 'ad group id', '广告组编号', 'keyword id', '关键词编号', 'product targeting id', '商品投放 id', '商品投放编号', 'ad id', '广告编号', 'portfolio id', '广告组合编号', 'state', '状态', 'status', 'bid', '竞价', 'max bid', 'percentage', '百分比', 'placement', '广告位', 'keyword text', '关键词文本', 'match type', '匹配类型', 'product targeting expression', '商品投放表达式' ]; const sanitizedDataToExport = rawDataToExport.map(row => { const cleanRow = {}; const opVal = String(row[h_op] || getVal(row, ['Operation', '操作'])).trim().toLowerCase(); headers.forEach(h => { const hStrLower = String(h).trim().toLowerCase(); let val = row[h]; if (val === undefined) { const matchedKey = Object.keys(row).find(rk => String(rk).trim().toLowerCase() === hStrLower); if (matchedKey) val = row[matchedKey]; } // 神盾级降噪与强转模块 if (opVal === 'update' && slimExport) { if (allowedUpdateCols.includes(hStrLower)) { if (isIdCol(hStrLower)) { // ID 列强转 String 防止科学计数法崩溃 cleanRow[h] = val != null && val !== '' ? String(val).trim() : ''; } else if (isNumericCol(hStrLower)) { // 金额/百分比列强转浮点数 cleanRow[h] = val != null && val !== '' ? parseFloat(String(val).replace(/[,%\$]/g, '')) : ''; } else { cleanRow[h] = val != null ? val : ''; } } else { // 物理涂白所有其他可能致错的冗余指标列 cleanRow[h] = ''; } } else { // 对于 Create 或未开启极简导出 if (isIdCol(hStrLower)) { cleanRow[h] = val != null && val !== '' ? String(val).trim() : ''; } else if (isNumericCol(hStrLower)) { cleanRow[h] = val != null && val !== '' ? parseFloat(String(val).replace(/[,%\$]/g, '')) : ''; } else { cleanRow[h] = val != null ? val : ''; } } }); return cleanRow; }); const newWs = window.XLSX.utils.json_to_sheet(sanitizedDataToExport, { header: headers }); const newWb = window.XLSX.utils.book_new(); const baseRow = sanitizedDataToExport[0] || {}; const isChinese = Object.keys(baseRow).some(k => String(k).includes('广告活动') || String(k).includes('商品推广')); const finalSheetName = isChinese ? '商品推广活动' : 'Sponsored Products Campaigns'; window.XLSX.utils.book_append_sheet(newWb, newWs, finalSheetName); const dateStr = new Date().toISOString().slice(0,10).replace(/-/g,""); window.XLSX.writeFile(newWb, `EngineA_ShieldUpdate_${dateStr}.xlsx`); }; const handleExportLogs = () => { if (!summary || !summary.unifiedLogs || summary.unifiedLogs.length === 0) { alert("当前没有生成任何优化日志可供导出!"); return; } const logDataForExport = summary.unifiedLogs.map(log => ({ "操作类型 (Type)": log.action, "归属广告活动 (Campaign Name)": log.campaign, "具体操作对象 (Target)": log.target, "动作结果 (Result)": log.result, "触发规则原因 (Reason)": log.reason, "当时核心指标 (Metrics)": log.metrics })); const newWs = window.XLSX.utils.json_to_sheet(logDataForExport); const newWb = window.XLSX.utils.book_new(); window.XLSX.utils.book_append_sheet(newWb, newWs, '优化操作复盘日志'); const dateStr = new Date().toISOString().slice(0,10).replace(/-/g,""); window.XLSX.writeFile(newWb, `Optimization_Details_Log_${dateStr}.xlsx`); }; const handleEngineBExport = () => { if (!fileData) return; const { spu, exact, phrase, asin } = globalNegState; if (!spu) { alert("请先选择或输入目标 SPU!"); return; } const exList = Array.from(new Set(exact.split('\n').map(s => s.trim()).filter(Boolean))); const phList = Array.from(new Set(phrase.split('\n').map(s => s.trim()).filter(Boolean))); const asList = Array.from(new Set(asin.split('\n').map(s => s.trim()).filter(Boolean))); if (exList.length === 0 && phList.length === 0 && asList.length === 0) { alert("请至少输入一个待否定的特征!"); return; } const HEADERS = [ "Product", "Entity", "Operation", "Campaign Id", "Campaign Name", "Ad Group Id", "Ad Group Name", "Portfolio Id", "State", "Keyword Text", "Match Type", "Product Targeting Expression" ]; const buildRow = (cId, cName, entity, text, match, expr) => { let r = {}; HEADERS.forEach(h => r[h] = ""); r["Product"] = "Sponsored Products"; r["Entity"] = entity; r["Operation"] = "Create"; r["Campaign Id"] = String(cId); r["Campaign Name"] = String(cName); r["State"] = "enabled"; if(text !== undefined && text !== '') r["Keyword Text"] = String(text); if(match !== undefined && match !== '') r["Match Type"] = String(match); if(expr !== undefined && expr !== '') r["Product Targeting Expression"] = String(expr); return r; }; let rowsToExport = []; let blockCount = 0; let successCount = 0; const targetCampaigns = activeCampaigns.filter(c => c.cName.toUpperCase().includes(spu.toUpperCase())); if (targetCampaigns.length === 0) { alert("未在全表中检测到包含此 SPU 名称的广告活动,请检查输入或原始表格!"); return; } targetCampaigns.forEach(camp => { const cId = String(camp.cId).trim(); const cName = String(camp.cName).trim(); exList.forEach(text => { const textLower = text.toLowerCase(); const checkKey = `CAMP_${cId}_${textLower}_exact`; if (dedupSet.has(checkKey)) { blockCount++; } else { rowsToExport.push(buildRow(cId, cName, 'Campaign Negative Keyword', text, 'Negative Exact', '')); dedupSet.add(checkKey); successCount++; } }); phList.forEach(text => { const textLower = text.toLowerCase(); const checkKey = `CAMP_${cId}_${textLower}_phrase`; if (dedupSet.has(checkKey)) { blockCount++; } else { rowsToExport.push(buildRow(cId, cName, 'Campaign Negative Keyword', text, 'Negative Phrase', '')); dedupSet.add(checkKey); successCount++; } }); asList.forEach(rawAsin => { const asinVal = rawAsin.replace(/asin=|"|'/gi, '').toUpperCase(); const expr = `asin="${asinVal}"`; const checkKey = `CAMP_${cId}_${expr.toLowerCase()}`; if (dedupSet.has(checkKey)) { blockCount++; } else { rowsToExport.push(buildRow(cId, cName, 'Campaign Negative Product Targeting', '', '', expr)); dedupSet.add(checkKey); successCount++; } }); }); if (rowsToExport.length === 0) { alert(`所有指令已被防撞库雷达拦截!(共拦截 ${blockCount} 条已存在的重复数据)`); return; } alert(`成功生成 ${successCount} 条全局否定指令!\n(已跨全表精准下发至 ${targetCampaigns.length} 个广告活动,自动防重拦截 ${blockCount} 条)`); const newWs = window.XLSX.utils.json_to_sheet(rowsToExport, { header: HEADERS }); const newWb = window.XLSX.utils.book_new(); window.XLSX.utils.book_append_sheet(newWb, newWs, 'Sponsored Products Campaigns'); const dateStr = new Date().toISOString().slice(0,10).replace(/-/g,""); window.XLSX.writeFile(newWb, `EngineB_GlobalNeg_${spu}_${dateStr}.xlsx`); }; return (
【铁血全域熔断版】解除 0 曝光限制,实现真正的 14 天到期无差别斩杀!底层实装神盾级黄金比例数据架构,强锁 ID 防止科学计数法崩溃,物理剿灭 Invalid Request,100% 顺滑通关亚马逊强校验接口。
{isProcessing ? progressMsg : "点击载入亚马逊 Bulk 文件 (.xlsx / .csv)"}
⚠️ 警告:如果您希望触发“时间超时熔断”,请务必去亚马逊重新下载完整的原版报表!不要上传错误报告,否则系统会由于找不到原始广告活动的创建时间而导致时间熔断失效。
{fileData.fileName}
右侧参数已就绪。点击下方按钮启动多轨算法清洗。
防御矩阵拦截 (实体停用)
活动熔断
{summary.tierCounts.pausedCampaign}
广告组熔断
{summary.tierCounts.pausedAdGroup}
投放停用 (含时间斩杀)
{summary.tierCounts.pausedKw}
SKU 停用 (含时间斩杀)
{summary.tierCounts.pausedSku}
阶梯竞价微调 (活跃实体)
降价止损
{summary.tierCounts.tier1}
放量提价
{summary.tierCounts.tier2}
逼近利润线
{summary.tierCounts.tier3}
长尾与破冰
{summary.tierCounts.tier4}
TOS 溢价联动
{summary.tierCounts.tosBoost}
| 归属活动 | 操作对象 | 动作 | 指标 | 结果 |
|---|---|---|---|---|
| {log.campaign} | {log.target} | {log.action} | {log.metrics} | {log.result} |
底层探针状态:
已拦截/隔离错误文件冗余行数:{summary.skipDetails.apiError || 0} 条。
无转化指标或非目标行跳过:{summary.skipDetails.notTargeting + summary.skipDetails.notEnabled + summary.skipDetails.spuExcluded + summary.skipDetails.nullBid + summary.skipDetails.targetMetHold + summary.skipDetails.noConditionMet}
防护引擎战报:
因父级架构停用/归档被拦截:{summary.skipDetails.parentNotActive}
活动级 0单花费 ($)
广告组 0单花费 ($)
0单存活上限 (天)
投放端 0单点击
投放端 0单花费($)
投放端 ACoS ≥
SKU端 0单点击
SKU端 0单花费($)
SKU端 ACoS ≥
天花板 ($)
地板价 ($)
提价幅度 (%)
降价幅度 (%)
顶部广告位提价(%)
稳健容忍区 (%)
0单点击超出
0单花费超 ($)
不再受组合与横杠限制!只要在下方输入或选择您的目标核心词,系统将自动扫描出名称中【包含】该词的所有存活广告活动,并强行注射否定指令。
setGlobalNegState({...globalNegState, spu: e.target.value.toUpperCase()})} placeholder="输入或选择目标 SPU (例如: D7A280)" className="w-full md:w-1/3 bg-white border border-gray-200 rounded-xl p-3 outline-none text-sm font-bold text-[#007aff] shadow-sm transition-all uppercase" />每行一个烂词
每行一个垃圾词根
粘贴自动净化格式