client / static /js /batch.js
P01yH3dr0n's picture
launch
774fe36
Raw
History Blame Contribute Delete
7.42 kB
/* ===== Batch Selection & Actions ===== */
window.NAIBatch = {
/**
* Create a batch controller for a gallery page.
* @param {object} opts
* - gridSel: grid container selector
* - toolbarSel: toolbar container selector
* - getItems: () => [{id, ...}] all items
* - renderCard: (item, isSelected, batchMode) => HTMLElement
* - onDelete: async (ids) => void
* - onDownload: async (ids) => void (optional, we provide default ZIP)
* - getImageB64: async (id) => {b64, filename}
* - getName: (item) => string filename
*/
create(opts) {
const ctrl = {
opts,
batchMode: false,
selected: new Set(),
toggle() {
this.batchMode = !this.batchMode;
if (!this.batchMode) this.selected.clear();
this.updateToolbar();
this.refreshGrid();
},
selectAll() {
const items = this.opts.getItems();
items.forEach(item => this.selected.add(item.id ?? item.path));
this.updateToolbar();
this.refreshGrid();
},
selectNone() {
this.selected.clear();
this.updateToolbar();
this.refreshGrid();
},
toggleItem(id) {
if (this.selected.has(id)) {
this.selected.delete(id);
} else {
this.selected.add(id);
}
this.updateToolbar();
// Update just the card
const card = document.querySelector(`[data-batch-id="${id}"]`);
if (card) {
card.classList.toggle('batch-selected', this.selected.has(id));
const cb = card.querySelector('.batch-checkbox');
if (cb) cb.checked = this.selected.has(id);
}
},
// Shift-click range select
_lastClickedId: null,
rangeSelect(id) {
const items = this.opts.getItems();
const ids = items.map(i => i.id ?? i.path);
const curIdx = ids.indexOf(id);
const lastIdx = ids.indexOf(this._lastClickedId);
if (lastIdx === -1 || curIdx === -1) {
this.toggleItem(id);
this._lastClickedId = id;
return;
}
const start = Math.min(curIdx, lastIdx);
const end = Math.max(curIdx, lastIdx);
for (let i = start; i <= end; i++) {
this.selected.add(ids[i]);
}
this._lastClickedId = id;
this.updateToolbar();
this.refreshGrid();
},
updateToolbar() {
const toolbar = document.querySelector(this.opts.toolbarSel);
if (!toolbar) return;
if (!this.batchMode) {
toolbar.style.display = 'none';
return;
}
toolbar.style.display = 'flex';
const count = this.selected.size;
const total = this.opts.getItems().length;
const countEl = toolbar.querySelector('.batch-count');
if (countEl) countEl.textContent = `已选 ${count} / ${total}`;
const allCb = toolbar.querySelector('.batch-select-all');
if (allCb) {
allCb.checked = count > 0 && count === total;
allCb.indeterminate = count > 0 && count < total;
}
// Disable actions when nothing selected
toolbar.querySelectorAll('.batch-action-btn').forEach(btn => {
btn.disabled = count === 0;
});
},
refreshGrid() {
// Re-render grid via the host page
if (this.opts.onRefreshGrid) this.opts.onRefreshGrid();
},
async batchDelete() {
const count = this.selected.size;
if (count === 0) return;
if (!confirm(`确定删除选中的 ${count} 张图片?`)) return;
const ids = [...this.selected];
try {
await this.opts.onDelete(ids);
this.selected.clear();
this.updateToolbar();
} catch(e) {
console.error('Batch delete failed:', e);
alert('部分删除失败,请重试');
}
},
async batchDownload() {
const count = this.selected.size;
if (count === 0) return;
const ids = [...this.selected];
const toolbar = document.querySelector(this.opts.toolbarSel);
const progressEl = toolbar?.querySelector('.batch-progress');
// Use JSZip to create ZIP in browser
if (typeof JSZip === 'undefined') {
alert('JSZip 未加载,无法打包下载');
return;
}
const zip = new JSZip();
let loaded = 0;
if (progressEl) {
progressEl.style.display = '';
progressEl.textContent = `打包中 0/${count}`;
}
for (const id of ids) {
try {
const result = await this.opts.getImageB64(id);
if (result && result.b64) {
zip.file(result.filename, result.b64, { base64: true });
}
} catch(e) {
console.error('Failed to fetch image:', id, e);
}
loaded++;
if (progressEl) progressEl.textContent = `打包中 ${loaded}/${count}`;
}
if (progressEl) progressEl.textContent = '生成ZIP...';
try {
const blob = await zip.generateAsync({
type: 'blob',
compression: 'STORE',
}, (meta) => {
if (progressEl) progressEl.textContent = `压缩 ${Math.round(meta.percent)}%`;
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
const now = new Date();
a.download = `nai_batch_${now.getFullYear()}${String(now.getMonth()+1).padStart(2,'0')}${String(now.getDate()).padStart(2,'0')}.zip`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} catch(e) {
console.error('ZIP generation failed:', e);
alert('ZIP生成失败');
}
if (progressEl) {
progressEl.textContent = '完成';
setTimeout(() => { progressEl.style.display = 'none'; }, 2000);
}
},
};
return ctrl;
},
};