Files

124 lines
4.2 KiB
JavaScript

function element(tag, className, text) {
const el = document.createElement(tag);
if (className) el.className = className;
if (text !== undefined) el.textContent = text;
return el;
}
function button(label, { active = false, onClick, className = '' } = {}) {
const el = element('button', className, label);
el.type = 'button';
el.dataset.active = String(active);
if (onClick) el.addEventListener('click', onClick);
return el;
}
export function createAppShell(root, { onReload }) {
root.replaceChildren();
const reloadButton = button('↻', { onClick: onReload, className: 'reload-button' });
reloadButton.title = '重新加载数据';
reloadButton.setAttribute('aria-label', '重新加载数据');
const layout = element('div', 'app-layout');
const companyNav = element('aside', 'sidebar company-sidebar');
const content = element('main', 'content');
const tabs = element('nav', 'tabs');
const cards = element('div', 'cards');
content.append(tabs, cards);
const chartNav = element('aside', 'sidebar chart-sidebar');
layout.append(companyNav, content, chartNav);
root.append(layout, reloadButton);
return { companyNav, tabs, cards, chartNav };
}
export function renderCompanies(container, companies, activeCompanyId, onSelect) {
container.replaceChildren(element('h2', 'sidebar-title', '公司'));
const list = element('div', 'company-list');
companies.forEach((company) => {
list.append(button(company.name, {
active: company.id === activeCompanyId,
onClick: () => onSelect(company.id),
className: 'company-button',
}));
});
container.append(list);
}
export function renderTabs(container, tabs, activeTabId, onSelect) {
container.replaceChildren();
tabs.forEach((tab) => {
container.append(button(tab.name, {
active: tab.id === activeTabId,
onClick: () => onSelect(tab.id),
className: 'tab-button',
}));
});
}
export function renderCards(container, charts, activeViews, { onExport, onSelectView }) {
container.replaceChildren();
if (!charts.length) {
container.append(element('p', 'empty-state', '该分类暂时没有图表。'));
return;
}
charts.forEach((chart) => {
const card = element('section', 'card');
card.id = `card-${chart.instanceId}`;
const bar = element('div', 'card-bar');
const heading = element('div', 'card-heading');
heading.append(element('h2', 'card-title', chart.title));
const viewNames = Object.keys(chart.views || {});
const activeView = activeViews.get(chart.viewKey) || chart.defaultView || viewNames[0];
const source = chart.views ? chart.views[activeView] : chart.csv;
heading.append(element('span', 'src', source));
bar.append(heading);
if (viewNames.length > 1) {
const switcher = element('div', 'view-switcher');
const labels = { all: '全部', annual: '年度', halfyear: '半年', quarter: '季度' };
viewNames.forEach((view) => {
const viewButton = button(labels[view] || view, {
active: view === activeView,
onClick: () => onSelectView(chart, view),
className: 'view-button',
});
viewButton.dataset.view = view;
switcher.append(viewButton);
});
bar.append(switcher);
}
bar.append(button('导出 PNG', {
onClick: () => onExport(chart.instanceId, chart.filename || chart.title),
className: 'export-button',
}));
const chartEl = element('div', 'chart');
chartEl.id = `chart-${chart.instanceId}`;
chartEl.style.height = `${chart.height || 680}px`;
card.append(bar, chartEl);
container.append(card);
});
}
export function renderAnchors(container, charts, onSelect) {
container.replaceChildren(element('h2', 'sidebar-title', '当前图表'));
const list = element('div', 'anchor-list');
charts.forEach((chart) => {
container.append(button(chart.title, {
onClick: () => onSelect(chart.id),
className: 'anchor-button',
}));
});
if (!charts.length) container.append(element('p', 'sidebar-empty', '暂无图表'));
}
export function showChartError(chartId, message) {
const chartEl = document.getElementById(`chart-${chartId}`);
if (!chartEl) return;
chartEl.replaceChildren(element('div', 'chart-error', message));
}