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();
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user