feat: 增加图片合并功能

This commit is contained in:
R524809
2026-09-09 15:31:05 +08:00
parent bab02e1efd
commit 2a38778fa3
57 changed files with 684 additions and 50 deletions
+104 -4
View File
@@ -28,9 +28,11 @@ export function createAppShell(root, { onReload }) {
content.append(tabs, cards);
const chartNav = element('aside', 'sidebar chart-sidebar');
layout.append(companyNav, content, chartNav);
root.append(layout, reloadButton);
const cartBar = element('div', 'cart-bar');
cartBar.hidden = true;
root.append(layout, reloadButton, cartBar);
return { companyNav, tabs, cards, chartNav };
return { companyNav, tabs, cards, chartNav, cartBar };
}
export function renderCompanies(container, companies, activeCompanyId, onSelect) {
@@ -57,7 +59,7 @@ export function renderTabs(container, tabs, activeTabId, onSelect) {
});
}
export function renderCards(container, charts, activeViews, { onExport, onSelectView }) {
export function renderCards(container, charts, activeViews, { onExport, isCarted, onToggleCart, onSelectView }) {
container.replaceChildren();
if (!charts.length) {
container.append(element('p', 'empty-state', '该分类暂时没有图表。'));
@@ -92,7 +94,13 @@ export function renderCards(container, charts, activeViews, { onExport, onSelect
bar.append(switcher);
}
bar.append(button('导出 PNG', {
const cartButton = button(isCarted?.(chart.instanceId) ? '已加入 ✓' : '➕ 加入合并', {
onClick: () => onToggleCart?.(chart),
className: 'cart-button',
});
cartButton.dataset.cartId = chart.instanceId;
cartButton.dataset.inCart = String(Boolean(isCarted?.(chart.instanceId)));
bar.append(cartButton, button('导出 PNG', {
onClick: () => onExport(chart.instanceId, chart.filename || chart.title),
className: 'export-button',
}));
@@ -121,3 +129,95 @@ export function showChartError(chartId, message) {
if (!chartEl) return;
chartEl.replaceChildren(element('div', 'chart-error', message));
}
const CART_MODES = [
['pairs', '两两合并'],
['single', '合并为一张'],
];
export function renderCartBar(container, { items = [], mode, onModeChange, onMove, onRemove, onClear, onPreview }) {
container.replaceChildren();
if (!items.length) {
container.hidden = true;
return;
}
container.hidden = false;
container.append(element('span', 'cart-label', `合并队列 ${items.length}`));
const chips = element('div', 'cart-chips');
items.forEach((item, index) => {
const chip = element('div', 'cart-chip');
chip.title = item.title;
const up = button('↑', { onClick: () => onMove?.(item.instanceId, -1), className: 'cart-chip-button' });
up.disabled = index === 0;
const down = button('↓', { onClick: () => onMove?.(item.instanceId, 1), className: 'cart-chip-button' });
down.disabled = index === items.length - 1;
chip.append(
element('span', 'cart-chip-name', item.title),
up,
down,
button('✕', { onClick: () => onRemove?.(item.instanceId), className: 'cart-chip-button' }),
);
chips.append(chip);
});
container.append(chips);
container.append(element('span', 'cart-divider'));
const modes = element('div', 'cart-modes');
CART_MODES.forEach(([value, label]) => {
modes.append(button(label, {
active: mode === value,
onClick: () => onModeChange?.(value),
className: 'cart-mode-button',
}));
});
container.append(modes);
container.append(button('预览导出', { onClick: () => onPreview?.(), className: 'cart-export-button' }));
container.append(button('清空', { onClick: () => onClear?.(), className: 'cart-clear-button' }));
}
export function showMergePreview(entries, { notice = '', onDownload, onDownloadAll, onClose }) {
const overlay = element('div', 'modal-overlay');
const modal = element('div', 'modal');
const head = element('div', 'modal-head');
head.append(element('h2', 'modal-title', `合并预览 · ${entries.length} 张`));
if (notice) head.append(element('span', 'modal-notice', notice));
head.append(button('✕', { onClick: onClose, className: 'modal-close' }));
const body = element('div', 'modal-body');
entries.forEach((entry) => {
const item = element('div', 'preview-item');
entry.canvas.className = 'preview-canvas';
item.append(entry.canvas);
item.append(button(`⬇ 下载:${entry.filename}.png`, {
onClick: () => onDownload?.(entry),
className: 'export-button',
}));
body.append(item);
});
const foot = element('div', 'modal-foot');
foot.append(button('全部下载', { onClick: () => onDownloadAll?.(), className: 'primary-button' }));
foot.append(button('取消', { onClick: onClose, className: 'export-button' }));
modal.append(head, body, foot);
overlay.append(modal);
overlay.addEventListener('click', (event) => {
if (event.target === overlay) onClose?.();
});
document.addEventListener('keydown', onKeydown);
document.body.append(overlay);
function onKeydown(event) {
if (event.key === 'Escape') onClose?.();
}
return () => {
document.removeEventListener('keydown', onKeydown);
overlay.remove();
};
}