This commit is contained in:
wintsa 2026-06-01 14:48:17 +08:00
parent cfb6afcdce
commit 178a3e656f
4 changed files with 272 additions and 270 deletions

View File

@ -6,11 +6,13 @@ import {
ModuleRegistry as AgGridModuleRegistry,
type ColDef,
type ColGroupDef,
type GridApi,
type GridReadyEvent,
type ValueFormatterParams,
} from 'ag-grid-community';
import type { AgCartesianChartOptions } from 'ag-charts-community';
import { ModuleRegistry } from 'ag-charts-community';
import { Building2, Construction, LayoutGrid, Library, LocateFixed, MapPinned, SquareFunction, Waypoints } from 'lucide-react';
import { Building2, Construction, LayoutGrid, Library, LocateFixed, MapPinned, PanelRightClose, PanelRightOpen, SquareFunction, Waypoints } from 'lucide-react';
import {
AnnotationsModule,
ContextMenuModule,
@ -140,6 +142,7 @@ const defaultTemplateFilterNode = {
filterKey: 'templateLibrary',
label: '默认模板',
} as const;
const overallSummaryKey = 'summary';
// const mockGeoLocationPayload = {
// checkStrictly: true,
@ -196,6 +199,7 @@ type ApiBuildingFunctionStat = {
type ApiBuildingFunctionStatBatchItem = {
key?: string;
data?: ApiBuildingFunctionStat[];
summary?: ApiBuildingFunctionStat | null;
};
type ChartDatum = {
groupName: string;
@ -237,6 +241,7 @@ type SelectedFilterNode = {
type PivotGridRow = {
year: string;
name: string;
summary: boolean;
lowValue: number | null;
centerValue: number | null;
highValue: number | null;
@ -717,6 +722,7 @@ function renderFilterTreeNodes(
function App() {
const workspaceRef = useRef<HTMLElement>(null);
const chartFrameRef = useRef<HTMLDivElement>(null);
const pivotGridApiRef = useRef<GridApi<PivotGridRow> | null>(null);
const treeInitialLoadStartedRef = useRef<Record<ContentKey, boolean>>({
geoLocation: false,
facilityType: false,
@ -738,6 +744,8 @@ function App() {
const [metricKey, setMetricKey] = useState<MetricKey>('cost');
const [groupKey, setGroupKey] = useState<GroupKey>('year');
const [chartViewKey, setChartViewKey] = useState<ChartViewKey>('trend');
const [workspaceFullscreen, setWorkspaceFullscreen] = useState(false);
const [rightPanelCollapsed, setRightPanelCollapsed] = useState(false);
const [statisticMenuOpen, setStatisticMenuOpen] = useState(false);
const [metricMenuOpen, setMetricMenuOpen] = useState(false);
const [activeContentKey, setActiveContentKey] = useState<ContentKey>('geoLocation');
@ -764,6 +772,7 @@ function App() {
});
const [selectedContentNodes, setSelectedContentNodes] = useState<SelectedContentNode[]>([]);
const [chartDataBySelection, setChartDataBySelection] = useState<Record<string, ChartDatum[]>>({});
const [chartSummaryBySelection, setChartSummaryBySelection] = useState<Record<string, ChartDatum>>({});
const [chartQueryVersion, setChartQueryVersion] = useState(0);
const [loading, setLoading] = useState(false);
const [loadError, setLoadError] = useState<string | null>(null);
@ -861,6 +870,8 @@ function App() {
const currentViewShortLabel = chartViewKey === 'pivot' ? '表' : '趋';
const pivotToggleActionLabel = chartViewKey === 'pivot' ? '切换到趋势图' : '切换到表格';
const pivotToggleTitle = `${chartViewKey === 'pivot' ? '当前表格' : '当前趋势图'}${pivotToggleActionLabel}`;
const fullscreenToggleLabel = workspaceFullscreen ? '退出全屏' : '全屏';
const rightPanelToggleLabel = rightPanelCollapsed ? '展开选择区' : '收起选择区';
const activeContent = contentOptions.find((option) => option.key === activeContentKey) ?? contentOptions[0];
const activeTree = treeByContent[activeContentKey];
const activeFilter = filterOptions.find((option) => option.key === filterModalKey);
@ -912,6 +923,7 @@ function App() {
return rows.map((datum) => ({
year: datum.groupName,
name: node.label,
summary: false,
lowValue: datum.thresholdLowValue,
centerValue: datum.thresholdCenterValue,
highValue: datum.thresholdHighValue,
@ -927,6 +939,32 @@ function App() {
}),
[chartDataBySelection, selectedContentNodes],
);
const pivotGridPinnedBottomRowData = useMemo<PivotGridRow[]>(
() => {
const datum = chartSummaryBySelection[overallSummaryKey]
?? selectedContentNodes
.map((node) => chartSummaryBySelection[getSelectionKey(node.contentKey, node.id)])
.find(Boolean);
if (!datum) return [];
return [{
year: '',
name: '统计',
summary: true,
lowValue: datum.thresholdLowValue,
centerValue: datum.thresholdCenterValue,
highValue: datum.thresholdHighValue,
maxValue: datum.maxValue,
minValue: datum.minValue,
avgValue: datum.avgValue,
medianValue: datum.medianValue,
standardDeviation: datum.standardDeviation,
interquartileRange: datum.interquartileRange,
coefficientOfVariation: datum.coefficientOfVariation,
dataCount: datum.dataCount,
}];
},
[chartSummaryBySelection, selectedContentNodes],
);
const pivotGridColumnDefs = useMemo<(ColDef<PivotGridRow> | ColGroupDef<PivotGridRow>)[]>(
() => {
const valueFormatter = ({ value }: ValueFormatterParams<PivotGridRow, number | null>) => (
@ -937,14 +975,15 @@ function App() {
{
field: 'year',
headerName: '年度',
minWidth: 68,
width: 74,
minWidth: 58,
width: 64,
},
{
field: 'name',
headerName: '名称',
flex: 1,
minWidth: 108,
minWidth: 86,
tooltipField: 'name',
},
{
headerName: '基准阀值',
@ -953,21 +992,21 @@ function App() {
field: 'lowValue',
headerName: '低值',
type: 'numericColumn',
minWidth: 78,
minWidth: 64,
valueFormatter,
},
{
field: 'centerValue',
headerName: '中心值',
type: 'numericColumn',
minWidth: 78,
minWidth: 68,
valueFormatter,
},
{
field: 'highValue',
headerName: '高值',
type: 'numericColumn',
minWidth: 78,
minWidth: 64,
valueFormatter,
},
],
@ -979,49 +1018,49 @@ function App() {
field: 'maxValue',
headerName: '最大值',
type: 'numericColumn',
minWidth: 78,
minWidth: 68,
valueFormatter,
},
{
field: 'minValue',
headerName: '最小值',
type: 'numericColumn',
minWidth: 78,
minWidth: 68,
valueFormatter,
},
{
field: 'avgValue',
headerName: '平均值',
type: 'numericColumn',
minWidth: 78,
minWidth: 68,
valueFormatter,
},
{
field: 'medianValue',
headerName: '中位数',
type: 'numericColumn',
minWidth: 78,
minWidth: 68,
valueFormatter,
},
{
field: 'standardDeviation',
headerName: '标准差',
type: 'numericColumn',
minWidth: 78,
minWidth: 68,
valueFormatter,
},
{
field: 'interquartileRange',
headerName: '四分位距',
type: 'numericColumn',
minWidth: 88,
minWidth: 76,
valueFormatter,
},
{
field: 'coefficientOfVariation',
headerName: '变异系数',
type: 'numericColumn',
minWidth: 88,
minWidth: 76,
valueFormatter: ({ value }: ValueFormatterParams<PivotGridRow, number | null>) => (
value == null ? '' : formatNumber(Number(value), 4)
),
@ -1032,6 +1071,17 @@ function App() {
},
[requestMetricKey],
);
const fitPivotGridColumns = useCallback(() => {
window.requestAnimationFrame(() => {
pivotGridApiRef.current?.sizeColumnsToFit({
defaultMinWidth: 58,
columnLimits: [
{ key: 'year', minWidth: 58, maxWidth: 72 },
{ key: 'name', minWidth: 86, maxWidth: 220 },
],
});
});
}, []);
const selectedNodeKeys = useMemo(
() => new Set(selectedContentNodes.map((node) => getSelectionKey(node.contentKey, node.id))),
[selectedContentNodes],
@ -1306,6 +1356,10 @@ function App() {
const { [selectionKey]: _removed, ...rest } = data;
return rest;
});
setChartSummaryBySelection((data) => {
const { [selectionKey]: _removed, ...rest } = data;
return rest;
});
return current.filter((_, index) => index !== existingIndex);
}
@ -1319,6 +1373,7 @@ function App() {
setActiveContentKey(nextContentKey);
setSelectedContentNodes([]);
setChartDataBySelection({});
setChartSummaryBySelection({});
setLoadError(null);
setLoadingHint('');
setLoading(false);
@ -1546,6 +1601,7 @@ function App() {
resetIndicatorTreeState();
}
setChartDataBySelection({});
setChartSummaryBySelection({});
setLoadError(null);
if (selectedContentNodes.length > 0) {
setLoadingHint('正在按筛选条件重新计算');
@ -1578,6 +1634,7 @@ function App() {
resetIndicatorTreeState();
}
setChartDataBySelection({});
setChartSummaryBySelection({});
setLoadError(null);
if (selectedContentNodes.length > 0) {
setLoadingHint('正在按筛选条件重新计算');
@ -1589,6 +1646,7 @@ function App() {
const updateMetricKey = (nextMetricKey: MetricKey) => {
setMetricKey(nextMetricKey);
setChartDataBySelection({});
setChartSummaryBySelection({});
setLoadError(null);
setLoadingHint('正在重新加载数据');
setLoading(true);
@ -1601,12 +1659,33 @@ function App() {
setStatisticMenuOpen(false);
}, []);
const toggleWorkspaceFullscreen = useCallback(() => {
const fullscreenTarget = workspaceRef.current;
if (!fullscreenTarget) return;
if (document.fullscreenElement === fullscreenTarget) {
void document.exitFullscreen();
} else {
void fullscreenTarget.requestFullscreen();
}
}, []);
const openGridFilterModal = (filterKey: 'templateLibrary' | 'indicatorTree') => {
setMetricMenuOpen(false);
setStatisticMenuOpen(false);
openFilterModal(filterKey);
};
useEffect(() => {
const handleFullscreenChange = () => {
setWorkspaceFullscreen(document.fullscreenElement === workspaceRef.current);
};
handleFullscreenChange();
document.addEventListener('fullscreenchange', handleFullscreenChange);
return () => {
document.removeEventListener('fullscreenchange', handleFullscreenChange);
};
}, []);
useEffect(() => {
if (!contentTreeConfigs[activeContentKey]) return;
if (treeByContent[activeContentKey].length > 0 || treeInitialLoadStartedRef.current[activeContentKey]) return;
@ -1645,10 +1724,6 @@ function App() {
async function loadStats() {
if (selectedContentNodes.length === 0) {
setChartDataBySelection({});
setLoading(false);
setLoadingHint('');
setLoadError(null);
return;
}
setLoading(true);
@ -1656,7 +1731,7 @@ function App() {
setLoadError(null);
try {
const hasAnyMissingNode = selectedContentNodes.some((node) => !chartDataBySelection[getSelectionKey(node.contentKey, node.id)]);
if (!hasAnyMissingNode) {
if (selectedContentNodes.length > 0 && !hasAnyMissingNode && chartSummaryBySelection[overallSummaryKey]) {
setLoading(false);
setLoadingHint('');
return;
@ -1687,10 +1762,19 @@ function App() {
const results = (payload.data ?? [])
.filter((item) => item.key)
.map((item) => [item.key as string, (item.data ?? []).map(normalizeStat).slice(0, 36)] as const);
const summaries = (payload.data ?? [])
.filter((item) => item.key && item.summary)
.map((item) => [item.key as string, normalizeStat(item.summary as ApiBuildingFunctionStat)] as const);
const overallSummary = (payload.data ?? []).find((item) => item.summary)?.summary;
setChartDataBySelection((current) => ({
...current,
...Object.fromEntries(results),
}));
setChartSummaryBySelection((current) => ({
...current,
...Object.fromEntries(summaries),
...(overallSummary ? { [overallSummaryKey]: normalizeStat(overallSummary) } : {}),
}));
} catch (error) {
if (controller.signal.aborted) return;
setLoadError(error instanceof Error ? error.message : '接口请求失败');
@ -1783,20 +1867,12 @@ function App() {
}
};
const toggleFullscreen = () => {
if (document.fullscreenElement === fullscreenTarget) {
void document.exitFullscreen();
} else {
void fullscreenTarget.requestFullscreen();
}
};
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key !== 'F11') return;
event.preventDefault();
event.stopPropagation();
toggleFullscreen();
toggleWorkspaceFullscreen();
};
const handleFullscreenChange = () => {
@ -1818,7 +1894,7 @@ function App() {
} else if (button.classList.contains('chart-pivot-button')) {
togglePivotView();
} else {
toggleFullscreen();
toggleWorkspaceFullscreen();
}
};
@ -1891,8 +1967,16 @@ function App() {
selectedContentNodes.length,
statisticMenuOpen,
togglePivotView,
toggleWorkspaceFullscreen,
]);
useEffect(() => {
if (chartViewKey !== 'pivot') return;
const timer = window.setTimeout(fitPivotGridColumns, 180);
return () => window.clearTimeout(timer);
}, [chartViewKey, fitPivotGridColumns, metricKey, pivotGridRowData.length, rightPanelCollapsed]);
const chartOptions = useMemo<AgCartesianChartOptions>(() => {
const visibleData = groupNames.map((groupName) => {
const row: Record<string, string | number | null> = { groupName };
@ -2161,7 +2245,11 @@ function App() {
))}
</div>
<section className="workspace" aria-label="年度费用模板" ref={workspaceRef}>
<section
className={`workspace${rightPanelCollapsed ? ' is-right-panel-collapsed' : ''}`}
aria-label="年度费用模板"
ref={workspaceRef}
>
{indicatorSelectionLabel ? <div className="chart-indicator-selection-label" title={indicatorSelectionLabel}>{indicatorSelectionLabel}</div> : null}
<div className="chart-filter-bar chart-filter-bar--workspace" aria-label="筛选条件">
{chartFilterOptions.map((option) => {
@ -2199,6 +2287,7 @@ function App() {
planningForm: [],
});
setChartDataBySelection({});
setChartSummaryBySelection({});
setLoadError(null);
if (selectedContentNodes.length > 0) {
setLoadingHint('正在按筛选条件重新计算');
@ -2264,9 +2353,23 @@ function App() {
>
{currentViewShortLabel}
</button>
<button
className="chart-grid-tool-button chart-grid-tool-button--fullscreen"
type="button"
title={fullscreenToggleLabel}
aria-label={fullscreenToggleLabel}
aria-pressed={workspaceFullscreen}
onClick={toggleWorkspaceFullscreen}
>
<i
className={`anticon ${workspaceFullscreen ? 'anticon-shrink' : 'anticon-arrow-salt'} ag-charts-myButton-fullScreen ag-charts-diy-button`}
aria-hidden="true"
/>
</button>
{renderMetricSwitcher('grid')}
<AgGridReact<PivotGridRow>
rowData={pivotGridRowData}
pinnedBottomRowData={pivotGridPinnedBottomRowData}
columnDefs={pivotGridColumnDefs}
containerStyle={{ width: '100%', height: '100%' }}
theme="legacy"
@ -2274,7 +2377,17 @@ function App() {
sortable: true,
resizable: true,
filter: true,
minWidth: 58,
}}
rowHeight={30}
headerHeight={32}
groupHeaderHeight={32}
onGridReady={(event: GridReadyEvent<PivotGridRow>) => {
pivotGridApiRef.current = event.api;
fitPivotGridColumns();
}}
onGridSizeChanged={fitPivotGridColumns}
onFirstDataRendered={fitPivotGridColumns}
suppressCellFocus
overlayNoRowsTemplate={selectedContentNodes.length === 0 ? '请选择右侧分类项' : '暂无透视数据'}
/>
@ -2288,7 +2401,21 @@ function App() {
</div>
</section>
<aside className="right-panel" aria-label="选择内容">
<button
className="right-panel-toggle"
type="button"
title={rightPanelToggleLabel}
aria-label={rightPanelToggleLabel}
aria-expanded={!rightPanelCollapsed}
onClick={() => setRightPanelCollapsed((collapsed) => !collapsed)}
>
{rightPanelCollapsed ? (
<PanelRightOpen className="right-panel-toggle-icon" aria-hidden="true" strokeWidth={2} />
) : (
<PanelRightClose className="right-panel-toggle-icon" aria-hidden="true" strokeWidth={2} />
)}
</button>
<aside className="right-panel" aria-label="选择内容" aria-hidden={rightPanelCollapsed}>
<div className="content-tabs" role="tablist" aria-label="选择内容切换项">
{contentOptions.map((option) => (
<button

View File

@ -69,10 +69,18 @@ button {
gap: 12px 28px;
height: 100vh;
padding: 30px 28px 18px 64px;
transition: grid-template-columns 160ms ease, gap 160ms ease;
}
.workspace.is-right-panel-collapsed {
grid-template-columns: minmax(0, 1fr) 34px;
gap: 12px 12px;
}
.chart-area {
display: grid;
grid-column: 1;
grid-row: 2;
grid-template-rows: minmax(0, 1fr);
min-width: 0;
}
@ -251,6 +259,11 @@ button {
background: #f5e8d8;
}
.workspace:fullscreen.is-right-panel-collapsed {
grid-template-columns: minmax(0, 1fr) 34px;
gap: 12px 12px;
}
.workspace:fullscreen .chart-area {
min-height: 0;
}
@ -471,6 +484,10 @@ button {
top: 80px;
}
.chart-grid-tool-button--fullscreen {
top: 114px;
}
.chart-grid-tool-button:hover {
color: #0078a8;
border-color: rgba(0, 120, 168, 0.36);
@ -482,13 +499,31 @@ button {
--ag-active-color: #0078a8;
--ag-background-color: #fffaf4;
--ag-border-color: rgba(90, 82, 72, 0.18);
--ag-cell-horizontal-padding: 6px;
--ag-font-family: "Microsoft YaHei", "PingFang SC", "Segoe UI", Arial, sans-serif;
--ag-font-size: 12px;
--ag-foreground-color: #262a33;
--ag-header-cell-horizontal-padding: 6px;
--ag-header-background-color: #f6eadc;
--ag-row-hover-color: rgba(0, 120, 168, 0.08);
}
.chart-pivot-grid-panel .ag-header-cell,
.chart-pivot-grid-panel .ag-header-group-cell {
padding-left: 6px;
padding-right: 6px;
}
.chart-pivot-grid-panel .ag-cell {
padding-left: 6px;
padding-right: 6px;
}
.chart-pivot-grid-panel .ag-header-cell-label,
.chart-pivot-grid-panel .ag-header-group-cell-label {
justify-content: center;
}
.metric-switcher {
position: absolute;
z-index: 12;
@ -501,7 +536,7 @@ button {
.metric-switcher--grid {
left: 24px;
top: 114px;
top: 148px;
}
.metric-switcher-button {
@ -661,15 +696,19 @@ button {
box-shadow: 0 1px 5px rgba(69, 54, 36, 0.12);
}
.chart-frame .ag-charts-myButton-fullScreen {
.chart-frame .ag-charts-myButton-fullScreen,
.chart-grid-tool-button .ag-charts-myButton-fullScreen {
position: relative;
display: block;
width: 16px;
height: 16px;
margin: 4px auto;
}
.chart-frame .ag-charts-myButton-fullScreen::before,
.chart-frame .ag-charts-myButton-fullScreen::after {
.chart-frame .ag-charts-myButton-fullScreen::after,
.chart-grid-tool-button .ag-charts-myButton-fullScreen::before,
.chart-grid-tool-button .ag-charts-myButton-fullScreen::after {
position: absolute;
inset: 0;
display: block;
@ -678,19 +717,22 @@ button {
content: "";
}
.chart-frame .anticon-arrow-salt::before {
.chart-frame .anticon-arrow-salt::before,
.chart-grid-tool-button .anticon-arrow-salt::before {
border-top: 1px solid currentColor;
border-right: 1px solid currentColor;
clip-path: polygon(54% 0, 100% 0, 100% 46%, 86% 46%, 86% 14%, 54% 14%);
}
.chart-frame .anticon-arrow-salt::after {
.chart-frame .anticon-arrow-salt::after,
.chart-grid-tool-button .anticon-arrow-salt::after {
border-bottom: 1px solid currentColor;
border-left: 1px solid currentColor;
clip-path: polygon(0 54%, 14% 54%, 14% 86%, 46% 86%, 46% 100%, 0 100%);
}
.chart-frame .anticon-shrink::before {
.chart-frame .anticon-shrink::before,
.chart-grid-tool-button .anticon-shrink::before {
top: 2px;
left: 2px;
width: 6px;
@ -699,7 +741,8 @@ button {
border-bottom: 1px solid currentColor;
}
.chart-frame .anticon-shrink::after {
.chart-frame .anticon-shrink::after,
.chart-grid-tool-button .anticon-shrink::after {
right: 2px;
bottom: 2px;
left: auto;
@ -722,6 +765,8 @@ button {
.right-panel {
display: grid;
grid-column: 2;
grid-row: 2;
grid-template-rows: auto minmax(0, 1fr);
min-width: 0;
height: 100%;
@ -730,6 +775,52 @@ button {
overflow: hidden;
}
.right-panel-toggle {
display: inline-grid;
grid-column: 2;
grid-row: 2;
justify-self: end;
align-self: start;
width: 28px;
height: 28px;
place-items: center;
padding: 0;
border: 1px solid rgba(90, 82, 72, 0.18);
border-radius: 3px;
color: #46413b;
background: rgba(255, 249, 241, 0.82);
cursor: pointer;
z-index: 4;
}
.right-panel-toggle:hover,
.right-panel-toggle:focus-visible {
color: #0078a8;
border-color: rgba(0, 120, 168, 0.36);
background: rgba(255, 252, 248, 0.96);
box-shadow: 0 1px 5px rgba(69, 54, 36, 0.12);
}
.right-panel-toggle:focus-visible {
outline: 2px solid rgba(0, 120, 168, 0.28);
outline-offset: 2px;
}
.right-panel-toggle-icon {
display: block;
width: 16px;
height: 16px;
}
.workspace.is-right-panel-collapsed .right-panel {
display: none;
}
.workspace.is-right-panel-collapsed .right-panel-toggle {
position: sticky;
top: 24px;
}
.content-tabs {
display: flex;
align-items: center;
@ -1100,14 +1191,27 @@ button {
padding: 8px 18px 18px 56px;
}
.workspace.is-right-panel-collapsed {
grid-template-columns: 1fr;
}
.chart-area {
grid-row: 2;
min-height: 520px;
}
.right-panel {
grid-column: 1;
grid-row: 3;
min-height: 280px;
}
.right-panel-toggle {
grid-column: 1;
grid-row: 3;
justify-self: end;
}
.content-tabs {
flex-wrap: wrap;
}

View File

@ -1 +1 @@
$ vite --host 0.0.0.0 --port "5173"
$ vite --host 0.0.0.0 --port "5174"

View File

@ -1,237 +1,8 @@
Port 5173 is in use, trying another one...
VITE v7.3.2 ready in 271 ms
VITE v7.3.2 ready in 306 ms
➜ Local: http://localhost:5174/
➜ Network: http://198.18.0.1:5174/
➜ Network: http://100.106.162.120:5174/
➜ Network: http://192.168.1.155:5174/
➜ Network: http://172.31.112.1:5174/
 ➜ press h + enter to show help
15:45:13 [vite] (client) hmr update /src/App.tsx
15:45:39 [vite] (client) hmr update /src/styles.css
15:59:24 [vite] (client) hmr update /src/App.tsx
15:59:52 [vite] (client) hmr update /src/styles.css
16:00:28 [vite] (client) hmr update /src/styles.css
16:00:58 [vite] (client) hmr update /src/styles.css
16:06:48 [vite] (client) hmr update /src/App.tsx
16:07:28 [vite] (client) hmr update /src/App.tsx
16:07:28 [vite] (client) ✨ new dependencies optimized: ag-charts-enterprise, ag-charts-locale
16:07:28 [vite] (client) ✨ optimized dependencies changed. reloading
16:07:47 [vite] (client) hmr update /src/styles.css
16:08:26 [vite] (client) hmr update /src/styles.css
16:09:28 [vite] (client) hmr update /src/App.tsx
16:11:07 [vite] (client) hmr update /src/App.tsx
16:11:48 [vite] (client) hmr update /src/styles.css
16:13:15 [vite] (client) hmr update /src/styles.css
16:17:34 [vite] (client) hmr update /src/App.tsx
16:18:10 [vite] (client) hmr update /src/styles.css
16:22:30 [vite] (client) hmr update /src/App.tsx
16:23:11 [vite] (client) hmr update /src/styles.css
16:24:37 [vite] (client) hmr update /src/App.tsx
16:25:42 [vite] (client) page reload demo.html
16:25:42 [vite] (client) page reload demo.html
16:27:28 [vite] (client) hmr update /src/App.tsx
16:28:39 [vite] (client) hmr update /src/styles.css
16:32:10 [vite] (client) hmr update /src/App.tsx
16:33:55 [vite] (client) hmr update /src/App.tsx
16:38:49 [vite] (client) hmr update /src/App.tsx
16:39:34 [vite] (client) hmr update /src/styles.css
16:46:57 [vite] (client) hmr update /src/App.tsx
16:47:20 [vite] (client) hmr update /src/App.tsx
16:53:41 [vite] (client) hmr update /src/App.tsx
16:54:18 [vite] (client) hmr update /src/styles.css
16:59:35 [vite] (client) hmr update /src/styles.css
17:01:23 [vite] (client) hmr update /src/styles.css
17:03:24 [vite] (client) hmr update /src/styles.css
17:06:50 [vite] (client) hmr update /src/styles.css
17:29:44 [vite] (client) hmr update /src/App.tsx
17:29:52 [vite] (client) hmr update /src/styles.css
17:32:17 [vite] (client) hmr update /src/App.tsx
17:32:43 [vite] (client) hmr update /src/styles.css
17:34:33 [vite] (client) hmr update /src/App.tsx
17:40:09 [vite] (client) hmr update /src/styles.css
17:40:37 [vite] (client) hmr update /src/App.tsx
17:41:32 [vite] (client) hmr update /src/styles.css
17:42:39 [vite] (client) hmr update /src/styles.css
17:44:42 [vite] (client) hmr update /src/App.tsx
09:11:33 [vite] (client) hmr update /src/App.tsx
09:11:59 [vite] (client) hmr update /src/styles.css
09:14:42 [vite] (client) hmr update /src/styles.css
09:15:32 [vite] (client) hmr update /src/styles.css
09:18:16 [vite] (client) hmr update /src/App.tsx
09:18:28 [vite] (client) hmr update /src/styles.css
09:35:28 [vite] (client) hmr update /src/App.tsx
09:35:53 [vite] (client) hmr update /src/App.tsx
09:35:53 [vite] (client) hmr update /src/styles.css
09:36:43 [vite] (client) hmr update /src/App.tsx
09:40:50 [vite] (client) hmr update /src/App.tsx
09:41:06 [vite] (client) hmr update /src/styles.css
09:42:18 [vite] (client) hmr update /src/styles.css
09:55:02 [vite] (client) hmr update /src/App.tsx
09:55:36 [vite] (client) hmr update /src/App.tsx
09:55:49 [vite] (client) hmr update /src/styles.css
10:07:23 [vite] (client) hmr update /src/App.tsx
10:26:33 [vite] (client) hmr update /src/App.tsx
10:27:25 [vite] (client) hmr update /src/App.tsx
10:27:56 [vite] (client) hmr update /src/styles.css
11:14:09 [vite] (client) hmr update /src/App.tsx
11:16:09 [vite] (client) page reload zbChart/index.html
11:17:29 [vite] (client) page reload index.html
11:17:43 [vite] (client) page reload src/main.tsx
11:32:17 [vite] (client) page reload src/main.tsx
11:35:33 [vite] (client) hmr update /src/App.tsx
11:35:45 [vite] (client) hmr update /src/styles.css
11:49:29 [vite] (client) hmr update /src/App.tsx
11:52:03 [vite] (client) hmr update /src/styles.css
11:53:37 [vite] (client) hmr update /src/App.tsx
11:57:22 [vite] (client) hmr update /src/App.tsx
11:59:02 [vite] (client) hmr update /src/App.tsx
15:00:25 [vite] (client) hmr update /src/App.tsx
15:14:46 [vite] (client) hmr update /src/App.tsx
15:14:56 [vite] (client) hmr update /src/App.tsx
15:15:05 [vite] (client) hmr update /src/App.tsx
15:15:18 [vite] (client) hmr update /src/App.tsx
15:15:29 [vite] (client) hmr update /src/App.tsx
15:15:39 [vite] (client) hmr update /src/App.tsx
15:15:49 [vite] (client) hmr update /src/styles.css
15:54:06 [vite] (client) hmr update /src/App.tsx
15:54:29 [vite] (client) hmr update /src/styles.css
16:29:28 [vite] (client) hmr update /src/App.tsx
16:29:37 [vite] (client) hmr update /src/App.tsx
17:53:09 [vite] (client) hmr update /src/App.tsx
17:53:24 [vite] (client) hmr update /src/styles.css
17:54:53 [vite] (client) hmr update /src/App.tsx
17:55:03 [vite] (client) hmr update /src/styles.css
18:10:22 [vite] (client) hmr update /src/App.tsx
09:43:29 [vite] (client) page reload zbChart/index.html
09:43:43 [vite] (client) page reload demo.html
09:44:24 [vite] (client) hmr update /src/App.tsx
09:47:32 [vite] (client) hmr update /src/App.tsx
09:56:50 [vite] (client) hmr update /src/App.tsx
10:03:05 [vite] (client) hmr update /src/App.tsx
10:04:28 [vite] (client) hmr update /src/App.tsx
10:04:35 [vite] (client) hmr update /src/styles.css
10:06:06 [vite] (client) hmr update /src/App.tsx
10:06:25 [vite] (client) hmr update /src/styles.css
14:21:06 [vite] (client) hmr update /src/App.tsx
14:26:18 [vite] (client) hmr update /src/App.tsx
14:26:51 [vite] (client) hmr update /src/styles.css
14:28:15 [vite] (client) hmr update /src/App.tsx
14:28:24 [vite] (client) hmr update /src/App.tsx
15:07:03 [vite] (client) hmr update /src/App.tsx
15:07:31 [vite] (client) hmr update /src/App.tsx
15:08:11 [vite] (client) hmr update /src/App.tsx
15:13:59 [vite] (client) hmr update /src/App.tsx
16:47:31 [vite] (client) hmr update /src/App.tsx
16:48:22 [vite] (client) hmr update /src/App.tsx
16:48:32 [vite] (client) hmr update /src/App.tsx
17:00:07 [vite] (client) hmr update /src/App.tsx
17:15:44 [vite] (client) hmr update /src/App.tsx
18:06:45 [vite] (client) hmr update /src/App.tsx
18:07:01 [vite] (client) hmr update /src/App.tsx
18:07:22 [vite] (client) hmr update /src/App.tsx
18:07:43 [vite] (client) hmr update /src/App.tsx
18:08:03 [vite] (client) hmr update /src/App.tsx
18:08:20 [vite] (client) hmr update /src/App.tsx
18:08:55 [vite] (client) hmr update /src/App.tsx
18:09:09 [vite] (client) hmr update /src/App.tsx
18:09:41 [vite] (client) hmr update /src/App.tsx
18:10:20 [vite] (client) hmr update /src/styles.css
18:11:00 [vite] (client) hmr update /src/styles.css
09:08:22 [vite] (client) hmr update /src/App.tsx
09:09:58 [vite] (client) hmr update /src/App.tsx
09:10:10 [vite] (client) hmr update /src/styles.css
09:12:48 [vite] (client) hmr update /src/styles.css
09:24:09 [vite] (client) hmr update /src/styles.css
09:24:54 [vite] (client) hmr update /src/styles.css
09:25:33 [vite] (client) hmr update /src/App.tsx
09:25:55 [vite] (client) hmr update /src/styles.css
09:31:55 [vite] (client) hmr update /src/App.tsx
09:32:05 [vite] (client) hmr update /src/App.tsx
09:32:12 [vite] (client) hmr update /src/App.tsx
09:32:32 [vite] (client) hmr update /src/styles.css
09:45:56 [vite] (client) hmr update /src/App.tsx
10:00:51 [vite] (client) hmr update /src/App.tsx
10:01:08 [vite] (client) hmr update /src/App.tsx
10:01:23 [vite] (client) hmr update /src/App.tsx
10:01:36 [vite] (client) hmr update /src/App.tsx
10:01:46 [vite] (client) hmr update /src/App.tsx
10:01:56 [vite] (client) hmr update /src/App.tsx
10:02:58 [vite] (client) hmr update /src/App.tsx
10:08:33 [vite] (client) hmr update /src/App.tsx
10:08:56 [vite] (client) hmr update /src/App.tsx
10:09:20 [vite] (client) hmr update /src/styles.css
10:09:29 [vite] (client) hmr update /src/styles.css
10:12:15 [vite] (client) hmr update /src/styles.css
10:17:02 [vite] (client) hmr update /src/App.tsx
10:54:20 [vite] (client) hmr update /src/App.tsx
10:54:44 [vite] (client) hmr update /src/App.tsx
10:59:54 [vite] (client) hmr update /src/App.tsx
11:00:16 [vite] (client) hmr update /src/App.tsx
11:00:32 [vite] (client) hmr update /src/App.tsx
11:00:55 [vite] (client) hmr update /src/App.tsx
11:12:15 [vite] (client) hmr update /src/App.tsx
11:16:52 [vite] (client) hmr update /src/App.tsx
11:20:34 [vite] (client) hmr update /src/App.tsx
11:22:39 [vite] (client) hmr update /src/App.tsx
11:30:32 [vite] (client) hmr update /src/App.tsx
11:42:14 [vite] (client) hmr update /src/App.tsx
11:48:40 [vite] (client) hmr update /src/App.tsx
12:07:11 [vite] (client) hmr update /src/App.tsx
12:07:25 [vite] (client) hmr update /src/App.tsx
12:07:50 [vite] (client) hmr update /src/App.tsx
14:44:40 [vite] (client) hmr update /src/App.tsx
14:47:46 [vite] (client) hmr update /src/App.tsx
14:48:15 [vite] (client) hmr update /src/App.tsx
14:51:05 [vite] (client) hmr update /src/App.tsx
15:13:54 [vite] (client) hmr update /src/App.tsx
15:14:22 [vite] (client) hmr update /src/App.tsx
15:17:54 [vite] (client) hmr update /src/App.tsx
15:18:05 [vite] (client) hmr update /src/App.tsx
15:18:58 [vite] (client) hmr update /src/App.tsx
15:37:07 [vite] (client) hmr update /src/App.tsx
15:37:17 [vite] (client) hmr update /src/App.tsx
15:44:06 [vite] (client) hmr update /src/App.tsx
16:32:19 [vite] (client) hmr update /src/App.tsx
17:15:32 [vite] (client) hmr update /src/App.tsx
15:34:28 [vite] (client) hmr update /src/App.tsx
11:18:46 [vite] (client) hmr update /src/App.tsx
11:18:59 [vite] (client) hmr update /src/App.tsx
11:19:51 [vite] (client) hmr update /src/App.tsx
11:20:06 [vite] (client) hmr update /src/App.tsx
11:20:27 [vite] (client) hmr update /src/App.tsx
11:20:45 [vite] (client) hmr update /src/App.tsx
11:21:04 [vite] (client) hmr update /src/App.tsx
11:21:48 [vite] (client) hmr update /src/App.tsx
11:25:16 [vite] (client) hmr update /src/App.tsx
11:25:34 [vite] (client) hmr update /src/App.tsx
11:33:37 [vite] (client) hmr update /src/styles.css
11:38:01 [vite] (client) hmr update /src/App.tsx
11:38:14 [vite] (client) hmr update /src/App.tsx
11:38:28 [vite] (client) hmr update /src/App.tsx
11:38:42 [vite] (client) hmr update /src/styles.css
11:39:18 [vite] (client) hmr update /src/App.tsx
11:41:08 [vite] (client) hmr update /src/App.tsx
11:48:17 [vite] (client) hmr update /src/App.tsx
11:50:43 [vite] (client) hmr update /src/styles.css
12:07:07 [vite] (client) hmr update /src/App.tsx
12:07:24 [vite] (client) hmr update /src/App.tsx
12:07:45 [vite] (client) hmr update /src/App.tsx
12:08:39 [vite] (client) hmr update /src/App.tsx
12:08:54 [vite] (client) hmr update /src/App.tsx
12:09:23 [vite] (client) hmr update /src/App.tsx
12:09:54 [vite] (client) hmr update /src/App.tsx
12:10:36 [vite] (client) hmr update /src/App.tsx
12:11:14 [vite] (client) hmr update /src/App.tsx
12:12:29 [vite] (client) hmr update /src/App.tsx
12:13:01 [vite] (client) hmr update /src/App.tsx
12:14:05 [vite] (client) hmr update /src/styles.css
12:18:36 [vite] (client) hmr update /src/App.tsx
12:19:03 [vite] (client) hmr update /src/App.tsx
12:22:51 [vite] (client) hmr update /src/App.tsx
12:44:18 [vite] (client) hmr update /src/App.tsx
12:45:27 [vite] (client) hmr update /src/App.tsx
14:35:53 [vite] (client) hmr update /src/App.tsx
15:23:18 [vite] (client) hmr update /src/App.tsx
15:23:36 [vite] (client) hmr update /src/App.tsx
15:41:18 [vite] (client) hmr update /src/App.tsx