|
|
| 第8行: |
第8行: |
| border: 1px solid #e0e0e0; | | border: 1px solid #e0e0e0; |
| } | | } |
| .deadlink-scanner h3 { | | .deadlink-scanner h3 { margin-bottom: 10px; } |
| margin-bottom: 10px;
| | .scan-btn { |
| } | |
| .deadlink-scanner .scan-btn {
| |
| padding: 10px 24px; | | padding: 10px 24px; |
| background: #f44336; | | background: #f44336; |
| 第20行: |
第18行: |
| cursor: pointer; | | cursor: pointer; |
| } | | } |
| .deadlink-scanner .scan-btn:hover { background: #d32f2f; }
| | .scan-btn:hover { background: #d32f2f; } |
| .deadlink-scanner .scan-btn:disabled { background: #ccc; cursor: not-allowed; }
| | .scan-btn:disabled { background: #ccc; cursor: not-allowed; } |
| .deadlink-progress { | | .deadlink-progress { |
| width: 100%; | | width: 100%; height: 6px; background: #e0e0e0; |
| height: 6px;
| | border-radius: 3px; margin: 15px 0; overflow: hidden; display: none; |
| background: #e0e0e0;
| |
| border-radius: 3px; | |
| margin: 15px 0;
| |
| overflow: hidden;
| |
| display: none;
| |
| } | | } |
| .deadlink-progress-bar { | | .deadlink-progress-bar { |
| height: 100%; | | height: 100%; background: #f44336; border-radius: 3px; |
| background: #f44336;
| | transition: width 0.3s; width: 0%; |
| border-radius: 3px;
| |
| transition: width 0.3s; | |
| width: 0%;
| |
| }
| |
| .deadlink-result {
| |
| margin-top: 10px;
| |
| font-size: 14px;
| |
| }
| |
| .deadlink-item {
| |
| padding: 6px 0;
| |
| border-bottom: 1px solid #eee;
| |
| } | | } |
| | .deadlink-result { margin-top: 10px; font-size: 14px; } |
| | .deadlink-item { padding: 6px 0; border-bottom: 1px solid #eee; } |
| .deadlink-item a { color: #f44336; } | | .deadlink-item a { color: #f44336; } |
| .deadlink-item .from-page { font-size: 12px; color: #888; } | | .deadlink-item .from-page { font-size: 12px; color: #888; } |
| 第59行: |
第43行: |
| <div class="deadlink-result" id="scan-result"></div> | | <div class="deadlink-result" id="scan-result"></div> |
| </div> | | </div> |
|
| |
| <script>
| |
| (function() {
| |
| var $startBtn = document.getElementById('scan-start');
| |
| var $progress = document.getElementById('scan-progress');
| |
| var $progressBar = document.getElementById('scan-progress-bar');
| |
| var $result = document.getElementById('scan-result');
| |
|
| |
| var allPages = [];
| |
| var deadLinks = [];
| |
| var checkedCount = 0;
| |
|
| |
| $startBtn.addEventListener('click', function() {
| |
| $startBtn.disabled = true;
| |
| $startBtn.textContent = '扫描中...';
| |
| $progress.style.display = 'block';
| |
| $progressBar.style.width = '0%';
| |
| $result.innerHTML = '';
| |
| deadLinks = [];
| |
| checkedCount = 0;
| |
|
| |
| // 第一步:获取所有页面
| |
| fetchAllPages();
| |
| });
| |
|
| |
| function fetchAllPages(apcontinue) {
| |
| var url = mw.util.wikiScript('api') + '?action=query&list=allpages&aplimit=500&format=json&origin=*';
| |
| if (apcontinue) url += '&apcontinue=' + encodeURIComponent(apcontinue);
| |
|
| |
| fetch(url)
| |
| .then(function(res) { return res.json(); })
| |
| .then(function(data) {
| |
| if (data.query && data.query.allpages) {
| |
| data.query.allpages.forEach(function(page) {
| |
| allPages.push(page.title);
| |
| });
| |
| if (data.continue && data.continue.apcontinue) {
| |
| fetchAllPages(data.continue.apcontinue);
| |
| } else {
| |
| // 全部页面获取完毕,开始检查死链
| |
| checkDeadLinks();
| |
| }
| |
| }
| |
| });
| |
| }
| |
|
| |
| function checkDeadLinks() {
| |
| if (allPages.length === 0) {
| |
| $result.innerHTML = '<p style="color:#999;">未找到任何页面。</p>';
| |
| $startBtn.disabled = false;
| |
| $startBtn.textContent = '开始扫描';
| |
| return;
| |
| }
| |
|
| |
| // 分批检查
| |
| var batchSize = 10;
| |
| var batchIndex = 0;
| |
|
| |
| function processBatch() {
| |
| var batch = allPages.slice(batchIndex, batchIndex + batchSize);
| |
| if (batch.length === 0) {
| |
| // 全部完成
| |
| showResults();
| |
| return;
| |
| }
| |
|
| |
| var promises = batch.map(function(page) {
| |
| return checkPageLinks(page);
| |
| });
| |
|
| |
| Promise.all(promises).then(function() {
| |
| batchIndex += batchSize;
| |
| checkedCount += batch.length;
| |
| var progress = Math.round((checkedCount / allPages.length) * 100);
| |
| $progressBar.style.width = progress + '%';
| |
| $startBtn.textContent = '扫描中... (' + checkedCount + '/' + allPages.length + ')';
| |
| setTimeout(processBatch, 200);
| |
| });
| |
| }
| |
|
| |
| processBatch();
| |
| }
| |
|
| |
| function checkPageLinks(page) {
| |
| return new Promise(function(resolve) {
| |
| var url = mw.util.wikiScript('api') + '?action=parse&page=' + encodeURIComponent(page) + '&prop=links&format=json&origin=*';
| |
|
| |
| fetch(url)
| |
| .then(function(res) { return res.json(); })
| |
| .then(function(data) {
| |
| if (data.parse && data.parse.links) {
| |
| var links = data.parse.links;
| |
| var internalLinks = links.filter(function(l) {
| |
| return l.ns === 0 && !l.title.match(/^文件:|^MediaWiki:|^模板:|^Widget:|^分类:|^Module:|^用户:|^Project:|^Special:|^Media:|^Topic:/);
| |
| });
| |
|
| |
| if (internalLinks.length === 0) {
| |
| resolve();
| |
| return;
| |
| }
| |
|
| |
| // 批量检查这些内部链接是否存在
| |
| var titles = internalLinks.map(function(l) { return l.title; }).join('|');
| |
| var checkUrl = mw.util.wikiScript('api') + '?action=query&titles=' + encodeURIComponent(titles) + '&format=json&origin=*';
| |
|
| |
| fetch(checkUrl)
| |
| .then(function(res) { return res.json(); })
| |
| .then(function(checkData) {
| |
| if (checkData.query && checkData.query.pages) {
| |
| var pages = checkData.query.pages;
| |
| for (var id in pages) {
| |
| if (parseInt(id) < 0) {
| |
| deadLinks.push({
| |
| from: page,
| |
| to: pages[id].title
| |
| });
| |
| }
| |
| }
| |
| }
| |
| resolve();
| |
| })
| |
| .catch(function() { resolve(); });
| |
| } else {
| |
| resolve();
| |
| }
| |
| })
| |
| .catch(function() { resolve(); });
| |
| });
| |
| }
| |
|
| |
| function showResults() {
| |
| $startBtn.disabled = false;
| |
| $startBtn.textContent = '开始扫描';
| |
| $progress.style.display = 'none';
| |
|
| |
| if (deadLinks.length === 0) {
| |
| $result.innerHTML = '<p style="color:#4CAF50;">✅ 未发现死链,所有链接正常!</p>';
| |
| return;
| |
| }
| |
|
| |
| // 按来源页面分组
| |
| var grouped = {};
| |
| deadLinks.forEach(function(item) {
| |
| if (!grouped[item.from]) grouped[item.from] = [];
| |
| grouped[item.from].push(item.to);
| |
| });
| |
|
| |
| var html = '<p style="color:#f44336;">⚠️ 发现 <b>' + deadLinks.length + '</b> 条死链,分布在 <b>' + Object.keys(grouped).length + '</b> 个页面:</p>';
| |
| for (var from in grouped) {
| |
| html += '<div class="deadlink-item">';
| |
| html += '<b><a href="/w/' + encodeURIComponent(from) + '" target="_blank">' + from + '</a></b>';
| |
| html += ' 中引用了不存在的:';
| |
| grouped[from].forEach(function(to) {
| |
| html += '<a href="/w/' + encodeURIComponent(to) + '?action=edit" target="_blank" style="margin-right:8px;">' + to + '</a>';
| |
| });
| |
| html += '</div>';
| |
| }
| |
| $result.innerHTML = html;
| |
| }
| |
| })();
| |
| </script>
| |
| </includeonly> | | </includeonly> |