用户:RedDragon/Test:修订间差异
来自Rizline中文维基
更多操作
Xp00000000(留言 | 贡献) 小 |
|||
| 第9行: | 第9行: | ||
style="padding: 0.5rem 1rem; background: #3366CC; color: #ffffff; font-size: 16px; border: none; border-radius: 0 5px 5px 0">搜索</button> | style="padding: 0.5rem 1rem; background: #3366CC; color: #ffffff; font-size: 16px; border: none; border-radius: 0 5px 5px 0">搜索</button> | ||
</div> | </div> | ||
<div style="display: flex; justify-content: center; align-items: center; margin-top: 1rem;"> | <div style="display: flex; justify-content: center; align-items: center; margin-top: 1rem;"> | ||
<label for="score-filter" style="margin-right: 0.5rem;">筛选匹配度 ≥</label> | <label for="algorithm-select" style="margin-right: 0.5rem;">算法:</label> | ||
<select id="algorithm-select" style="padding: 0.3rem; margin-right: 1rem;"> | |||
<option value="custom">神秘算法</option> | |||
<option value="fuse">Fuse.js</option> | |||
</select> | |||
<div id="custom-filter-container"> | |||
<label for="score-filter" style="margin-right: 0.5rem;">筛选匹配度 ≥</label> | |||
<input type="number" id="score-filter" value="3" min="1" max="100" | |||
style="width: 4rem; padding: 0.3rem;"> | |||
</div> | |||
<div id="fuse-filter-container" style="display: none;"> | |||
<label for="fuse-threshold" style="margin-right: 0.5rem;">匹配阈值 ≤</label> | |||
<input type="number" id="fuse-threshold" value="0.4" min="0" max="1" step="0.1" | |||
style="width: 4rem; padding: 0.3rem;"> | |||
</div> | |||
</div> | </div> | ||
<div style="margin-top: 100px"> | <div style="margin-top: 100px"> | ||
<h3>搜索结果:</h3> | <h3>搜索结果:</h3> | ||
| 第24行: | 第39行: | ||
</div> | </div> | ||
<script src="https://cdn.jsdelivr.net/npm/fuse.js@6.6.2/dist/fuse.min.js"></script> | |||
<script> | <script> | ||
let fuseInstance = null | |||
document.addEventListener('DOMContentLoaded', function () { | document.addEventListener('DOMContentLoaded', function () { | ||
initAllAliases() | initAllAliases() | ||
initFuse() | |||
document.getElementById('alias-btn').addEventListener('click', searchSongs) | document.getElementById('alias-btn').addEventListener('click', searchSongs) | ||
document.getElementById('alias-input').addEventListener('keypress', function (e) { | document.getElementById('alias-input').addEventListener('keypress', function (e) { | ||
if (e.key == 'Enter') searchSongs() | if (e.key == 'Enter') searchSongs() | ||
}) | |||
document.getElementById('algorithm-select').addEventListener('change', function () { | |||
const algorithm = this.value | |||
const customContainer = document.getElementById('custom-filter-container') | |||
const fuseContainer = document.getElementById('fuse-filter-container') | |||
if (algorithm == 'fuse') { | |||
customContainer.style.display = 'none' | |||
fuseContainer.style.display = 'block' | |||
} else { | |||
customContainer.style.display = 'block' | |||
fuseContainer.style.display = 'none' | |||
} | |||
searchSongs() | |||
}) | }) | ||
}) | }) | ||
| 第44行: | 第79行: | ||
li.innerHTML = `<span class="song-title">${song.title || ''}</span>: <span class="aliases">${aliasText}</span>` | li.innerHTML = `<span class="song-title">${song.title || ''}</span>: <span class="aliases">${aliasText}</span>` | ||
container.appendChild(li) | container.appendChild(li) | ||
}) | |||
} | |||
function initFuse() { | |||
const fuseOptions = { | |||
includeScore: true, | |||
includeMatches: true, | |||
threshold: 0.4, | |||
minMatchCharLength: 1, | |||
keys: ['title', 'aliases'] | |||
} | |||
const searchData = songlist.map(function (song) { | |||
return { | |||
id: song.title, | |||
title: song.title, | |||
aliases: song.aliases ? song.aliases.join(' ') : '' | |||
} | |||
}) | |||
fuseInstance = new Fuse(searchData, fuseOptions) | |||
} | |||
// Fuse.js搜索函数 | |||
function searchWithFuse(searchText, threshold) { | |||
if (!fuseInstance) initFuse() | |||
const results = fuseInstance.search(searchText, { | |||
limit: 50, | |||
threshold: threshold | |||
}) | |||
return results.map(function (result) { | |||
const song = songlist.find(function (s) { | |||
return s.title == result.item.id | |||
}) | |||
return { | |||
song: song, | |||
score: Math.round((1 - (result.score || 0)) * 100), | |||
matched: result.matches ? result.matches[0].value : '', | |||
matchedAlias: '', | |||
fuseScore: result.score | |||
} | |||
}) | }) | ||
} | } | ||
| 第51行: | 第131行: | ||
允许间隔,顺序匹配 | 允许间隔,顺序匹配 | ||
每匹配一个字符加1分 | 每匹配一个字符加1分 | ||
连续匹配加0.5分 | |||
大小写精确匹配加0.3分 | |||
开头匹配加1.5分 | |||
完全包含关系额外加分 | |||
*/ | |||
function getMatchScore(input, target) { | function getMatchScore(input, target) { | ||
if (!input || !target) return { score: 0, matched: "" } | if (!input || !target) return { score: 0, matched: "" } | ||
const originalInput = input | |||
const originalTarget = target | |||
input = input.toLowerCase() | input = input.toLowerCase() | ||
target = target.toLowerCase() | target = target.toLowerCase() | ||
if (input == target) return { score: 100, matched: originalTarget } | |||
let | |||
let containBonus = 0 | |||
if (input.includes(target)) { | |||
if (target.length >= 5) { | |||
if (input | if (input.length >= 3) { | ||
containBonus = 6 + (target.length * 0.3) | |||
} | |||
} else { | |||
if (input.length >= 2) { | |||
containBonus = 6 + (target.length * 0.3) | |||
} | |||
} | |||
} else if (target.includes(input)) { | |||
if (target.length >= 5) { | |||
if (input.length >= 3) { | |||
containBonus = 5 + (input.length * 0.3) | |||
} | |||
} else { | |||
if (input.length >= 2) { | |||
containBonus = 5 + (input.length * 0.3) | |||
} | } | ||
} | } | ||
} | } | ||
function calculateScore(src, tgt, originalSrc, originalTgt) { | |||
let score = 0, matched = "", lastPos = -1, bonus = 0, pos = 0, caseBonus = 0 | |||
for (let i = 0; i < src.length; i++) { | |||
let found = false | |||
for (let j = pos; j < tgt.length; j++) { | |||
if (src[i] == tgt[j]) { | |||
score++ | |||
matched += originalTgt[j] | |||
if ( | if (originalSrc[i] == originalTgt[j]) { | ||
caseBonus += 0.3 | |||
} | } | ||
if (lastPos >= 0) { | |||
if (j == lastPos + 1) { | |||
bonus += 0.5 | |||
} | |||
} | |||
lastPos = j | |||
pos = j + 1 | |||
found = true | |||
break | |||
} | } | ||
} | } | ||
if (!found) break | |||
} | |||
let startBonus = 0 | |||
if (matched.length > 0) { | |||
if (tgt.indexOf(src[0]) == 0) { | |||
startBonus = 1.5 | |||
} | |||
} | |||
return { | |||
score: score + bonus + caseBonus + startBonus + containBonus, | |||
matched | |||
} | } | ||
} | } | ||
if ( | const result1 = calculateScore(input, target, originalInput, originalTarget) | ||
return | const result2 = calculateScore(target, input, originalTarget, originalInput) | ||
if (result1.score >= result2.score) { | |||
return result1 | |||
} else { | } else { | ||
return | return result2 | ||
} | } | ||
} | } | ||
function searchSongs() { | function searchSongs() { | ||
const searchText = document.getElementById('alias-input').value.trim | const searchText = document.getElementById('alias-input').value.trim() | ||
const resultsContainer = document.getElementById('alias-results') | const resultsContainer = document.getElementById('alias-results') | ||
const | const algorithm = document.getElementById('algorithm-select').value | ||
if (!searchText) { | if (!searchText) { | ||
| 第121行: | 第229行: | ||
} | } | ||
const results = songlist.map(function (song) { | let results = [] | ||
if (algorithm == 'fuse') { | |||
const threshold = parseFloat(document.getElementById('fuse-threshold').value) || 0.4 | |||
results = searchWithFuse(searchText, threshold) | |||
// console.log(`Fuse.js搜索: "${searchText}", 阈值: ${threshold}, 结果数: ${results.length}`) | |||
} else { | |||
const resc = Number(document.getElementById('score-filter').value) || 3 | |||
results = songlist.map(function (song) { | |||
} | let best = getMatchScore(searchText, song.title || '') | ||
} | let bestAlias = "" | ||
if (song.aliases) { | |||
if (song.aliases.length > 0) { | |||
song.aliases.forEach(function (alias) { | |||
let result = getMatchScore(searchText, alias) | |||
if (result.score > best.score) { | |||
best = result | |||
bestAlias = alias | |||
} | |||
}) | |||
} | |||
} | |||
return { | |||
song: song, | |||
score: best.score, | |||
matched: best.matched, | |||
matchedAlias: bestAlias | |||
} | } | ||
}).filter(function (result) { | |||
// if (result.score >= resc) { | |||
// console.log( | |||
// `${result.song.title}: [${result.matched}]${(result.matchedAlias ? ` (${result.matchedAlias})` : "")} ${result.score} ` | |||
// ) | |||
// } | |||
return result.score >= resc | |||
}).sort(function (a, b) { | |||
return b.score - a.score | |||
}) | |||
} | |||
} | |||
if (results.length == 0) { | if (results.length == 0) { | ||
2025年9月15日 (一) 01:10的版本