feat: add persistent link summary

This commit is contained in:
王源
2026-10-19 20:24:00 +08:00
parent 969bab77a7
commit 903b058340
3 changed files with 155 additions and 41 deletions
+120 -17
View File
@@ -3,12 +3,14 @@
if (!templates?.length) return; if (!templates?.length) return;
const { convertTrackingLink } = window.TrackingLinkConverter; const { convertTrackingLink } = window.TrackingLinkConverter;
const HISTORY_KEY = 'traffic_link_history_v1';
const MAX_HISTORY = 50;
const $ = (id) => document.getElementById(id); const $ = (id) => document.getElementById(id);
const platformSelect = $('platformSelect'); const platformSelect = $('platformSelect');
const macroBody = $('macroTableBody'); const macroBody = $('macroTableBody');
const output = $('urlOutput'); const output = $('urlOutput');
let activeResult = null; let activeResult = null;
let history = loadHistory();
let inputTimer; let inputTimer;
$('templateCount').textContent = `${templates.length} 个平台模板`; $('templateCount').textContent = `${templates.length} 个平台模板`;
@@ -26,7 +28,9 @@
function renderMacros() { function renderMacros() {
const query = $('macroSearch').value.trim().toLowerCase(); const query = $('macroSearch').value.trim().toLowerCase();
const rows = activeTemplate().macros.filter(([macro, desc]) => `${macro} ${desc}`.toLowerCase().includes(query)); const allMacros = activeTemplate().macros;
const rows = allMacros.filter(([macro, desc]) => `${macro} ${desc}`.toLowerCase().includes(query));
$('macroCount').textContent = `${allMacros.length} 个可用宏 · 点击展开`;
macroBody.innerHTML = rows.length macroBody.innerHTML = rows.length
? rows.map(([macro, desc]) => `<tr><td><code>${escapeHtml(macro)}</code></td><td>${escapeHtml(desc)}</td></tr>`).join('') ? 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>'; : '<tr><td colspan="2" class="table-empty">没有匹配的媒体宏</td></tr>';
@@ -66,7 +70,7 @@
if (!original) { if (!original) {
showEmpty(); showEmpty();
if (showRequiredMessage) $('generatorMessage').textContent = '请先粘贴一条完整的 Tracking Link'; if (showRequiredMessage) $('generatorMessage').textContent = '请先粘贴一条完整的 Tracking Link';
return; return null;
} }
try { try {
@@ -84,34 +88,112 @@
$('conversionEmpty').classList.add('hidden'); $('conversionEmpty').classList.add('hidden');
$('conversionResult').classList.remove('hidden'); $('conversionResult').classList.remove('hidden');
$('copyLink').disabled = false; $('copyLink').disabled = false;
return result;
} catch (error) { } catch (error) {
showError(error.message === 'Invalid URL' ? '链接格式不正确,请粘贴包含 http:// 或 https:// 的完整链接' : error.message); showError(error.message === 'Invalid URL' ? '链接格式不正确,请粘贴包含 http:// 或 https:// 的完整链接' : error.message);
return null;
} }
} }
function convertAndSave() {
const result = convertFromInput(true);
if (!result) return;
saveHistory(result);
$('generatorMessage').textContent = '转换完成,已加入平台链接总结';
showToast('转换完成并已保存记录');
}
function loadSample() { 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'; $('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); convertFromInput(false);
} }
function loadHistory() {
try {
const stored = JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]');
return Array.isArray(stored) ? stored.filter((item) => item?.id && item?.url).slice(0, MAX_HISTORY) : [];
} catch (_) { return []; }
}
function persistHistory() {
try {
localStorage.setItem(HISTORY_KEY, JSON.stringify(history));
return true;
} catch (_) {
showToast('浏览器无法保存记录,请检查存储权限');
return false;
}
}
function extractPkg(url) {
try { return new URL(url).searchParams.get('pkg') || '—'; }
catch (_) { return '—'; }
}
function saveHistory(result) {
const platform = activeTemplate().name;
const pkg = extractPkg(result.url);
history = history.filter((item) => item.url !== result.url);
history.unshift({
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
platform,
pkg,
type: result.type,
createdAt: new Date().toISOString(),
url: result.url
});
history = history.slice(0, MAX_HISTORY);
persistHistory();
renderHistory();
}
function formatTime(value) {
const date = new Date(value);
if (Number.isNaN(date.getTime())) return '—';
return new Intl.DateTimeFormat('zh-CN', { year:'numeric', month:'2-digit', day:'2-digit', hour:'2-digit', minute:'2-digit', second:'2-digit', hour12:false }).format(date);
}
function renderHistory() {
$('historyCount').textContent = history.length;
$('clearHistory').disabled = history.length === 0;
$('historyEmpty').classList.toggle('hidden', history.length > 0);
$('historyTableWrap').classList.toggle('hidden', history.length === 0);
$('historyTableBody').innerHTML = history.map((item) => `<tr>
<td><time datetime="${escapeHtml(item.createdAt)}">${escapeHtml(formatTime(item.createdAt))}</time></td>
<td><span class="history-platform">${escapeHtml(item.platform)}</span></td>
<td><code class="history-pkg">${escapeHtml(item.pkg)}</code></td>
<td><div class="history-link"><span title="${escapeHtml(item.url)}">${escapeHtml(item.url)}</span><button type="button" data-action="copy" data-id="${escapeHtml(item.id)}">复制</button></div></td>
<td><button class="delete-record" type="button" data-action="delete" data-id="${escapeHtml(item.id)}" aria-label="删除这条记录">删除</button></td>
</tr>`).join('');
}
async function copyText(text) {
try { await navigator.clipboard.writeText(text); }
catch (_) {
const area = document.createElement('textarea');
area.value = text;
area.style.position = 'fixed';
area.style.opacity = '0';
document.body.appendChild(area);
area.select();
document.execCommand('copy');
area.remove();
}
}
async function copyUrl() { async function copyUrl() {
if (!activeResult) return; if (!activeResult) return;
try { await copyText(activeResult.url);
await navigator.clipboard.writeText(activeResult.url);
} catch (_) {
const range = document.createRange();
range.selectNodeContents(output);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
document.execCommand('copy');
selection.removeAllRanges();
}
$('copyStatus').textContent = '已复制'; $('copyStatus').textContent = '已复制';
showToast('转换后的平台链接已复制');
}
function showToast(message) {
const toast = $('toast'); const toast = $('toast');
toast.textContent = '转换后的平台链接已复制'; toast.textContent = message;
toast.classList.add('show'); toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 1800); clearTimeout(showToast.timer);
showToast.timer = setTimeout(() => toast.classList.remove('show'), 1800);
} }
function escapeHtml(value) { function escapeHtml(value) {
@@ -120,10 +202,30 @@
platformSelect.addEventListener('change', renderPlatform); platformSelect.addEventListener('change', renderPlatform);
$('macroSearch').addEventListener('input', renderMacros); $('macroSearch').addEventListener('input', renderMacros);
$('convertLink').addEventListener('click', () => convertFromInput(true)); $('convertLink').addEventListener('click', convertAndSave);
$('sampleLink').addEventListener('click', loadSample); $('sampleLink').addEventListener('click', loadSample);
$('copyLink').addEventListener('click', copyUrl); $('copyLink').addEventListener('click', copyUrl);
$('clearConverter').addEventListener('click', () => { $('originalLink').value = ''; showEmpty(); $('originalLink').focus(); }); $('clearConverter').addEventListener('click', () => { $('originalLink').value = ''; showEmpty(); $('originalLink').focus(); });
$('clearHistory').addEventListener('click', () => {
if (!history.length || !window.confirm('确定清空全部平台链接记录吗?')) return;
history = [];
persistHistory();
renderHistory();
});
$('historyTableBody').addEventListener('click', async (event) => {
const button = event.target.closest('button[data-action]');
if (!button) return;
const item = history.find((record) => record.id === button.dataset.id);
if (!item) return;
if (button.dataset.action === 'copy') {
await copyText(item.url);
showToast('历史链接已复制');
} else if (button.dataset.action === 'delete') {
history = history.filter((record) => record.id !== item.id);
persistHistory();
renderHistory();
}
});
$('originalLink').addEventListener('input', () => { $('originalLink').addEventListener('input', () => {
clearTimeout(inputTimer); clearTimeout(inputTimer);
inputTimer = setTimeout(() => convertFromInput(false), 220); inputTimer = setTimeout(() => convertFromInput(false), 220);
@@ -131,4 +233,5 @@
renderPlatform(); renderPlatform();
showEmpty(); showEmpty();
renderHistory();
})(); })();
+3 -3
View File
File diff suppressed because one or more lines are too long
+32 -21
View File
@@ -3,7 +3,7 @@
<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="粘贴原始 Tracking Link,自动替换为目标自投平台宏"> <meta name="description" content="粘贴原始 Tracking Link,自动替换为 Mintegral 平台宏">
<title>自投链接转换器 · Traffic Intelligence</title> <title>自投链接转换器 · Traffic Intelligence</title>
<link rel="stylesheet" href="assets/styles.css"> <link rel="stylesheet" href="assets/styles.css">
</head> </head>
@@ -16,7 +16,7 @@
<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 CONVERTER / 02</p><h1>自投平台链接转换器</h1><p>粘贴一条原始 Tracking Link,自动替换为目标平台要求的宏参数。</p></div> <div><p class="eyebrow"><span></span> TRACKING CONVERTER / 02</p><h1>自投平台链接转换器</h1><p>粘贴一条原始 Tracking Link,自动替换为 Mintegral 要求的宏参数。</p></div>
<div class="data-badge"><span class="pulse-dot"></span><p><strong id="templateCount">— 个平台模板</strong><small>Mintegral Click URL 转换</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>
@@ -29,7 +29,7 @@
<div class="detect-card"><span>识别类型</span><strong id="detectedType">等待链接</strong></div> <div class="detect-card"><span>识别类型</span><strong id="detectedType">等待链接</strong></div>
</div> </div>
<label class="textarea-label" for="originalLink"><span>原始 Tracking Link *</span><small>原有业务参数与固定值会保留</small></label> <label class="textarea-label" for="originalLink"><span>原始 Tracking Link *</span><small>原有业务参数与固定值会保留</small></label>
<textarea id="originalLink" class="link-textarea" rows="6" spellcheck="false" placeholder="在这里粘贴完整的 Click 或 Impression Link…"></textarea> <textarea id="originalLink" class="link-textarea" rows="6" spellcheck="false" placeholder="在这里粘贴完整的 Mintegral Click Link…"></textarea>
<div class="converter-actions"> <div class="converter-actions">
<button class="primary-btn" id="convertLink" type="button"><span>转换为平台链接</span><b>→</b></button> <button class="primary-btn" id="convertLink" type="button"><span>转换为平台链接</span><b>→</b></button>
<button class="text-inline-btn" id="sampleLink" type="button">载入示例链接</button> <button class="text-inline-btn" id="sampleLink" type="button">载入示例链接</button>
@@ -37,28 +37,39 @@
</div> </div>
</section> </section>
<section class="generator-columns"> <details class="panel macro-disclosure">
<section class="panel macro-panel"> <summary><span class="disclosure-icon">+</span><div><span class="section-index">REFERENCE</span><strong>Mintegral 平台宏参考</strong></div><small id="macroCount">点击展开完整宏清单</small></summary>
<div class="panel-title compact"><span>PLATFORM MACROS</span><h2>目标平台宏参考</h2><p>当前平台可用于替换的动态参数</p></div> <div class="macro-content">
<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> </div>
</details>
<section class="panel output-panel"> <section class="panel output-panel output-panel-wide">
<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="config-topline output-title"><span class="section-index">STEP 02</span><h2>转换结果</h2><span class="live-chip"><i></i> AUTO</span></div>
<div class="conversion-empty" id="conversionEmpty"><div>↳</div><h3>等待原始链接</h3><p>转换后将在这里展示参数替换明细与最终链接。</p></div> <div class="conversion-empty" id="conversionEmpty"><div>↳</div><h3>等待原始链接</h3><p>转换后将在这里展示参数替换明细与最终链接。</p></div>
<div id="conversionResult" class="hidden"> <div id="conversionResult" class="hidden">
<div class="conversion-summary"> <div class="conversion-summary">
<div><span>链接类型</span><strong id="summaryType">—</strong></div> <div><span>链接类型</span><strong id="summaryType">—</strong></div>
<div><span>已替换参数</span><strong id="replacementCount">0</strong></div> <div><span>已替换参数</span><strong id="replacementCount">0</strong></div>
<div><span>保留参数</span><strong id="preservedCount">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>
<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> </div>
</section> <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>
<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 class="panel history-panel">
<div class="history-heading">
<div><span class="section-index">LINK SUMMARY</span><h2>平台链接总结 <em id="historyCount">0</em></h2><p>保存已转换链接的平台、PKG、时间和最终结果,仅存储在当前浏览器。</p></div>
<button class="outline-btn" id="clearHistory" type="button" disabled>清空记录</button>
</div>
<div class="history-empty" id="historyEmpty"><span>⌁</span><p>还没有转换记录</p></div>
<div class="history-table-wrap hidden" id="historyTableWrap">
<table class="history-table"><thead><tr><th>转换时间</th><th>平台</th><th>PKG</th><th>最终链接</th><th>操作</th></tr></thead><tbody id="historyTableBody"></tbody></table>
</div>
</section> </section>
</section> </section>
</main> </main>