feat: add traffic intelligence toolkit
This commit is contained in:
@@ -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
Reference in New Issue
Block a user