|
|
| 第1行: |
第1行: |
| <includeonly> | | <includeonly> |
| <style> | | <style> |
| .image-cleaner-wrap { | | .unused-scanner { |
| margin: 15px 0; | | margin: 15px 0; |
| | padding: 15px; |
| | background: #f9f9f9; |
| | border-radius: 12px; |
| | border: 1px solid #e0e0e0; |
| } | | } |
| .image-cleaner-btn { | | .unused-scanner button { |
| padding: 10px 24px; | | padding: 8px 20px; |
| background: #f44336; | | background: #f44336; |
| color: #fff; | | color: #fff; |
| border: none; | | border: none; |
| border-radius: 8px; | | border-radius: 6px; |
| cursor: pointer; | | cursor: pointer; |
| font-size: 14px; | | font-size: 14px; |
| margin: 5px; | | margin: 5px; |
| } | | } |
| .image-cleaner-btn:hover { background: #d32f2f; } | | .unused-scanner button:hover { background: #d32f2f; } |
| .image-cleaner-btn:disabled { background: #ccc; cursor: not-allowed; } | | .unused-scanner button:disabled { background: #ccc; cursor: not-allowed; } |
| .image-cleaner-result { | | .unused-item { |
| margin-top: 10px;
| |
| max-height: 500px;
| |
| overflow-y: auto;
| |
| }
| |
| .image-item {
| |
| display: flex; | | display: flex; |
| align-items: center; | | align-items: center; |
| padding: 8px; | | justify-content: space-between; |
| | padding: 8px 0; |
| border-bottom: 1px solid #eee; | | border-bottom: 1px solid #eee; |
| font-size: 13px;
| |
| justify-content: space-between;
| |
| } | | } |
| .image-item img { | | .unused-item img { |
| width: 48px; | | width: 40px; |
| height: 48px; | | height: 40px; |
| object-fit: contain; | | object-fit: contain; |
| margin-right: 10px; | | margin-right: 10px; |
| border-radius: 4px;
| |
| } | | } |
| .image-item .file-info { | | .unused-item .file-info { |
| flex: 1; | | flex: 1; |
| | font-size: 13px; |
| } | | } |
| .image-item .file-name { | | .unused-item .del-btn { |
| font-weight: bold;
| |
| }
| |
| .image-item .file-usage {
| |
| font-size: 11px;
| |
| color: #888;
| |
| }
| |
| .image-item .delete-btn {
| |
| padding: 4px 12px; | | padding: 4px 12px; |
| background: #f44336; | | background: #f44336; |
| 第55行: |
第46行: |
| font-size: 12px; | | font-size: 12px; |
| } | | } |
| .image-item .delete-btn:hover { background: #d32f2f; } | | .unused-item .del-btn:hover { background: #d32f2f; } |
| .image-item .delete-btn:disabled { background: #ccc; cursor: not-allowed; } | | .unused-item .del-btn:disabled { background: #ccc; } |
| .image-progress {
| |
| margin: 10px 0;
| |
| font-size: 13px;
| |
| color: #888;
| |
| } | |
| </style> | | </style> |
|
| |
|
| <div class="image-cleaner-wrap"> | | <div class="unused-scanner"> |
| <button class="image-cleaner-btn" id="img-scan-btn">🔍 扫描未使用图片</button> | | <h3>🗑️ 未使用图片扫描器</h3> |
| <button class="image-cleaner-btn" id="img-delete-all-btn" style="display:none;">🗑️ 一键清除全部</button> | | <p style="color:#888;font-size:13px;">扫描全站未被引用的图片,可一键删除</p> |
| <div class="image-progress" id="img-progress"></div> | | <button id="unused-scan-btn">开始扫描</button> |
| <div class="image-cleaner-result" id="img-result"></div>
| | <button id="unused-delete-all-btn" style="display:none;">全部删除</button> |
| | <div id="unused-result" style="margin-top:10px;"></div> |
| </div> | | </div> |
|
| |
| <script>
| |
| (function() {
| |
| var unusedImages = [];
| |
| var allImages = [];
| |
| var totalChecked = 0;
| |
| var batchSize = 10;
| |
|
| |
| function fetchAllImages(continueFrom) {
| |
| $('#img-progress').text('正在获取图片列表...');
| |
| $.getJSON(mw.util.wikiScript('api'), {
| |
| action: 'query',
| |
| list: 'allimages',
| |
| ailimit: 50,
| |
| aifrom: continueFrom || undefined,
| |
| format: 'json'
| |
| }, function(data) {
| |
| if (data.query && data.query.allimages) {
| |
| allImages = allImages.concat(data.query.allimages);
| |
| if (data.continue && data.continue.aifrom) {
| |
| fetchAllImages(data.continue.aifrom);
| |
| } else {
| |
| checkUsage();
| |
| }
| |
| }
| |
| });
| |
| }
| |
|
| |
| function checkUsage() {
| |
| unusedImages = [];
| |
| totalChecked = 0;
| |
| var total = allImages.length;
| |
| $('#img-progress').text('正在检查引用... 0/' + total);
| |
| $('#img-scan-btn').prop('disabled', true);
| |
|
| |
| function processBatch(index) {
| |
| var batch = allImages.slice(index, index + batchSize);
| |
| if (batch.length === 0) {
| |
| showResults();
| |
| return;
| |
| }
| |
|
| |
| var titles = batch.map(function(img) { return 'File:' + img.name; }).join('|');
| |
| $.getJSON(mw.util.wikiScript('api'), {
| |
| action: 'query',
| |
| list: 'imageusage',
| |
| iutitles: titles,
| |
| iulimit: 1,
| |
| format: 'json'
| |
| }, function(data) {
| |
| var usedFiles = {};
| |
| if (data.query && data.query.imageusage) {
| |
| data.query.imageusage.forEach(function(iu) {
| |
| usedFiles[iu.title.replace('File:', '')] = true;
| |
| });
| |
| }
| |
| batch.forEach(function(img) {
| |
| totalChecked++;
| |
| if (!usedFiles[img.name]) {
| |
| unusedImages.push(img);
| |
| }
| |
| });
| |
| $('#img-progress').text('正在检查引用... ' + totalChecked + '/' + total);
| |
| setTimeout(function() { processBatch(index + batchSize); }, 200);
| |
| });
| |
| }
| |
|
| |
| processBatch(0);
| |
| }
| |
|
| |
| function showResults() {
| |
| $('#img-scan-btn').prop('disabled', false);
| |
| $('#img-progress').text('共 ' + allImages.length + ' 张图片,' + unusedImages.length + ' 张未使用');
| |
| var $result = $('#img-result').empty();
| |
|
| |
| if (unusedImages.length === 0) {
| |
| $result.html('<p style="color:#4CAF50;">✅ 所有图片都被引用,没有未使用的图片!</p>');
| |
| $('#img-delete-all-btn').hide();
| |
| return;
| |
| }
| |
|
| |
| $('#img-delete-all-btn').show();
| |
|
| |
| unusedImages.forEach(function(img) {
| |
| var $item = $('<div class="image-item"></div>');
| |
| $item.append('<img src="' + img.url + '" alt="' + img.name + '">');
| |
| $item.append('<div class="file-info"><div class="file-name">' + img.name + '</div><div class="file-usage">未使用</div></div>');
| |
| $item.append('<button class="delete-btn" data-name="' + img.name + '">删除</button>');
| |
| $result.append($item);
| |
| });
| |
| }
| |
|
| |
| function deleteImage(name, $btn) {
| |
| if (!confirm('确定删除 ' + name + ' 吗?此操作不可恢复!')) return;
| |
| $btn.prop('disabled', true).text('删除中...');
| |
|
| |
| var api = new mw.Api();
| |
| api.postWithEditToken({
| |
| action: 'delete',
| |
| title: 'File:' + name,
| |
| reason: '清理未使用图片'
| |
| }).then(function() {
| |
| $btn.text('✅ 已删除').css('background', '#4CAF50');
| |
| $btn.closest('.image-item').fadeOut(1000, function() { $(this).remove(); });
| |
| }).fail(function() {
| |
| $btn.prop('disabled', false).text('❌ 失败');
| |
| });
| |
| }
| |
|
| |
| $('#img-scan-btn').click(fetchAllImages);
| |
|
| |
| $('#img-delete-all-btn').click(function() {
| |
| if (!confirm('确定删除全部 ' + unusedImages.length + ' 张未使用图片吗?此操作不可恢复!')) return;
| |
| var $btn = $(this);
| |
| $btn.prop('disabled', true).text('删除中...');
| |
|
| |
| var api = new mw.Api();
| |
| var done = 0;
| |
| var total = unusedImages.length;
| |
| var failed = [];
| |
|
| |
| unusedImages.forEach(function(img) {
| |
| api.postWithEditToken({
| |
| action: 'delete',
| |
| title: 'File:' + img.name,
| |
| reason: '批量清理未使用图片'
| |
| }).then(function() {
| |
| done++;
| |
| $btn.text('删除中... ' + done + '/' + total);
| |
| if (done >= total) finishDelete($btn, failed);
| |
| }).fail(function() {
| |
| done++;
| |
| failed.push(img.name);
| |
| if (done >= total) finishDelete($btn, failed);
| |
| });
| |
| });
| |
| });
| |
|
| |
| function finishDelete($btn, failed) {
| |
| if (failed.length === 0) {
| |
| $btn.text('✅ 全部删除完成').css('background', '#4CAF50');
| |
| $('#img-result').html('<p style="color:#4CAF50;">✅ 所有未使用图片已删除!</p>');
| |
| } else {
| |
| $btn.text('⚠️ ' + failed.length + ' 张删除失败').prop('disabled', false);
| |
| }
| |
| $('#img-progress').text('');
| |
| }
| |
|
| |
| $('#img-result').on('click', '.delete-btn', function() {
| |
| var name = $(this).data('name');
| |
| deleteImage(name, $(this));
| |
| });
| |
| })();
| |
| </script>
| |
| </includeonly> | | </includeonly> |