feat: convert pasted Mintegral click links

This commit is contained in:
王源
2026-10-19 20:15:39 +08:00
parent f34e0e085a
commit 969bab77a7
7 changed files with 258 additions and 170 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
- `index.html`:工具入口 - `index.html`:工具入口
- `channel-recommendation.html`:包名、品类、国家 → 渠道优先级建议 - `channel-recommendation.html`:包名、品类、国家 → 渠道优先级建议
- `link-generator.html`:自投平台 Click / Impression 链接生成 - `link-generator.html`:粘贴原始 Mintegral Click Link 并转换为已确认的平台宏
## 可维护数据 ## 可维护数据
+77 -49
View File
@@ -1,57 +1,85 @@
window.LINK_TEMPLATES = [ window.LINK_TEMPLATES = [
{ {
name:'Mintegral', platformCode:'M_API_Self_Mintegral', status:'已配置', name: 'Mintegral',
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']], platformCode: 'M_API_Self_Mintegral',
status: 'Click 已确认',
macros: [
['{ad_id}', 'Creative ID'],
['{ad_name}', 'Creative name'],
['{ad_type}', 'Ad type'],
['{adv_campaign_id}', 'Campaign unique number ID'],
['{adv_offer_id}', 'Offer unique number ID'],
['{affiliate_id}', 'Sub channel ID on MTG platform'],
['{amazon_id}', 'Amazon device ID'],
['{android_id_md5_low}', 'Android ID MD5 lowercase'],
['{android_id_md5}', 'Android ID MD5 (32-bit lowercase)'],
['{android_id}', 'Android ID for attribution'],
['{billing_type}', 'Billing type'],
['{click_id}', 'Click ID defined by MTG'],
['{cn_event_callback}', 'CN-region event callback URL'],
['{cn_install_callback}', 'CN-region install callback URL'],
['{country_code}', 'ISO 3166-1 alpha-2 country code'],
['{creative_group}', 'Creative group'],
['{creative_id}', 'Creative ID'],
['{creative_name}', 'Creative name'],
['{ctgv_usd}', 'Target goal value USD'],
['{currency}', 'Currency type'],
['{device_brand}', 'Device brand'],
['{device_id}', 'IDFA for iOS or GAID for Android'],
['{device_model}', 'Device model'],
['{device_os}', 'Device OS: iOS or Android'],
['{encode}', 'AppsFlyer encryption parameter'],
['{event_callback}', 'Event callback URL'],
['{event_callback_url}', 'Event callback URL for web'],
['{gaid_md5}', 'GAID MD5 (32-bit lowercase)'],
['{gaid}', 'Google advertising ID'],
['{idfa_md5}', 'IDFA MD5 (32-bit lowercase)'],
['{idfa}', 'iOS identifier for advertising'],
['{idfv}', 'IDFV'],
['{imei_md5}', 'IMEI MD5 (32-bit lowercase)'],
['{imei}', 'IMEI for Android attribution'],
['{impression_id}', 'Impression ID defined by MTG'],
['{install_callback}', 'Install callback URL'],
['{install_callback_url}', 'Install callback URL for web'],
['{ip}', 'IP address for fingerprint attribution'],
['{mac_md5}', 'MAC MD5 (32-bit lowercase)'],
['{mac}', 'Media access control address'],
['{microtime}', 'Microtime'],
['{oaid_md5}', 'OAID MD5'],
['{oaid}', 'OAID'],
['{offer_name}', 'Offer name'],
['{os_platform}', 'OS platform'],
['{os_version}', 'OS version'],
['{package_name}', 'Package name'],
['{price}', 'Price'],
['{timestamp}', 'Timestamp'],
['{ua}', 'User agent'],
['{uuid}', 'Promotion ID defined by MTG']
],
links: { 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}'}}, click: {
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}'}} mappings: {
clickid: '{click_id}',
offer_name: '{offer_name}',
creative_id: '{ad_id}',
creative_name: '{ad_name}',
affiliate_id: '{affiliate_id}',
idfa: '{idfa}',
gaid: '{gaid}',
imei: '{imei}',
android_id: '{android_id}',
mac: '{mac}',
oaid: '{oaid}',
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}'
} }
},
{
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:''}}}
} }
]; ];
+59
View File
@@ -0,0 +1,59 @@
((root) => {
function decodeKey(value) {
try { return decodeURIComponent(value.replace(/\+/g, ' ')); }
catch (_) { return value; }
}
function detectLinkType(link) {
const pathname = new URL(link).pathname.toLowerCase();
return /(?:^|\/)imp(?:\/|$)/.test(pathname) ? 'imp' : 'click';
}
function convertTrackingLink(originalLink, template) {
const original = originalLink.trim();
const parsed = new URL(original);
if (!['http:', 'https:'].includes(parsed.protocol)) throw new Error('仅支持 HTTP 或 HTTPS 链接');
if (!parsed.search) throw new Error('链接中没有可转换的查询参数');
const type = detectLinkType(original);
const linkTemplate = template.links[type];
if (!linkTemplate) {
const typeLabel = type === 'imp' ? 'Impression' : 'Click';
throw new Error(`${template.name} 暂未配置 ${typeLabel} 宏模板`);
}
const hashIndex = original.indexOf('#');
const hash = hashIndex >= 0 ? original.slice(hashIndex) : '';
const withoutHash = hashIndex >= 0 ? original.slice(0, hashIndex) : original;
const queryIndex = withoutHash.indexOf('?');
const base = withoutHash.slice(0, queryIndex);
const query = withoutHash.slice(queryIndex + 1);
const replacements = { platform: template.platformCode, ...linkTemplate.mappings };
const changes = [];
let parameterCount = 0;
const convertedSegments = query.split('&').map((segment) => {
if (!segment) return segment;
parameterCount += 1;
const equalIndex = segment.indexOf('=');
const rawKey = equalIndex >= 0 ? segment.slice(0, equalIndex) : segment;
const rawValue = equalIndex >= 0 ? segment.slice(equalIndex + 1) : '';
const key = decodeKey(rawKey);
if (!Object.prototype.hasOwnProperty.call(replacements, key)) return segment;
const replacement = String(replacements[key] ?? '');
if (rawValue !== replacement) changes.push({ key, from: rawValue, to: replacement });
return `${rawKey}=${replacement}`;
});
return {
url: `${base}?${convertedSegments.join('&')}${hash}`,
type,
changes,
parameterCount,
preservedCount: parameterCount - changes.length
};
}
root.TrackingLinkConverter = { detectLinkType, convertTrackingLink };
})(typeof window === 'undefined' ? globalThis : window);
+75 -83
View File
@@ -2,36 +2,26 @@
const templates = window.LINK_TEMPLATES; const templates = window.LINK_TEMPLATES;
if (!templates?.length) return; if (!templates?.length) return;
const { convertTrackingLink } = window.TrackingLinkConverter;
const $ = (id) => document.getElementById(id); const $ = (id) => document.getElementById(id);
const platformSelect = $('platformSelect'); const platformSelect = $('platformSelect');
const linkTypeSelect = $('linkTypeSelect');
const mappingBody = $('mappingTableBody');
const macroBody = $('macroTableBody'); const macroBody = $('macroTableBody');
const output = $('urlOutput'); const output = $('urlOutput');
const businessIds = ['advid','campid','pkg','platformCode','sanCampid','adsetId','countryCode']; let activeResult = null;
let state = { platformIndex:0, type:'click', mappings:{} }; let inputTimer;
$('templateCount').textContent = `${templates.length} 个平台模板`; $('templateCount').textContent = `${templates.length} 个平台模板`;
templates.forEach((template, index) => platformSelect.add(new Option(template.name, index))); templates.forEach((template, index) => platformSelect.add(new Option(template.name, index)));
const activeTemplate = () => templates[state.platformIndex]; const activeTemplate = () => templates[Number(platformSelect.value || 0)];
const activeLink = () => activeTemplate().links[state.type];
function loadPlatform() { function renderPlatform() {
const template = activeTemplate(); const template = activeTemplate();
linkTypeSelect.innerHTML = ''; const review = template.status.includes('待');
Object.keys(template.links).forEach((type) => linkTypeSelect.add(new Option(type === 'click' ? 'Click Link' : 'Impression Link', type))); $('platformStatus').innerHTML = `<span class="template-status ${review ? 'review' : ''}">${escapeHtml(template.status)}</span>`;
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(); renderMacros();
loadLink(); if ($('originalLink').value.trim()) convertFromInput(false);
}
function loadLink() {
state.mappings = { ...activeLink().mappings };
renderMappings();
buildUrl();
} }
function renderMacros() { function renderMacros() {
@@ -42,76 +32,72 @@
: '<tr><td colspan="2" class="table-empty">没有匹配的媒体宏</td></tr>'; : '<tr><td colspan="2" class="table-empty">没有匹配的媒体宏</td></tr>';
} }
function renderMappings() { function renderChanges(changes) {
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(''); const rows = changes.length ? changes : [{ key:'—', from:'原链接参数无需变化', to:'—' }];
mappingBody.querySelectorAll('input').forEach((input) => input.addEventListener('input', () => { $('mappingTableBody').innerHTML = rows.map((change) => `<tr>
state.mappings[input.dataset.key] = input.value; <td><code>${escapeHtml(change.key)}</code></td>
buildUrl(); <td><span class="old-param">${escapeHtml(change.from || '(空值)')}</span></td>
})); <td>${change.to ? `<code>${escapeHtml(change.to)}</code>` : '<span class="empty-param">留空</span>'}</td>
</tr>`).join('');
} }
function buildUrl() { function showEmpty() {
const link = activeLink(); activeResult = null;
const campid = $('campid').value.trim(); $('conversionEmpty').classList.remove('hidden');
const pkg = $('pkg').value.trim(); $('conversionResult').classList.add('hidden');
const type = state.type; $('detectedType').textContent = '等待链接';
const params = []; $('generatorMessage').textContent = '';
const addBusiness = (key, value) => params.push(`${key}=${encodeURIComponent(value)}`); $('copyStatus').textContent = '';
const addRaw = (key, value) => params.push(`${key}=${value}`); $('copyLink').disabled = true;
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]) => { function showError(message) {
const finalValue = key === 'country_code' && $('countryCode').value.trim() activeResult = null;
? $('countryCode').value.trim().toUpperCase() $('generatorMessage').textContent = message;
: value; $('conversionEmpty').classList.remove('hidden');
addRaw(key, finalValue); $('conversionResult').classList.add('hidden');
}); $('detectedType').textContent = '无法识别';
$('copyLink').disabled = true;
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('&')}`; function convertFromInput(showRequiredMessage = true) {
output.textContent = url; const original = $('originalLink').value.trim();
$('urlLength').textContent = `${url.length} chars`; $('copyStatus').textContent = '';
if (!original) {
const missing = []; showEmpty();
if (!campid) missing.push('Campaign ID'); if (showRequiredMessage) $('generatorMessage').textContent = '请先粘贴一条完整的 Tracking Link';
if (!pkg) missing.push('Package Name'); return;
$('generatorMessage').textContent = missing.length ? `待填写:${missing.join('、')}` : '';
} }
function resetGenerator() { try {
$('advid').value = '30'; const result = convertTrackingLink(original, activeTemplate());
$('campid').value = ''; activeResult = result;
$('pkg').value = ''; const typeLabel = result.type === 'imp' ? 'Impression' : 'Click';
$('platformCode').value = activeTemplate().platformCode; $('detectedType').innerHTML = `<span class="type-chip">${typeLabel}</span>`;
$('sanCampid').value = ''; $('summaryType').textContent = typeLabel;
$('adsetId').value = '-1'; $('replacementCount').textContent = result.changes.length;
$('countryCode').value = ''; $('preservedCount').textContent = result.preservedCount;
loadLink(); $('urlLength').textContent = `${result.url.length} chars`;
output.textContent = result.url;
renderChanges(result.changes);
$('generatorMessage').textContent = result.changes.length ? '' : '链接有效,但没有发现需要替换的参数';
$('conversionEmpty').classList.add('hidden');
$('conversionResult').classList.remove('hidden');
$('copyLink').disabled = false;
} catch (error) {
showError(error.message === 'Invalid URL' ? '链接格式不正确,请粘贴包含 http:// 或 https:// 的完整链接' : error.message);
}
}
function loadSample() {
$('originalLink').value = 'https://trident.bigpinggo.com/l/click?advid=30&campid=91566789&platform=M_API_Self_Mintegral&pkg=com.salaryse.android&clickid={click_id}&offer_name={offer_name}&creative_id={creative_id}&creative_name={creative_name}&affiliate_id={affiliate_id}&idfa={idfa}&gaid={gaid}&imei={imei}&android_id={android_id}&mac={mac}&oaid={oaid}&uagent={ua}&os_platform={os_platform}&os_version={os_version}&device_model={device_model}&ip={ip}&country_code={country_code}&ext={ext}&rta_token={rta_token}&san_campid=91566789&adset_id=-1&c_cto=0';
convertFromInput(false);
} }
async function copyUrl() { async function copyUrl() {
if (!$('campid').value.trim() || !$('pkg').value.trim()) { if (!activeResult) return;
$('copyStatus').textContent = '请先补全必填参数';
return;
}
try { try {
await navigator.clipboard.writeText(output.textContent); await navigator.clipboard.writeText(activeResult.url);
} catch (_) { } catch (_) {
const range = document.createRange(); const range = document.createRange();
range.selectNodeContents(output); range.selectNodeContents(output);
@@ -123,7 +109,7 @@
} }
$('copyStatus').textContent = '已复制'; $('copyStatus').textContent = '已复制';
const toast = $('toast'); const toast = $('toast');
toast.textContent = '最终链接已复制到剪贴板'; toast.textContent = '转换后的平台链接已复制';
toast.classList.add('show'); toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 1800); setTimeout(() => toast.classList.remove('show'), 1800);
} }
@@ -132,11 +118,17 @@
return String(value).replace(/[&<>"']/g, (char) => ({ '&':'&amp;', '<':'&lt;', '>':'&gt;', '"':'&quot;', "'":'&#39;' })[char]); return String(value).replace(/[&<>"']/g, (char) => ({ '&':'&amp;', '<':'&lt;', '>':'&gt;', '"':'&quot;', "'":'&#39;' })[char]);
} }
platformSelect.addEventListener('change', () => { state.platformIndex = Number(platformSelect.value); loadPlatform(); }); platformSelect.addEventListener('change', renderPlatform);
linkTypeSelect.addEventListener('change', () => { state.type = linkTypeSelect.value; loadLink(); });
$('macroSearch').addEventListener('input', renderMacros); $('macroSearch').addEventListener('input', renderMacros);
businessIds.forEach((id) => $(id).addEventListener('input', buildUrl)); $('convertLink').addEventListener('click', () => convertFromInput(true));
$('resetGenerator').addEventListener('click', resetGenerator); $('sampleLink').addEventListener('click', loadSample);
$('copyLink').addEventListener('click', copyUrl); $('copyLink').addEventListener('click', copyUrl);
loadPlatform(); $('clearConverter').addEventListener('click', () => { $('originalLink').value = ''; showEmpty(); $('originalLink').focus(); });
$('originalLink').addEventListener('input', () => {
clearTimeout(inputTimer);
inputTimer = setTimeout(() => convertFromInput(false), 220);
});
renderPlatform();
showEmpty();
})(); })();
+3 -3
View File
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -47,10 +47,10 @@
<div class="tool-number">02</div> <div class="tool-number">02</div>
<div class="tool-icon" aria-hidden="true">⌁</div> <div class="tool-icon" aria-hidden="true">⌁</div>
<p class="tool-kicker">TRACKING BUILDER</p> <p class="tool-kicker">TRACKING BUILDER</p>
<h2>自投链接生成器</h2> <h2>自投链接转换器</h2>
<p>选择自投媒体,填写业务参数,按平台专属宏规则实时生成 Click / Impression 链接。</p> <p>粘贴原始 Mintegral Click Link,自动将通用参数替换为已确认的平台专属宏。</p>
<ul><li>平台宏模板集中管理</li><li>业务参数实时替换</li><li>一键复制最终链接</li></ul> <ul><li>Mintegral Click</li><li>业务参数原样保留</li><li>一键复制转换结果</li></ul>
<span class="tool-cta">生成链接 <b>→</b></span> <span class="tool-cta">转换链接 <b>→</b></span>
</a> </a>
</section> </section>
+31 -22
View File
@@ -3,8 +3,8 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="自投平台 Click 和 Impression Tracking Link 生成器"> <meta name="description" content="粘贴原始 Tracking Link,自动替换为目标自投平台宏">
<title>自投链接生成器 · Traffic Intelligence</title> <title>自投链接转换器 · Traffic Intelligence</title>
<link rel="stylesheet" href="assets/styles.css"> <link rel="stylesheet" href="assets/styles.css">
</head> </head>
<body> <body>
@@ -16,40 +16,48 @@
<main class="shell page-main"> <main class="shell page-main">
<section class="page-heading"> <section class="page-heading">
<div><p class="eyebrow"><span></span> TRACKING BUILDER / 02</p><h1>自投平台链接生成器</h1><p>选择媒体模板,填写订单参数,并实时检查平台宏映射与最终链接。</p></div> <div><p class="eyebrow"><span></span> TRACKING CONVERTER / 02</p><h1>自投平台链接转换器</h1><p>粘贴一条原始 Tracking Link,自动替换为目标平台要求的宏参数。</p></div>
<div class="data-badge"><span class="pulse-dot"></span><p><strong id="templateCount">— 个平台模板</strong><small>Click / Impression</small></p></div> <div class="data-badge"><span class="pulse-dot"></span><p><strong id="templateCount">— 个平台模板</strong><small>Mintegral Click URL 转换</small></p></div>
</section> </section>
<section class="generator-stack"> <section class="generator-stack">
<section class="panel generator-config"> <section class="panel converter-input-panel">
<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-topline"><span class="section-index">STEP 01</span><h2>选择目标平台并粘贴链接</h2><button class="outline-btn" id="clearConverter" type="button">清空</button></div>
<div class="config-grid"> <div class="converter-toolbar">
<label><span>自投平台 *</span><select id="platformSelect"></select></label> <label><span>目标自投平台 *</span><select id="platformSelect"></select></label>
<label><span>链接类型 *</span><select id="linkTypeSelect"></select></label> <div class="detect-card"><span>模板状态</span><strong id="platformStatus">—</strong></div>
<label><span>Advertiser ID</span><input id="advid" value="30" inputmode="numeric"></label> <div class="detect-card"><span>识别类型</span><strong id="detectedType">等待链接</strong></div>
<label><span>Campaign ID *</span><input id="campid" placeholder="请输入 Camp ID" inputmode="numeric"></label> </div>
<label><span>Package Name *</span><input id="pkg" placeholder="如 com.example.app"></label> <label class="textarea-label" for="originalLink"><span>原始 Tracking Link *</span><small>原有业务参数与固定值会保留</small></label>
<label><span>Platform Code</span><input id="platformCode"></label> <textarea id="originalLink" class="link-textarea" rows="6" spellcheck="false" placeholder="在这里粘贴完整的 Click 或 Impression Link…"></textarea>
<label><span>SAN Campaign ID</span><input id="sanCampid" placeholder="可选"></label> <div class="converter-actions">
<label><span>Ad Set ID</span><input id="adsetId" value="-1"></label> <button class="primary-btn" id="convertLink" type="button"><span>转换为平台链接</span><b>→</b></button>
<label><span>固定国家码</span><input id="countryCode" maxlength="3" placeholder="留空则使用媒体宏"></label> <button class="text-inline-btn" id="sampleLink" type="button">载入示例链接</button>
<span class="validation-line" id="generatorMessage" role="alert"></span>
</div> </div>
<div class="validation-line" id="generatorMessage" role="alert"></div>
</section> </section>
<section class="generator-columns"> <section class="generator-columns">
<section class="panel macro-panel"> <section class="panel macro-panel">
<div class="panel-title compact"><span>STEP 02</span><h2>媒体宏参考</h2><p id="platformStatus"></p></div> <div class="panel-title compact"><span>PLATFORM MACROS</span><h2>目标平台宏参考</h2><p>当前平台可用于替换的动态参数</p></div>
<div class="macro-search"><input id="macroSearch" placeholder="搜索宏或说明…" aria-label="搜索媒体宏"></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> <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>
<section class="panel output-panel"> <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="config-topline output-title"><span class="section-index">STEP 02</span><h2>转换结果</h2><span class="live-chip"><i></i> AUTO</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="conversion-empty" id="conversionEmpty"><div>↳</div><h3>等待原始链接</h3><p>转换后将在这里展示参数替换明细与最终链接。</p></div>
<div class="output-label"><span>最终投放链接</span><small id="urlLength">0 chars</small></div> <div id="conversionResult" class="hidden">
<div class="conversion-summary">
<div><span>链接类型</span><strong id="summaryType">—</strong></div>
<div><span>已替换参数</span><strong id="replacementCount">0</strong></div>
<div><span>保留参数</span><strong id="preservedCount">0</strong></div>
</div>
<div class="mapping-table-wrap"><table class="mapping-table conversion-table"><thead><tr><th>参数 Key</th><th>原始值</th><th>替换后的平台宏</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> <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> <div class="output-actions"><button class="primary-btn" id="copyLink" type="button" disabled><span>复制最终链接</span><b>⧉</b></button><span id="copyStatus" class="copy-status" role="status"></span></div>
</div>
</section> </section>
</section> </section>
</section> </section>
@@ -58,6 +66,7 @@
<div class="toast" id="toast" role="status"></div> <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> <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/data/link-templates.js"></script>
<script src="assets/link-converter-core.js"></script>
<script src="assets/common.js"></script> <script src="assets/common.js"></script>
<script src="assets/link-generator.js"></script> <script src="assets/link-generator.js"></script>
</body> </body>