feat: add traffic intelligence toolkit
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
# Traffic Information & Tracking Link Generator
|
||||
|
||||
纯静态的 AgentStage 报告框架,无构建依赖。
|
||||
|
||||
## 页面
|
||||
|
||||
- `index.html`:工具入口
|
||||
- `channel-recommendation.html`:包名、品类、国家 → 渠道优先级建议
|
||||
- `link-generator.html`:自投平台 Click / Impression 链接生成
|
||||
|
||||
## 可维护数据
|
||||
|
||||
- `assets/data/channel-rules.js`:品类、国家、区域和渠道评分规则
|
||||
- `assets/data/link-templates.js`:媒体宏与链接参数模板
|
||||
|
||||
当前渠道评分是用于验证页面和解释结构的 v0.1 规则基线,并非真实绩效模型。下一阶段可接入历史消耗、转化、渠道 Active/Pause 状态及包名国家命中数据。
|
||||
|
||||
## 本地预览
|
||||
|
||||
静态页面使用相对路径,可直接打开;若浏览器限制本地资源,可在目录中运行一次性静态服务器:
|
||||
|
||||
```sh
|
||||
python3 -m http.server 8080
|
||||
```
|
||||
|
||||
然后访问 `http://localhost:8080/`。
|
||||
@@ -0,0 +1,121 @@
|
||||
(() => {
|
||||
const data = window.CHANNEL_RULES;
|
||||
if (!data) return;
|
||||
|
||||
const form = document.getElementById('recommendationForm');
|
||||
const category = document.getElementById('category');
|
||||
const country = document.getElementById('country');
|
||||
const packageName = document.getElementById('packageName');
|
||||
const message = document.getElementById('formMessage');
|
||||
const empty = document.getElementById('resultEmpty');
|
||||
const content = document.getElementById('resultContent');
|
||||
const list = document.getElementById('resultList');
|
||||
const summary = document.getElementById('querySummary');
|
||||
const resetBtn = document.getElementById('resetBtn');
|
||||
|
||||
data.categories.forEach((item) => category.add(new Option(item.label, item.value)));
|
||||
data.countries.forEach(([code, name]) => country.add(new Option(`${name} · ${code}`, code)));
|
||||
|
||||
const categoryLabel = (value) => data.categories.find((item) => item.value === value)?.label || value;
|
||||
const countryLabel = (value) => {
|
||||
const item = data.countries.find(([code]) => code === value);
|
||||
return item ? `${item[1]} · ${item[0]}` : value;
|
||||
};
|
||||
|
||||
function scoreChannel(channel, query) {
|
||||
let score = channel.base;
|
||||
const reasons = [`基础能力 ${channel.base} 分`];
|
||||
const region = data.regionMap[query.country];
|
||||
|
||||
if (channel.categories.includes(query.category)) {
|
||||
score += 35;
|
||||
reasons.push(`适配「${categoryLabel(query.category)}」品类`);
|
||||
} else if (channel.categories.includes('other')) {
|
||||
score += 10;
|
||||
reasons.push('支持通用品类测试');
|
||||
}
|
||||
|
||||
if (channel.countries.includes(query.country)) {
|
||||
score += 30;
|
||||
reasons.push(`明确覆盖 ${query.country}`);
|
||||
} else if (region && channel.regions.includes(region)) {
|
||||
score += 24;
|
||||
reasons.push(`覆盖 ${region} 区域`);
|
||||
} else if (channel.countries.includes('GLOBAL')) {
|
||||
score += 18;
|
||||
reasons.push('具备全球流量覆盖');
|
||||
}
|
||||
|
||||
if (query.os === 'ALL' || channel.os.includes(query.os)) {
|
||||
score += 10;
|
||||
reasons.push(query.os === 'ALL' ? '同时支持 Android / iOS' : `支持 ${query.os === 'AOS' ? 'Android' : 'iOS'}`);
|
||||
}
|
||||
|
||||
const lowerPkg = query.pkg.toLowerCase();
|
||||
const hint = channel.pkgHints.find((word) => lowerPkg.includes(word));
|
||||
if (hint) {
|
||||
score += 5;
|
||||
reasons.push(`包名特征命中「${hint}」`);
|
||||
}
|
||||
|
||||
return { ...channel, score: Math.min(score, 100), reasons };
|
||||
}
|
||||
|
||||
function priority(score, index) {
|
||||
if (index === 0 || score >= 80) return ['P1', '优先测试', 'priority-high'];
|
||||
if (score >= 65) return ['P2', '建议测试', 'priority-mid'];
|
||||
return ['P3', '补充候选', 'priority-low'];
|
||||
}
|
||||
|
||||
function render(query) {
|
||||
const results = data.channels.map((channel) => scoreChannel(channel, query)).sort((a, b) => b.score - a.score).slice(0, 6);
|
||||
summary.innerHTML = `<div><span>PACKAGE</span><strong>${escapeHtml(query.pkg)}</strong></div><div><span>CATEGORY</span><strong>${escapeHtml(categoryLabel(query.category))}</strong></div><div><span>GEO</span><strong>${escapeHtml(countryLabel(query.country))}</strong></div>`;
|
||||
list.innerHTML = results.map((item, index) => {
|
||||
const [level, label, className] = priority(item.score, index);
|
||||
return `<article class="result-card ${index === 0 ? 'top-result' : ''}">
|
||||
<div class="rank">${String(index + 1).padStart(2, '0')}</div>
|
||||
<div class="result-main"><div class="result-name"><h3>${escapeHtml(item.name)}</h3><span>${escapeHtml(item.type)}</span></div>
|
||||
<div class="reason-tags">${item.reasons.slice(1, 4).map((reason) => `<span>${escapeHtml(reason)}</span>`).join('')}</div>
|
||||
<p>${escapeHtml(item.strengths.join(' · '))}</p>
|
||||
</div>
|
||||
<div class="score-block"><div class="score-ring" style="--score:${item.score}"><strong>${item.score}</strong><small>/100</small></div><span class="priority ${className}">${level} · ${label}</span></div>
|
||||
</article>`;
|
||||
}).join('');
|
||||
empty.classList.add('hidden');
|
||||
content.classList.remove('hidden');
|
||||
resetBtn.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value).replace(/[&<>"']/g, (char) => ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''' })[char]);
|
||||
}
|
||||
|
||||
form.addEventListener('submit', (event) => {
|
||||
event.preventDefault();
|
||||
const pkg = packageName.value.trim();
|
||||
if (!pkg || !category.value || !country.value) {
|
||||
message.textContent = '请完整填写包名、产品品类和投放国家。';
|
||||
return;
|
||||
}
|
||||
message.textContent = '';
|
||||
const os = form.querySelector('input[name="os"]:checked').value;
|
||||
render({ pkg, category: category.value, country: country.value, os });
|
||||
});
|
||||
|
||||
document.getElementById('sampleBtn').addEventListener('click', () => {
|
||||
packageName.value = 'com.example.finance';
|
||||
category.value = 'finance';
|
||||
country.value = 'ID';
|
||||
form.querySelector('input[name="os"][value="AOS"]').checked = true;
|
||||
message.textContent = '';
|
||||
});
|
||||
|
||||
resetBtn.addEventListener('click', () => {
|
||||
form.reset();
|
||||
message.textContent = '';
|
||||
content.classList.add('hidden');
|
||||
resetBtn.classList.add('hidden');
|
||||
empty.classList.remove('hidden');
|
||||
packageName.focus();
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,12 @@
|
||||
(() => {
|
||||
document.querySelectorAll('[data-year]').forEach((node) => {
|
||||
node.textContent = new Date().getFullYear();
|
||||
});
|
||||
|
||||
document.querySelectorAll('a[href]').forEach((link) => {
|
||||
if (link.hostname && link.hostname !== window.location.hostname) {
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener noreferrer';
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,36 @@
|
||||
window.CHANNEL_RULES = {
|
||||
version: '0.1',
|
||||
categories: [
|
||||
{ value: 'finance', label: '金融 / 借贷 / 交易' },
|
||||
{ value: 'gaming', label: '游戏' },
|
||||
{ value: 'ecommerce', label: '电商 / 购物' },
|
||||
{ value: 'social', label: '社交 / 直播' },
|
||||
{ value: 'entertainment', label: '娱乐 / 短剧 / 视频' },
|
||||
{ value: 'utility', label: '工具 / 效率' },
|
||||
{ value: 'travel', label: '旅游 / 出行' },
|
||||
{ value: 'food', label: '餐饮 / 本地生活' },
|
||||
{ value: 'other', label: '其他' }
|
||||
],
|
||||
countries: [
|
||||
['US','美国'],['CA','加拿大'],['MX','墨西哥'],['BR','巴西'],['AR','阿根廷'],['CO','哥伦比亚'],
|
||||
['IN','印度'],['ID','印度尼西亚'],['PH','菲律宾'],['TH','泰国'],['VN','越南'],['MY','马来西亚'],['SG','新加坡'],
|
||||
['SA','沙特阿拉伯'],['AE','阿联酋'],['EG','埃及'],['TR','土耳其'],['ZA','南非'],['NG','尼日利亚'],
|
||||
['RU','俄罗斯'],['GB','英国'],['DE','德国'],['FR','法国'],['ES','西班牙'],['IT','意大利'],['AU','澳大利亚'],['JP','日本'],['KR','韩国']
|
||||
],
|
||||
regionMap: {
|
||||
US:'NA', CA:'NA', MX:'LATAM', BR:'LATAM', AR:'LATAM', CO:'LATAM',
|
||||
IN:'SASIA', ID:'SEA', PH:'SEA', TH:'SEA', VN:'SEA', MY:'SEA', SG:'SEA',
|
||||
SA:'MENA', AE:'MENA', EG:'MENA', TR:'MENA', ZA:'AFRICA', NG:'AFRICA',
|
||||
RU:'EEA', GB:'EU', DE:'EU', FR:'EU', ES:'EU', IT:'EU', AU:'OCEANIA', JP:'NEA', KR:'NEA'
|
||||
},
|
||||
channels: [
|
||||
{ name:'Mintegral', type:'自投媒体', base:20, categories:['gaming','utility','ecommerce','entertainment'], countries:['GLOBAL'], regions:['SEA','SASIA','LATAM','MENA','EU','NA'], os:['AOS','IOS'], strengths:['程序化流量覆盖广','App 场景适配度高'], pkgHints:['game','play','shop','video'] },
|
||||
{ name:'Bigo Ads', type:'自投媒体', base:18, categories:['social','entertainment','gaming','ecommerce'], countries:['ID','PH','TH','VN','MY','BR','MX','SA','AE'], regions:['SEA','LATAM','MENA'], os:['AOS','IOS'], strengths:['社交娱乐流量场景','东南亚与新兴市场覆盖'], pkgHints:['live','chat','video','social'] },
|
||||
{ name:'Kwai for Business', type:'自投媒体', base:18, categories:['ecommerce','entertainment','gaming','social','finance'], countries:['BR','MX','ID','PH','SA','AE'], regions:['LATAM','SEA','MENA'], os:['AOS','IOS'], strengths:['短视频内容场景','拉美区域覆盖'], pkgHints:['video','shop','live','game'] },
|
||||
{ name:'MediaGO', type:'自投媒体', base:17, categories:['finance','ecommerce','utility','travel'], countries:['GLOBAL'], regions:['NA','EU','EEA','SEA','LATAM'], os:['AOS','IOS'], strengths:['原生广告场景','金融与电商候选流量'], pkgHints:['finance','loan','credit','bank','shop'] },
|
||||
{ name:'NewsBreak', type:'自投媒体', base:16, categories:['finance','ecommerce','utility','travel','food'], countries:['US','CA'], regions:['NA'], os:['AOS','IOS'], strengths:['北美资讯内容场景','本地化受众触达'], pkgHints:['news','finance','food','local'] },
|
||||
{ name:'Adsterra', type:'自投媒体', base:15, categories:['gaming','finance','utility','entertainment'], countries:['GLOBAL'], regions:['SEA','SASIA','LATAM','MENA','AFRICA','EEA'], os:['AOS','IOS'], strengths:['多广告形式覆盖','新兴市场可扩量'], pkgHints:['bet','casino','loan','vpn','game'] },
|
||||
{ name:'SmartyAds', type:'自投媒体', base:14, categories:['gaming','ecommerce','utility','entertainment','other'], countries:['GLOBAL'], regions:['NA','EU','SEA','LATAM','MENA'], os:['AOS','IOS'], strengths:['程序化流量补充','支持多种媒体宏'], pkgHints:['app','mobile','game','shop'] },
|
||||
{ name:'Runto', type:'代理渠道', base:13, categories:['finance','gaming','ecommerce','social','utility','travel','food','other'], countries:['GLOBAL'], regions:['SEA','SASIA','LATAM','MENA','AFRICA','EEA','EU'], os:['AOS','IOS'], strengths:['历史包名与国家池较丰富','适合作为代理渠道候选'], pkgHints:[] }
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
window.LINK_TEMPLATES = [
|
||||
{
|
||||
name:'Mintegral', platformCode:'M_API_Self_Mintegral', status:'已配置',
|
||||
macros:[['{click_id}','媒体点击 ID'],['{offer_name}','Offer 名称'],['{ad_id}','素材 ID'],['{ad_name}','素材名称'],['{affiliate_id}','子渠道 ID'],['{idfa}','iOS 广告标识'],['{gaid}','Google 广告标识'],['{device_os}','设备系统'],['{country_code}','ISO 国家码'],['{ip}','设备 IP'],['{ua}','User Agent']],
|
||||
links:{
|
||||
click:{endpoint:'https://trident.bigpinggo.com/click', mappings:{clickid:'{click_id}',offer_name:'{offer_name}',creative_id:'{ad_id}',creative_name:'{ad_name}',affiliate_id:'{affiliate_id}',idfa:'{idfa}',gaid:'{gaid}',uagent:'{ua}',os_platform:'{device_os}',os_version:'{os_version}',device_model:'{device_model}',ip:'{ip}',country_code:'{country_code}',ext:'{uuid}',rta_token:'{rta_token}'}},
|
||||
imp:{endpoint:'https://typhoon.bigpinggo.com/imp', mappings:{clickid:'{impression_id}',offer_name:'{offer_name}',creative_id:'{ad_id}',creative_name:'{ad_name}',affiliate_id:'{affiliate_id}',idfa:'{idfa}',gaid:'{gaid}',uagent:'{ua}',os_platform:'{device_os}',device_model:'{device_model}',ip:'{ip}',country_code:'{country_code}',ext:'{uuid}'}}
|
||||
}
|
||||
},
|
||||
{
|
||||
name:'Bigo', platformCode:'M_API_Self_Bigo', status:'已配置',
|
||||
macros:[['__SID__','广告请求 / 点击 ID'],['__APPID__','推广 App ID'],['__CAMPAIGN_ID__','广告系列 ID'],['__CAMPAIGN_NAME__','广告系列名称'],['__AD_ID__','广告 ID'],['__AD_NAME__','广告名称'],['__IDFA__','iOS 广告标识'],['__GAID__','Google 广告标识'],['__IP__','设备 IP'],['__OS__','操作系统'],['__PUBLISHER_ID__','子渠道 ID']],
|
||||
links:{
|
||||
click:{endpoint:'https://trident.bigpinggo.com/click', mappings:{clickid:'__SID__',offer_name:'__CAMPAIGN_NAME__',creative_id:'__AD_ID__',creative_name:'__AD_NAME__',affiliate_id:'__PUBLISHER_ID__',idfa:'__IDFA__',gaid:'__GAID__',os_platform:'__OS__',device_model:'__DEVICE_MODEL__',ip:'__IP__',country_code:'',ext:'__AD_EXT__',rta_token:'{rta_token}'}},
|
||||
imp:{endpoint:'https://typhoon.bigpinggo.com/imp', mappings:{clickid:'imp__SID__',offer_name:'__CAMPAIGN_NAME__',creative_id:'__AD_ID__',creative_name:'__AD_NAME__',affiliate_id:'__PUBLISHER_ID__',idfa:'__IDFA__',gaid:'__GAID__',os_platform:'__OS__',device_model:'__DEVICE_MODEL__',ip:'__IP__',country_code:'',ext:'__AD_EXT__'}}
|
||||
}
|
||||
},
|
||||
{
|
||||
name:'Kwai', platformCode:'M_API_Self_Kwai_NB', status:'已配置',
|
||||
macros:[['__CALLBACK__','Click ID'],['__GAID1__','Google 广告标识'],['__IDFA__','iOS 广告标识'],['__IP__','设备 IP'],['__UA__','设备 User Agent'],['__CMPNID__','广告计划 ID'],['__ADSETID__','广告组 ID'],['__ADID__','广告 ID']],
|
||||
links:{
|
||||
click:{endpoint:'https://trident.bigpinggo.com/click', mappings:{clickid:'__CALLBACK__',offer_name:'__CMPNID__',creative_id:'__ADSETID__',creative_name:'__ADID__',affiliate_id:'__ADSETID__',idfa:'__IDFA__',gaid:'__GAID1__',uagent:'__UA__',ip:'__IP__',country_code:'',ext:'',rta_token:'{rta_token}'}},
|
||||
imp:{endpoint:'https://typhoon.bigpinggo.com/imp', mappings:{clickid:'__CALLBACK__',offer_name:'__CMPNID__',creative_id:'__ADSETID__',creative_name:'__ADID__',affiliate_id:'__CMPNID__',idfa:'__IDFA__',gaid:'__GAID1__',uagent:'__UA__',ip:'__IP__',country_code:'',ext:''}}
|
||||
}
|
||||
},
|
||||
{
|
||||
name:'MediaGO', platformCode:'M_API_Self_Mediago', status:'已配置',
|
||||
macros:[['{TRACKING_ID}','完整用户旅程 Tracking ID'],['{CLICK_ID}','点击 ID'],['{ADVERTISING_ID}','通用广告标识'],['{ANDROID_ID}','Android 设备 ID'],['{AD_SET_ID}','广告组 / Affiliate ID']],
|
||||
links:{
|
||||
click:{endpoint:'https://trident.bigpinggo.com/click', mappings:{clickid:'{TRACKING_ID}',offer_name:'',creative_id:'',creative_name:'{AD_SET_ID}',affiliate_id:'{AD_SET_ID}',idfa:'',gaid:'{ADVERTISING_ID}',android_id:'{ANDROID_ID}',country_code:'',ext:'',rta_token:''}},
|
||||
imp:{endpoint:'https://typhoon.bigpinggo.com/imp', mappings:{clickid:'{TRACKING_ID}',affiliate_id:'{AD_SET_ID}',gaid:'{ADVERTISING_ID}',android_id:'{ANDROID_ID}',country_code:'',ext:''}}
|
||||
}
|
||||
},
|
||||
{
|
||||
name:'NewsBreak', platformCode:'A_API_SPDragon', status:'待业务复核',
|
||||
macros:[['__CALLBACK_PARAM__','平台唯一回调参数'],['__CAMPAIGN_ID__','Campaign ID'],['__CAMPAIGN_NAME__','Campaign 名称'],['__FLIGHT_ID__','Ad Set ID'],['__FLIGHT_NAME__','Ad Set 名称'],['__CREATIVE_ID__','Creative ID'],['__CREATIVE_NAME__','Creative 名称'],['__ADID__','GAID'],['__IDFA__','IDFA'],['__OS__','操作系统']],
|
||||
links:{
|
||||
click:{endpoint:'https://trident.bigpinggo.com/l/click', mappings:{clickid:'__CALLBACK_PARAM__',offer_name:'__CAMPAIGN_NAME__',creative_id:'__CREATIVE_ID__',creative_name:'__CREATIVE_NAME__',affiliate_id:'__FLIGHT_ID_____FLIGHT_NAME__',idfa:'__IDFA__',gaid:'__ADID__',os_platform:'__OS__',country_code:'',ext:'',rta_token:'{rta_token}'}},
|
||||
imp:{endpoint:'https://typhoon.bigpinggo.com/imp', mappings:{clickid:'__CALLBACK_PARAM__',offer_name:'__CAMPAIGN_NAME__',creative_id:'__CREATIVE_ID__',creative_name:'__CREATIVE_NAME__',affiliate_id:'__FLIGHT_ID_____FLIGHT_NAME__',idfa:'__IDFA__',gaid:'__ADID__',os_platform:'__OS__',country_code:'',ext:''}}
|
||||
}
|
||||
},
|
||||
{
|
||||
name:'Adsterra', platformCode:'P_API_Adsterra', status:'已配置',
|
||||
macros:[['##SUB_ID_SHORT(action)##','Click ID'],['##PLACEMENT_ID##','广告位 / Affiliate ID'],['##BANNER_ID##','素材 ID'],['##USERAGENT##','User Agent'],['##USER_OS##','设备系统'],['##USER_OS_VER##','系统版本'],['##REMOTE_IP##','设备 IP'],['##COUNTRY_CODE##','国家码']],
|
||||
links:{click:{endpoint:'https://trident.bigpinggo.com/l/click', mappings:{clickid:'##SUB_ID_SHORT(action)##',offer_name:'',creative_id:'##BANNER_ID##',creative_name:'',affiliate_id:'##PLACEMENT_ID##',uagent:'##USERAGENT##',os_platform:'##USER_OS##',os_version:'##USER_OS_VER##',ip:'##REMOTE_IP##',country_code:'##COUNTRY_CODE##',ext:''}}}
|
||||
},
|
||||
{
|
||||
name:'SmartyAds', platformCode:'M_API_Self_Smartyads', status:'已配置',
|
||||
macros:[['[CLICK_ID]','Click ID'],['[TIMESTAMP]','UNIX 时间戳'],['[CAMPAIGN_ID]','Campaign ID'],['[CREATIVE_ID]','素材 ID'],['[CREATIVE_NAME]','素材名称'],['[APP_BUNDLE]','媒体 App Bundle'],['[IFA]','设备广告标识'],['[USER_AGENT]','User Agent'],['[OS]','操作系统'],['[IP]','设备 IP'],['[COUNTRY]','国家码']],
|
||||
links:{click:{endpoint:'https://trident.bigpinggo.com/l/click', mappings:{clickid:'[CLICK_ID]_[TIMESTAMP]',offer_name:'[CAMPAIGN_ID]',creative_id:'[CREATIVE_ID]',creative_name:'[CREATIVE_NAME]',affiliate_id:'[APP_BUNDLE]',gaid:'[IFA]',uagent:'[USER_AGENT]',os_platform:'[OS]',os_version:'[OSV]',device_model:'[DEVICE_MAKE]',ip:'[IP]',country_code:'[COUNTRY]',ext:'[CLICK_ID]',rta_token:''}}}
|
||||
},
|
||||
{
|
||||
name:'Traffic Star', platformCode:'A_API_SPDragon', status:'待业务复核',
|
||||
macros:[['{click_id}','S2S Click ID'],['{campaign_id}','Campaign ID'],['{creative_id}','素材 ID'],['{site_id}','站点 ID'],['{os}','操作系统'],['{device_type_id}','设备类型 ID'],['{ip}','设备 IP'],['{geo}','两位国家码'],['{format}','素材格式']],
|
||||
links:{click:{endpoint:'https://trident.bigpinggo.com/l/click', mappings:{clickid:'{click_id}',offer_name:'{campaign_id}',creative_id:'{creative_id}',creative_name:'{format}',affiliate_id:'{site_id}',os_platform:'{os}',os_version:'{os_id}',device_model:'{device_type_id}',ip:'{ip}',country_code:'{geo}',ext:'',rta_token:''}}}
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,142 @@
|
||||
(() => {
|
||||
const templates = window.LINK_TEMPLATES;
|
||||
if (!templates?.length) return;
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const platformSelect = $('platformSelect');
|
||||
const linkTypeSelect = $('linkTypeSelect');
|
||||
const mappingBody = $('mappingTableBody');
|
||||
const macroBody = $('macroTableBody');
|
||||
const output = $('urlOutput');
|
||||
const businessIds = ['advid','campid','pkg','platformCode','sanCampid','adsetId','countryCode'];
|
||||
let state = { platformIndex:0, type:'click', mappings:{} };
|
||||
|
||||
$('templateCount').textContent = `${templates.length} 个平台模板`;
|
||||
templates.forEach((template, index) => platformSelect.add(new Option(template.name, index)));
|
||||
|
||||
const activeTemplate = () => templates[state.platformIndex];
|
||||
const activeLink = () => activeTemplate().links[state.type];
|
||||
|
||||
function loadPlatform() {
|
||||
const template = activeTemplate();
|
||||
linkTypeSelect.innerHTML = '';
|
||||
Object.keys(template.links).forEach((type) => linkTypeSelect.add(new Option(type === 'click' ? 'Click Link' : 'Impression Link', type)));
|
||||
state.type = Object.keys(template.links)[0];
|
||||
$('platformCode').value = template.platformCode;
|
||||
$('platformStatus').innerHTML = `<span class="template-status ${template.status.includes('待') ? 'review' : ''}">${template.status}</span>`;
|
||||
renderMacros();
|
||||
loadLink();
|
||||
}
|
||||
|
||||
function loadLink() {
|
||||
state.mappings = { ...activeLink().mappings };
|
||||
renderMappings();
|
||||
buildUrl();
|
||||
}
|
||||
|
||||
function renderMacros() {
|
||||
const query = $('macroSearch').value.trim().toLowerCase();
|
||||
const rows = activeTemplate().macros.filter(([macro, desc]) => `${macro} ${desc}`.toLowerCase().includes(query));
|
||||
macroBody.innerHTML = rows.length
|
||||
? rows.map(([macro, desc]) => `<tr><td><code>${escapeHtml(macro)}</code></td><td>${escapeHtml(desc)}</td></tr>`).join('')
|
||||
: '<tr><td colspan="2" class="table-empty">没有匹配的媒体宏</td></tr>';
|
||||
}
|
||||
|
||||
function renderMappings() {
|
||||
mappingBody.innerHTML = Object.entries(state.mappings).map(([key, value]) => `<tr><td><code>${escapeHtml(key)}</code></td><td><input data-key="${escapeHtml(key)}" value="${escapeHtml(value)}" aria-label="${escapeHtml(key)} value"></td></tr>`).join('');
|
||||
mappingBody.querySelectorAll('input').forEach((input) => input.addEventListener('input', () => {
|
||||
state.mappings[input.dataset.key] = input.value;
|
||||
buildUrl();
|
||||
}));
|
||||
}
|
||||
|
||||
function buildUrl() {
|
||||
const link = activeLink();
|
||||
const campid = $('campid').value.trim();
|
||||
const pkg = $('pkg').value.trim();
|
||||
const type = state.type;
|
||||
const params = [];
|
||||
const addBusiness = (key, value) => params.push(`${key}=${encodeURIComponent(value)}`);
|
||||
const addRaw = (key, value) => params.push(`${key}=${value}`);
|
||||
|
||||
if (type === 'click') {
|
||||
addBusiness('advid', $('advid').value.trim());
|
||||
addBusiness('campid', campid);
|
||||
addBusiness('platform', $('platformCode').value.trim());
|
||||
addBusiness('pkg', pkg);
|
||||
} else {
|
||||
addBusiness('campid', campid);
|
||||
addBusiness('pkg', pkg);
|
||||
}
|
||||
|
||||
Object.entries(state.mappings).forEach(([key, value]) => {
|
||||
const finalValue = key === 'country_code' && $('countryCode').value.trim()
|
||||
? $('countryCode').value.trim().toUpperCase()
|
||||
: value;
|
||||
addRaw(key, finalValue);
|
||||
});
|
||||
|
||||
if (type === 'click') {
|
||||
if ($('sanCampid').value.trim()) addBusiness('san_campid', $('sanCampid').value.trim());
|
||||
if ($('adsetId').value.trim()) addBusiness('adset_id', $('adsetId').value.trim());
|
||||
addRaw('c_cto', '0');
|
||||
} else {
|
||||
addRaw('at_tag', '2');
|
||||
}
|
||||
|
||||
const url = `${link.endpoint}?${params.join('&')}`;
|
||||
output.textContent = url;
|
||||
$('urlLength').textContent = `${url.length} chars`;
|
||||
|
||||
const missing = [];
|
||||
if (!campid) missing.push('Campaign ID');
|
||||
if (!pkg) missing.push('Package Name');
|
||||
$('generatorMessage').textContent = missing.length ? `待填写:${missing.join('、')}` : '';
|
||||
}
|
||||
|
||||
function resetGenerator() {
|
||||
$('advid').value = '30';
|
||||
$('campid').value = '';
|
||||
$('pkg').value = '';
|
||||
$('platformCode').value = activeTemplate().platformCode;
|
||||
$('sanCampid').value = '';
|
||||
$('adsetId').value = '-1';
|
||||
$('countryCode').value = '';
|
||||
loadLink();
|
||||
}
|
||||
|
||||
async function copyUrl() {
|
||||
if (!$('campid').value.trim() || !$('pkg').value.trim()) {
|
||||
$('copyStatus').textContent = '请先补全必填参数';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(output.textContent);
|
||||
} catch (_) {
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(output);
|
||||
const selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
document.execCommand('copy');
|
||||
selection.removeAllRanges();
|
||||
}
|
||||
$('copyStatus').textContent = '已复制';
|
||||
const toast = $('toast');
|
||||
toast.textContent = '最终链接已复制到剪贴板';
|
||||
toast.classList.add('show');
|
||||
setTimeout(() => toast.classList.remove('show'), 1800);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value).replace(/[&<>"']/g, (char) => ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''' })[char]);
|
||||
}
|
||||
|
||||
platformSelect.addEventListener('change', () => { state.platformIndex = Number(platformSelect.value); loadPlatform(); });
|
||||
linkTypeSelect.addEventListener('change', () => { state.type = linkTypeSelect.value; loadLink(); });
|
||||
$('macroSearch').addEventListener('input', renderMacros);
|
||||
businessIds.forEach((id) => $(id).addEventListener('input', buildUrl));
|
||||
$('resetGenerator').addEventListener('click', resetGenerator);
|
||||
$('copyLink').addEventListener('click', copyUrl);
|
||||
loadPlatform();
|
||||
})();
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,74 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="按包名、品类和国家生成渠道投放建议">
|
||||
<title>渠道投放建议 · Traffic Intelligence</title>
|
||||
<link rel="stylesheet" href="assets/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header inner-header">
|
||||
<a class="brand" href="index.html"><span class="brand-mark" aria-hidden="true"><i></i><i></i><i></i></span><span><strong>Traffic Intelligence</strong><small>投放决策工作台</small></span></a>
|
||||
<nav class="main-nav" aria-label="主导航"><a class="active" href="channel-recommendation.html">渠道建议</a><a href="link-generator.html">链接生成器</a></nav>
|
||||
<a class="home-link" href="index.html">返回首页</a>
|
||||
</header>
|
||||
|
||||
<main class="shell page-main">
|
||||
<section class="page-heading">
|
||||
<div><p class="eyebrow"><span></span> CHANNEL ADVISOR / 01</p><h1>渠道投放建议</h1><p>通过产品信息快速筛选候选渠道,并展示每个排序结果的匹配依据。</p></div>
|
||||
<div class="data-badge"><span class="pulse-dot"></span><p><strong>规则基线 v0.1</strong><small>正式绩效数据待接入</small></p></div>
|
||||
</section>
|
||||
|
||||
<section class="workspace recommendation-layout">
|
||||
<aside class="panel form-panel">
|
||||
<div class="panel-title"><span>INPUT</span><h2>填写产品信息</h2><p>带 * 的字段为必填项</p></div>
|
||||
<form id="recommendationForm" novalidate>
|
||||
<label class="field-label" for="packageName">应用包名 <b>*</b></label>
|
||||
<div class="input-wrap"><span class="input-prefix">PKG</span><input id="packageName" name="packageName" autocomplete="off" placeholder="如 com.example.app" required></div>
|
||||
<p class="field-help">支持 Android Package Name 或 iOS Bundle ID</p>
|
||||
|
||||
<label class="field-label" for="category">产品品类 <b>*</b></label>
|
||||
<select id="category" name="category" required><option value="">请选择产品品类</option></select>
|
||||
|
||||
<label class="field-label" for="country">投放国家 / 地区 <b>*</b></label>
|
||||
<select id="country" name="country" required><option value="">请选择目标国家</option></select>
|
||||
|
||||
<label class="field-label" for="os">操作系统</label>
|
||||
<div class="segmented" id="osOptions">
|
||||
<label><input type="radio" name="os" value="ALL" checked><span>不限</span></label>
|
||||
<label><input type="radio" name="os" value="AOS"><span>Android</span></label>
|
||||
<label><input type="radio" name="os" value="IOS"><span>iOS</span></label>
|
||||
</div>
|
||||
|
||||
<div class="form-message" id="formMessage" role="alert"></div>
|
||||
<button class="primary-btn full-btn" type="submit"><span>生成渠道建议</span><b>→</b></button>
|
||||
<button class="text-btn" type="button" id="sampleBtn">填入示例数据</button>
|
||||
</form>
|
||||
<div class="form-footnote"><b>i</b><p>当前结果用于验证页面与评分结构,不代表实际投放效果承诺。下一阶段将接入历史消耗、转化及渠道状态。</p></div>
|
||||
</aside>
|
||||
|
||||
<section class="panel result-panel" aria-live="polite">
|
||||
<div class="result-head">
|
||||
<div><span class="section-index">OUTPUT</span><h2>优先渠道</h2></div>
|
||||
<button class="outline-btn hidden" id="resetBtn" type="button">重新填写</button>
|
||||
</div>
|
||||
<div id="resultEmpty" class="result-empty">
|
||||
<div class="empty-graphic"><span>PKG</span><i>+</i><span>CAT</span><i>+</i><span>GEO</span></div>
|
||||
<h3>等待输入产品信息</h3><p>完成左侧三个必填字段后,这里会显示候选渠道、优先级和详细匹配原因。</p>
|
||||
</div>
|
||||
<div id="resultContent" class="hidden">
|
||||
<div class="query-summary" id="querySummary"></div>
|
||||
<div class="result-list" id="resultList"></div>
|
||||
<div class="method-note"><strong>本版排序逻辑</strong><span>品类匹配 35%</span><span>国家覆盖 30%</span><span>基础能力 20%</span><span>系统与包名特征 15%</span></div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer shell"><span>Traffic Intelligence Hub</span><span>© <b data-year></b> Media Operations</span></footer>
|
||||
<script src="assets/data/channel-rules.js"></script>
|
||||
<script src="assets/common.js"></script>
|
||||
<script src="assets/channel.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="投放决策与自投链接生成工具">
|
||||
<title>Traffic Intelligence Hub</title>
|
||||
<link rel="stylesheet" href="assets/styles.css">
|
||||
</head>
|
||||
<body class="landing-page">
|
||||
<header class="site-header">
|
||||
<a class="brand" href="index.html" aria-label="Traffic Intelligence Hub 首页">
|
||||
<span class="brand-mark" aria-hidden="true"><i></i><i></i><i></i></span>
|
||||
<span><strong>Traffic Intelligence</strong><small>投放决策工作台</small></span>
|
||||
</a>
|
||||
<span class="version-chip">FRAMEWORK · V0.1</span>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="hero shell">
|
||||
<div class="hero-copy">
|
||||
<p class="eyebrow"><span></span> MEDIA OPERATIONS TOOLKIT</p>
|
||||
<h1>从渠道判断到链接生成,<br><em>让投放准备更简单。</em></h1>
|
||||
<p class="hero-lead">一个面向投放团队的轻量工作台。先快速定位候选渠道,再按媒体宏规则生成可用的 Tracking Link。</p>
|
||||
<div class="hero-meta">
|
||||
<span><b>01</b> 渠道建议</span><i></i><span><b>02</b> 链接生成</span><i></i><span><b>03</b> 数据规则持续迭代</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-visual" aria-hidden="true">
|
||||
<div class="orbit orbit-one"></div><div class="orbit orbit-two"></div>
|
||||
<div class="visual-core"><span>ROUTE</span><strong>→</strong><span>TRACK</span></div>
|
||||
<span class="visual-node node-a">PKG</span><span class="visual-node node-b">GEO</span><span class="visual-node node-c">MACRO</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tool-grid shell" aria-label="工具入口">
|
||||
<a class="tool-card card-blue" href="channel-recommendation.html">
|
||||
<div class="tool-number">01</div>
|
||||
<div class="tool-icon" aria-hidden="true">↗</div>
|
||||
<p class="tool-kicker">CHANNEL ADVISOR</p>
|
||||
<h2>渠道投放建议</h2>
|
||||
<p>输入包名、产品品类与投放国家,获取按优先级排列的渠道候选及匹配理由。</p>
|
||||
<ul><li>可解释的评分结构</li><li>品类与 GEO 双重匹配</li><li>预留真实绩效数据接口</li></ul>
|
||||
<span class="tool-cta">开始匹配 <b>→</b></span>
|
||||
</a>
|
||||
<a class="tool-card card-lime" href="link-generator.html">
|
||||
<div class="tool-number">02</div>
|
||||
<div class="tool-icon" aria-hidden="true">⌁</div>
|
||||
<p class="tool-kicker">TRACKING BUILDER</p>
|
||||
<h2>自投链接生成器</h2>
|
||||
<p>选择自投媒体,填写业务参数,按平台专属宏规则实时生成 Click / Impression 链接。</p>
|
||||
<ul><li>平台宏模板集中管理</li><li>业务参数实时替换</li><li>一键复制最终链接</li></ul>
|
||||
<span class="tool-cta">生成链接 <b>→</b></span>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<section class="status-strip shell">
|
||||
<div><span class="pulse-dot"></span><p><strong>当前阶段</strong><small>交互框架已就绪,等待接入正式规则与数据</small></p></div>
|
||||
<p class="status-note">静态页面 · 无需构建 · 支持 AgentStage 直接发布</p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer shell"><span>Traffic Intelligence Hub</span><span>© <b data-year></b> Media Operations</span></footer>
|
||||
<script src="assets/common.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,64 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="自投平台 Click 和 Impression Tracking Link 生成器">
|
||||
<title>自投链接生成器 · Traffic Intelligence</title>
|
||||
<link rel="stylesheet" href="assets/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header inner-header">
|
||||
<a class="brand" href="index.html"><span class="brand-mark" aria-hidden="true"><i></i><i></i><i></i></span><span><strong>Traffic Intelligence</strong><small>投放决策工作台</small></span></a>
|
||||
<nav class="main-nav" aria-label="主导航"><a href="channel-recommendation.html">渠道建议</a><a class="active" href="link-generator.html">链接生成器</a></nav>
|
||||
<a class="home-link" href="index.html">返回首页</a>
|
||||
</header>
|
||||
|
||||
<main class="shell page-main">
|
||||
<section class="page-heading">
|
||||
<div><p class="eyebrow"><span></span> TRACKING BUILDER / 02</p><h1>自投平台链接生成器</h1><p>选择媒体模板,填写订单参数,并实时检查平台宏映射与最终链接。</p></div>
|
||||
<div class="data-badge"><span class="pulse-dot"></span><p><strong id="templateCount">— 个平台模板</strong><small>Click / Impression</small></p></div>
|
||||
</section>
|
||||
|
||||
<section class="generator-stack">
|
||||
<section class="panel generator-config">
|
||||
<div class="config-topline"><span class="section-index">STEP 01</span><h2>选择模板与业务参数</h2><button class="outline-btn" id="resetGenerator" type="button">恢复模板默认值</button></div>
|
||||
<div class="config-grid">
|
||||
<label><span>自投平台 *</span><select id="platformSelect"></select></label>
|
||||
<label><span>链接类型 *</span><select id="linkTypeSelect"></select></label>
|
||||
<label><span>Advertiser ID</span><input id="advid" value="30" inputmode="numeric"></label>
|
||||
<label><span>Campaign ID *</span><input id="campid" placeholder="请输入 Camp ID" inputmode="numeric"></label>
|
||||
<label><span>Package Name *</span><input id="pkg" placeholder="如 com.example.app"></label>
|
||||
<label><span>Platform Code</span><input id="platformCode"></label>
|
||||
<label><span>SAN Campaign ID</span><input id="sanCampid" placeholder="可选"></label>
|
||||
<label><span>Ad Set ID</span><input id="adsetId" value="-1"></label>
|
||||
<label><span>固定国家码</span><input id="countryCode" maxlength="3" placeholder="留空则使用媒体宏"></label>
|
||||
</div>
|
||||
<div class="validation-line" id="generatorMessage" role="alert"></div>
|
||||
</section>
|
||||
|
||||
<section class="generator-columns">
|
||||
<section class="panel macro-panel">
|
||||
<div class="panel-title compact"><span>STEP 02</span><h2>媒体宏参考</h2><p id="platformStatus"></p></div>
|
||||
<div class="macro-search"><input id="macroSearch" placeholder="搜索宏或说明…" aria-label="搜索媒体宏"></div>
|
||||
<div class="macro-table-wrap"><table class="macro-table"><thead><tr><th>媒体宏</th><th>说明</th></tr></thead><tbody id="macroTableBody"></tbody></table></div>
|
||||
</section>
|
||||
|
||||
<section class="panel output-panel">
|
||||
<div class="config-topline output-title"><span class="section-index">STEP 03</span><h2>参数映射与输出</h2><span class="live-chip"><i></i> LIVE</span></div>
|
||||
<div class="mapping-table-wrap"><table class="mapping-table"><thead><tr><th>参数 Key</th><th>媒体 Value / Macro</th></tr></thead><tbody id="mappingTableBody"></tbody></table></div>
|
||||
<div class="output-label"><span>最终投放链接</span><small id="urlLength">0 chars</small></div>
|
||||
<pre class="url-output" id="urlOutput"></pre>
|
||||
<div class="output-actions"><button class="primary-btn" id="copyLink" type="button"><span>复制最终链接</span><b>⧉</b></button><span id="copyStatus" class="copy-status" role="status"></span></div>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div class="toast" id="toast" role="status"></div>
|
||||
<footer class="site-footer shell"><span>Traffic Intelligence Hub</span><span>© <b data-year></b> Media Operations</span></footer>
|
||||
<script src="assets/data/link-templates.js"></script>
|
||||
<script src="assets/common.js"></script>
|
||||
<script src="assets/link-generator.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user