Files
smart-fin-chart/js/ui.js
T
2026-08-28 09:30:13 +08:00

123 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 header = element('header', 'app-header');
const brand = element('div', 'app-brand');
brand.append(element('h1', '', '财报图表'), element('p', '', '选择公司与分析维度,数据更新后可重新加载。'));
header.append(brand, button('重新加载数据', { onClick: onReload, className: 'reload-button' }));
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(header, layout);
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.id}`;
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.id) || 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 = { annual: '年度', halfyear: '半年', quarter: '季度' };
viewNames.forEach((view) => {
switcher.append(button(labels[view] || view, {
active: view === activeView,
onClick: () => onSelectView(chart.id, view),
className: 'view-button',
}));
});
bar.append(switcher);
}
bar.append(button('导出 PNG', {
onClick: () => onExport(chart.id, chart.filename || chart.title),
className: 'export-button',
}));
const chartEl = element('div', 'chart');
chartEl.id = `chart-${chart.id}`;
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));
}