Refactor financial chart implementation
This commit is contained in:
+45
-25
@@ -22,21 +22,31 @@ function validateManifest(value) {
|
||||
throw new Error('manifest 缺少 companies');
|
||||
}
|
||||
|
||||
const chartIds = new Set();
|
||||
value.companies.forEach((company) => {
|
||||
if (!company.id || !Array.isArray(company.tabs)) throw new Error('manifest 中存在无效公司配置');
|
||||
if (!company.id || !Array.isArray(company.tabs) || !company.chartCatalog) {
|
||||
throw new Error('manifest 中存在无效公司配置');
|
||||
}
|
||||
Object.entries(company.chartCatalog).forEach(([chartId, chart]) => {
|
||||
if (!chartId || !getChartBuilder(chart.key)) {
|
||||
throw new Error(`图表 ${chartId || '(空)'} 引用了未注册 key:${chart.key}`);
|
||||
}
|
||||
if (Boolean(chart.csv) === Boolean(chart.views)) {
|
||||
throw new Error(`图表 ${chartId} 必须声明 csv 或 views 之一`);
|
||||
}
|
||||
if (chart.views && (!chart.defaultView || !chart.views[chart.defaultView])) {
|
||||
throw new Error(`图表 ${chartId} 的 defaultView 无效`);
|
||||
}
|
||||
});
|
||||
const tabIds = new Set();
|
||||
company.tabs.forEach((tab) => {
|
||||
if (!tab.id || tabIds.has(tab.id)) throw new Error(`公司 ${company.id} 的 tab id 重复或为空`);
|
||||
if (!Array.isArray(tab.chartRefs)) throw new Error(`公司 ${company.id} 的 ${tab.id} 缺少 chartRefs`);
|
||||
tabIds.add(tab.id);
|
||||
(tab.charts || []).forEach((chart) => {
|
||||
if (!chart.id || chartIds.has(chart.id)) throw new Error(`图表 id 重复或为空:${chart.id || '(空)'}`);
|
||||
if (!getChartBuilder(chart.key)) throw new Error(`图表 ${chart.id} 引用了未注册 key:${chart.key}`);
|
||||
if (Boolean(chart.csv) === Boolean(chart.views)) throw new Error(`图表 ${chart.id} 必须声明 csv 或 views 之一`);
|
||||
if (chart.views && (!chart.defaultView || !chart.views[chart.defaultView])) {
|
||||
throw new Error(`图表 ${chart.id} 的 defaultView 无效`);
|
||||
}
|
||||
chartIds.add(chart.id);
|
||||
const refs = new Set();
|
||||
tab.chartRefs.forEach((chartId) => {
|
||||
if (!company.chartCatalog[chartId]) throw new Error(`栏目 ${tab.id} 引用了不存在的图表:${chartId}`);
|
||||
if (refs.has(chartId)) throw new Error(`栏目 ${tab.id} 重复引用图表:${chartId}`);
|
||||
refs.add(chartId);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -50,6 +60,15 @@ function getTab(company, tabId) {
|
||||
return company.tabs.find((tab) => tab.id === tabId) || company.tabs[0];
|
||||
}
|
||||
|
||||
function chartsForTab(company, tab) {
|
||||
return tab.chartRefs.map((chartId) => ({
|
||||
...company.chartCatalog[chartId],
|
||||
id: chartId,
|
||||
instanceId: `${company.id}--${tab.id}--${chartId}`,
|
||||
viewKey: `${company.id}--${chartId}`,
|
||||
}));
|
||||
}
|
||||
|
||||
function parseHash() {
|
||||
const [path = '', query = ''] = window.location.hash.replace(/^#\/?/, '').split('?');
|
||||
const [companyId, tabId, chartId] = path.split('/').filter(Boolean).map(decodeURIComponent);
|
||||
@@ -60,11 +79,11 @@ function parseHash() {
|
||||
function normalizeState(nextState) {
|
||||
const company = getCompany(nextState.companyId);
|
||||
const tab = getTab(company, nextState.tabId);
|
||||
const chart = tab.charts.find((item) => item.id === nextState.chartId);
|
||||
const chart = chartsForTab(company, tab).find((item) => item.id === nextState.chartId);
|
||||
const view = chart?.views && chart.views[nextState.view]
|
||||
? nextState.view
|
||||
: chart?.defaultView;
|
||||
if (chart?.views && view) activeViews.set(chart.id, view);
|
||||
if (chart?.views && view) activeViews.set(chart.viewKey, view);
|
||||
return { companyId: company.id, tabId: tab.id, chartId: chart?.id, view };
|
||||
}
|
||||
|
||||
@@ -83,12 +102,12 @@ function disposeAll() {
|
||||
|
||||
function chartSource(chart) {
|
||||
if (!chart.views) return chart.csv;
|
||||
const view = activeViews.get(chart.id) || chart.defaultView;
|
||||
const view = activeViews.get(chart.viewKey) || chart.defaultView;
|
||||
return chart.views[view];
|
||||
}
|
||||
|
||||
async function renderChart(chart, version) {
|
||||
const el = document.getElementById(`chart-${chart.id}`);
|
||||
const el = document.getElementById(`chart-${chart.instanceId}`);
|
||||
if (!el) return;
|
||||
const source = chartSource(chart);
|
||||
|
||||
@@ -97,11 +116,11 @@ async function renderChart(chart, version) {
|
||||
if (version !== renderVersion || !document.body.contains(el)) return;
|
||||
|
||||
const instance = echarts.init(el, null, { renderer: 'canvas' });
|
||||
instances.set(chart.id, instance);
|
||||
instances.set(chart.instanceId, instance);
|
||||
const { width, height } = el.getBoundingClientRect();
|
||||
instance.setOption(getChartBuilder(chart.key)(rows, { width, height }), true);
|
||||
} catch (error) {
|
||||
if (version === renderVersion) showChartError(chart.id, error.message);
|
||||
if (version === renderVersion) showChartError(chart.instanceId, error.message);
|
||||
console.error(`渲染图表失败:${chart.id}`, error);
|
||||
}
|
||||
}
|
||||
@@ -125,16 +144,17 @@ function updateHash(nextState) {
|
||||
window.location.hash = hash;
|
||||
}
|
||||
|
||||
function selectAnchor(chartId) {
|
||||
state = { ...state, chartId, view: activeViews.get(chartId) };
|
||||
function selectAnchor(chart) {
|
||||
state = { ...state, chartId: chart.id, view: activeViews.get(chart.viewKey) };
|
||||
history.replaceState(null, '', serializeHash(state));
|
||||
document.getElementById(`card-${chartId}`)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
document.getElementById(`card-${chart.instanceId}`)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
|
||||
function renderCurrent() {
|
||||
state = normalizeState(state);
|
||||
const company = getCompany(state.companyId);
|
||||
const tab = getTab(company, state.tabId);
|
||||
const charts = chartsForTab(company, tab);
|
||||
const version = ++renderVersion;
|
||||
disposeAll();
|
||||
|
||||
@@ -144,15 +164,15 @@ function renderCurrent() {
|
||||
renderTabs(shell.tabs, company.tabs, tab.id, (tabId) => {
|
||||
updateHash({ companyId: company.id, tabId });
|
||||
});
|
||||
renderCards(shell.cards, tab.charts || [], activeViews, {
|
||||
renderCards(shell.cards, charts, activeViews, {
|
||||
onExport: exportPNG,
|
||||
onSelectView: (chartId, view) => {
|
||||
activeViews.set(chartId, view);
|
||||
updateHash({ companyId: company.id, tabId: tab.id, chartId, view });
|
||||
onSelectView: (chart, view) => {
|
||||
activeViews.set(chart.viewKey, view);
|
||||
updateHash({ companyId: company.id, tabId: tab.id, chartId: chart.id, view });
|
||||
},
|
||||
});
|
||||
renderAnchors(shell.chartNav, tab.charts || [], selectAnchor);
|
||||
Promise.all((tab.charts || []).map((chart) => renderChart(chart, version)));
|
||||
renderAnchors(shell.chartNav, charts, selectAnchor);
|
||||
Promise.all(charts.map((chart) => renderChart(chart, version)));
|
||||
}
|
||||
|
||||
async function start() {
|
||||
|
||||
Reference in New Issue
Block a user