Compare commits

..

5 Commits

Author SHA1 Message Date
178a3e656f '1' 2026-06-01 14:48:17 +08:00
cfb6afcdce '1' 2026-05-21 09:24:15 +08:00
58abe588aa 1 2026-05-13 16:23:55 +08:00
46325ee801 Merge remote-tracking branch 'origin/main' into building-facility-object 2026-05-13 10:25:08 +08:00
c65235f377 feat: switch to building facility object 2026-05-11 10:30:14 +08:00
16 changed files with 283 additions and 241 deletions

View File

@ -0,0 +1,3 @@
[ 6918ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=ae174d51:20102
[ 7315ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://127.0.0.1:5173/favicon.ico:0
[ 331036ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=ae174d51:20102

View File

@ -1 +1,6 @@
[ 550538ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=ae174d51:20102
[ 300ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=ae174d51:20102
[ 1230ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://127.0.0.1:5173/favicon.ico:0
[ 40506ms] [ERROR] Failed to load resource: the server responded with a status of 401 () @ https://nest.zwgczx.com/api/v1/zw/getBuildingFunctionCostFilterTree?key=templateLibrary&currenttime=1778660578349&__random__=1778660578349:0
[ 40812ms] [ERROR] Failed to load resource: the server responded with a status of 401 () @ https://nest.zwgczx.com/api/v1/zw/getBuildingFunctionCostFilterTree?key=indicatorTree&templateId=3&currenttime=1778660578682&__random__=1778660578682:0
[ 52231ms] [ERROR] Failed to load resource: the server responded with a status of 401 () @ https://nest.zwgczx.com/api/v1/zw/getBuildingFunctionCostFilterTree?key=templateLibrary&currenttime=1778660590101&__random__=1778660590101:0
[ 53824ms] [ERROR] Failed to load resource: the server responded with a status of 401 () @ https://nest.zwgczx.com/api/v1/zw/getBuildingFunctionCostFilterTree?key=indicatorTree&templateId=3&currenttime=1778660591693&__random__=1778660591693:0

View File

@ -1,2 +0,0 @@
[ 136ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=ae174d51:20102
[ 595ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://127.0.0.1:5173/favicon.ico:0

View File

@ -1,2 +0,0 @@
[ 221ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://127.0.0.1:5179/node_modules/.vite/deps/react-dom_client.js?v=eccfa23d:20102
[ 1056ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://127.0.0.1:5179/favicon.ico:0

View File

@ -0,0 +1,84 @@
- main [ref=e3]:
- generic:
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- region "年度费用模板" [ref=e4]:
- generic "筛选条件" [ref=e5]:
- button "省市区" [ref=e6] [cursor=pointer]:
- img [ref=e7]
- generic [ref=e11]: 省市区
- button "自然地理区位" [ref=e12] [cursor=pointer]:
- img [ref=e13]
- generic [ref=e16]: 自然地理区位
- button "设施类别" [ref=e17] [cursor=pointer]:
- img [ref=e18]
- generic [ref=e22]: 设施类别
- button "建筑功能" [ref=e23] [cursor=pointer]:
- img [ref=e24]
- generic [ref=e27]: 建筑功能
- button "建设阶段" [ref=e28] [cursor=pointer]:
- img [ref=e29]
- generic [ref=e34]: 建设阶段
- button "规划形式" [ref=e35] [cursor=pointer]:
- img [ref=e36]
- generic [ref=e41]: 规划形式
- region "年度总费用图表" [ref=e42]:
- generic [ref=e43]:
- button "纵坐标:造价(元)" [ref=e45] [cursor=pointer]: 造价(元)
- generic [ref=e46]:
- figure "图表共有0个系列":
- generic [ref=e47]:
- img "interactive chart":
- generic:
- img
- img
- region [ref=e48]
- toolbar "标注" [ref=e49]:
- button "库" [ref=e50] [cursor=pointer]:
- generic:
- generic:
- button "树" [ref=e51] [cursor=pointer]:
- generic:
- generic:
- button "均" [ref=e52] [cursor=pointer]:
- generic:
- button "Line Tool" [disabled] [ref=e53]
- button "Text Tool" [disabled] [ref=e54]
- button "Shape Tool" [disabled] [ref=e55]
- button "Fibonacci Tool" [disabled] [ref=e56]
- button "全屏(F11)" [ref=e57] [cursor=pointer]
- button "Clear annotations" [disabled] [ref=e58]
- status:
- generic: 请选择右侧分类项
- toolbar "缩放" [ref=e59]:
- button "缩小" [disabled] [ref=e60]
- button "放大" [ref=e61] [cursor=pointer]
- button "左移" [disabled] [ref=e62]
- button "右移" [disabled] [ref=e63]
- button "重置" [disabled] [ref=e64]
- complementary "选择内容" [ref=e65]:
- tablist "选择内容切换项" [ref=e66]:
- tab "自然地理区位" [selected] [ref=e67] [cursor=pointer]
- tab "设施类别" [ref=e68] [cursor=pointer]
- tab "建筑功能" [ref=e69] [cursor=pointer]
- tab "建设阶段" [ref=e70] [cursor=pointer]
- tab "规划形式" [ref=e71] [cursor=pointer]
- generic [ref=e72]:
- generic [ref=e73]: 自然地理区位
- generic [ref=e74]: 加载中

View File

@ -0,0 +1,84 @@
- main [ref=e3]:
- generic:
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- region "年度费用模板" [ref=e4]:
- generic "筛选条件" [ref=e5]:
- button "省市区" [ref=e6] [cursor=pointer]:
- img [ref=e7]
- generic [ref=e11]: 省市区
- button "自然地理区位" [ref=e12] [cursor=pointer]:
- img [ref=e13]
- generic [ref=e16]: 自然地理区位
- button "设施类别" [ref=e17] [cursor=pointer]:
- img [ref=e18]
- generic [ref=e22]: 设施类别
- button "建筑功能" [ref=e23] [cursor=pointer]:
- img [ref=e24]
- generic [ref=e27]: 建筑功能
- button "建设阶段" [ref=e28] [cursor=pointer]:
- img [ref=e29]
- generic [ref=e34]: 建设阶段
- button "规划形式" [ref=e35] [cursor=pointer]:
- img [ref=e36]
- generic [ref=e41]: 规划形式
- region "年度总费用图表" [ref=e42]:
- generic [ref=e43]:
- button "纵坐标:造价(元)" [ref=e45] [cursor=pointer]: 造价(元)
- generic [ref=e46]:
- figure "图表共有0个系列":
- generic [ref=e47]:
- img "interactive chart":
- generic:
- img
- img
- region [ref=e48]
- toolbar "标注" [ref=e49]:
- button "库" [ref=e50] [cursor=pointer]:
- generic:
- generic:
- button "树" [ref=e51] [cursor=pointer]:
- generic:
- generic:
- button "均" [ref=e52] [cursor=pointer]:
- generic:
- button "Line Tool" [disabled] [ref=e53]
- button "Text Tool" [disabled] [ref=e54]
- button "Shape Tool" [disabled] [ref=e55]
- button "Fibonacci Tool" [disabled] [ref=e56]
- button "全屏(F11)" [ref=e57] [cursor=pointer]
- button "Clear annotations" [disabled] [ref=e58]
- status:
- generic: 请选择右侧分类项
- toolbar "缩放" [ref=e59]:
- button "缩小" [disabled] [ref=e60]
- button "放大" [ref=e61] [cursor=pointer]
- button "左移" [disabled] [ref=e62]
- button "右移" [disabled] [ref=e63]
- button "重置" [disabled] [ref=e64]
- complementary "选择内容" [ref=e65]:
- tablist "选择内容切换项" [ref=e66]:
- tab "自然地理区位" [selected] [ref=e67] [cursor=pointer]
- tab "设施类别" [ref=e68] [cursor=pointer]
- tab "建筑功能" [ref=e69] [cursor=pointer]
- tab "建设阶段" [ref=e70] [cursor=pointer]
- tab "规划形式" [ref=e71] [cursor=pointer]
- generic [ref=e72]:
- generic [ref=e73]: 自然地理区位
- generic [ref=e74]: 加载中

View File

@ -1,80 +0,0 @@
- main [ref=e3]:
- generic:
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- region "年度费用模板" [ref=e4]:
- generic "筛选条件" [ref=e5]:
- button "省市区" [ref=e6] [cursor=pointer]:
- img [ref=e7]
- generic [ref=e11]: 省市区
- button "自然地理区位" [ref=e12] [cursor=pointer]:
- img [ref=e13]
- generic [ref=e16]: 自然地理区位
- button "设施类别" [ref=e17] [cursor=pointer]:
- img [ref=e18]
- generic [ref=e22]: 设施类别
- button "建设阶段" [ref=e23] [cursor=pointer]:
- img [ref=e24]
- generic [ref=e29]: 建设阶段
- button "规划形式" [ref=e30] [cursor=pointer]:
- img [ref=e31]
- generic [ref=e36]: 规划形式
- region "年度总费用图表" [ref=e37]:
- generic [ref=e38]:
- button "纵坐标:造价(元)" [ref=e40] [cursor=pointer]: 造价(元)
- generic [ref=e41]:
- figure "图表共有0个系列":
- generic [ref=e42]:
- img "interactive chart":
- generic:
- img
- img
- region [ref=e43]
- toolbar "标注" [ref=e44]:
- button "库" [ref=e45] [cursor=pointer]:
- generic:
- generic:
- button "树" [ref=e46] [cursor=pointer]:
- generic:
- generic:
- button "均" [ref=e47] [cursor=pointer]:
- generic:
- button "Line Tool" [disabled] [ref=e48]
- button "Text Tool" [disabled] [ref=e49]
- button "Shape Tool" [disabled] [ref=e50]
- button "Fibonacci Tool" [disabled] [ref=e51]
- button "全屏(F11)" [ref=e52] [cursor=pointer]
- button "Clear annotations" [disabled] [ref=e53]
- status:
- generic: 请选择右侧分类项
- toolbar "缩放" [ref=e54]:
- button "缩小" [disabled] [ref=e55]
- button "放大" [ref=e56] [cursor=pointer]
- button "左移" [disabled] [ref=e57]
- button "右移" [disabled] [ref=e58]
- button "重置" [disabled] [ref=e59]
- complementary "选择内容" [ref=e60]:
- tablist "选择内容切换项" [ref=e61]:
- tab "自然地理区位" [selected] [ref=e62] [cursor=pointer]
- tab "设施类别" [ref=e63] [cursor=pointer]
- tab "建设阶段" [ref=e64] [cursor=pointer]
- tab "规划形式" [ref=e65] [cursor=pointer]
- generic [ref=e66]:
- generic [ref=e67]: 自然地理区位
- generic [ref=e68]: 加载中

View File

@ -1,83 +0,0 @@
- main [ref=e3]:
- generic:
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- region "年度费用模板" [ref=e4]:
- generic "筛选条件" [ref=e5]:
- button "省市区" [ref=e6] [cursor=pointer]:
- img [ref=e7]
- generic [ref=e11]: 省市区
- button "自然地理区位" [ref=e12] [cursor=pointer]:
- img [ref=e13]
- generic [ref=e16]: 自然地理区位
- button "设施类别" [ref=e17] [cursor=pointer]:
- img [ref=e18]
- generic [ref=e22]: 设施类别
- button "建设阶段" [ref=e23] [cursor=pointer]:
- img [ref=e24]
- generic [ref=e29]: 建设阶段
- button "规划形式" [ref=e30] [cursor=pointer]:
- img [ref=e31]
- generic [ref=e36]: 规划形式
- region "年度总费用图表" [ref=e37]:
- generic [ref=e38]:
- button "纵坐标:造价(元)" [ref=e40] [cursor=pointer]: 造价(元)
- generic [ref=e41]:
- figure "图表共有0个系列":
- generic [ref=e42]:
- img "interactive chart":
- generic:
- img
- img
- region [ref=e43]
- toolbar "标注" [ref=e44]:
- button "库" [disabled] [ref=e45] [cursor=pointer]:
- generic:
- generic:
- button "指" [disabled] [ref=e46] [cursor=pointer]:
- generic:
- generic:
- button "均" [disabled] [ref=e47] [cursor=pointer]:
- generic:
- button "Line Tool" [disabled] [ref=e48]
- button "Text Tool" [disabled] [ref=e49]
- button "Shape Tool" [disabled] [ref=e50]
- button "Fibonacci Tool" [disabled] [ref=e51]
- button "全屏(F11)" [disabled] [ref=e52] [cursor=pointer]
- button "Clear annotations" [disabled] [ref=e53]
- button "切换到表格" [disabled] [ref=e54] [cursor=pointer]:
- generic:
- generic:
- status:
- generic: 请选择右侧分类项
- toolbar "缩放" [ref=e55]:
- button "缩小" [disabled] [ref=e56]
- button "放大" [ref=e57] [cursor=pointer]
- button "左移" [disabled] [ref=e58]
- button "右移" [disabled] [ref=e59]
- button "重置" [disabled] [ref=e60]
- complementary "选择内容" [ref=e61]:
- tablist "选择内容切换项" [ref=e62]:
- tab "自然地理区位" [selected] [ref=e63] [cursor=pointer]
- tab "设施类别" [ref=e64] [cursor=pointer]
- tab "建设阶段" [ref=e65] [cursor=pointer]
- tab "规划形式" [ref=e66] [cursor=pointer]
- generic [ref=e67]:
- generic [ref=e68]: 自然地理区位
- generic [ref=e69]: Unexpected token '<', "<!doctype "... is not valid JSON

View File

@ -1,10 +1,10 @@
# 组团趋势图
# 建筑设施对象趋势图
这是一个基于 `React + AG Charts`组团趋势图页面,用来查看不同分类维度下的年度统计趋势,并通过右侧筛选条件联动图表结果。
这是一个基于 `React + AG Charts`建筑设施对象趋势图页面,用来查看不同分类维度下的年度统计趋势,并通过右侧筛选条件联动图表结果。
## 功能
- 左侧展示组团趋势图,支持最低值、最高值、平均值、中位数和数据量切换
- 左侧展示建筑设施对象趋势图,支持最低值、最高值、平均值、中位数和数据量切换
- 右侧展示分类树,支持按 `自然地理区位 / 设施类别 / 建设阶段 / 规划形式` 选择节点
- 筛选条件支持 `省市区`,后端会根据 `uf_xzqy` 展开到区级 `id` 后再过滤
- 支持搜索筛选树节点,支持多条件叠加

View File

@ -6,7 +6,7 @@
<title>AG Chart Service</title>
</head>
<body>
<div id="ztChart"></div>
<div id="sbChart"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@ -12,7 +12,7 @@ import {
} from 'ag-grid-community';
import type { AgCartesianChartOptions } from 'ag-charts-community';
import { ModuleRegistry } from 'ag-charts-community';
import { Building2, Construction, LayoutGrid, Library, LocateFixed, MapPinned, PanelRightClose, PanelRightOpen, Waypoints } from 'lucide-react';
import { Building2, Construction, LayoutGrid, Library, LocateFixed, MapPinned, PanelRightClose, PanelRightOpen, SquareFunction, Waypoints } from 'lucide-react';
import {
AnnotationsModule,
ContextMenuModule,
@ -29,6 +29,12 @@ AgGridModuleRegistry.registerModules([AgGridAllCommunityModule]);
const API_BASE_URL = 'https://nest.zwgczx.com/api/v1';
// const API_BASE_URL = 'http://127.0.0.1:9089/api/v1';
const API_ROUTES = {
filterTree: '/zw/getBuildingFacilityObjectFilterTree',
filterTreeSearch: '/zw/getBuildingFacilityObjectFilterTreeSearch',
statsBatch: '/zw/getBuildingFacilityObjectStatsBatch',
} as const;
const statisticOptions = [
{ key: 'minValue', label: '最低值', shortLabel: '低' },
{ key: 'maxValue', label: '最高值', shortLabel: '高' },
@ -55,6 +61,7 @@ const metricShortLabels: Record<MetricKey, string> = {
const contentOptions = [
{ key: 'geoLocation', label: '自然地理区位' },
{ key: 'facilityType', label: '设施类别' },
{ key: 'buildingFunction', label: '建筑功能' },
{ key: 'constructionStage', label: '建设阶段' },
{ key: 'planningForm', label: '规划形式' },
] as const;
@ -65,6 +72,7 @@ const filterOptions = [
{ key: 'region', label: '省市区', icon: MapPinned },
{ key: 'geoLocation', label: '自然地理区位', icon: LocateFixed },
{ key: 'facilityType', label: '设施类别', icon: Building2 },
{ key: 'buildingFunction', label: '建筑功能', icon: SquareFunction },
{ key: 'constructionStage', label: '建设阶段', icon: Construction },
{ key: 'planningForm', label: '规划形式', icon: LayoutGrid },
] as const;
@ -103,6 +111,17 @@ const contentTreeConfigs = {
fieldid: '305426',
defaultExpandedLevel: 3,
},
buildingFunction: {
endpoint: '/api/public/browser/data/256',
treeid: '95004',
fieldid: '305919',
defaultExpandedLevel: 0,
browserParams: {
workflowid: '182032',
wfid: '182032',
billid: '-1817',
},
},
constructionStage: {
endpoint: '/api/public/browser/data/256',
treeid: '94007',
@ -157,6 +176,7 @@ type StatisticKey = (typeof statisticOptions)[number]['key'];
type MetricKey = (typeof metricOptions)[number]['key'];
type ContentKey = (typeof contentOptions)[number]['key'];
type FilterKey = (typeof filterOptions)[number]['key'];
type GroupKey = 'year';
type ChartViewKey = 'trend' | 'pivot';
type ApiBuildingFunctionStat = {
group_key?: string | number | null;
@ -568,10 +588,6 @@ function isDefaultTemplateSelection(nodes: SelectedFilterNode[]) {
return nodes.length === 1 && nodes[0]?.id === defaultTemplateFilterNode.id;
}
function isSameFilterSelection(a: SelectedFilterNode[], b: SelectedFilterNode[]) {
return a.length === b.length && a.every((node, index) => node.id === b[index]?.id);
}
function getDefaultIndicatorTreeFilterNodes(nodes: TreeNode[]): SelectedFilterNode[] {
const defaultNode = nodes[0];
if (!defaultNode) return [];
@ -710,6 +726,7 @@ function App() {
const treeInitialLoadStartedRef = useRef<Record<ContentKey, boolean>>({
geoLocation: false,
facilityType: false,
buildingFunction: false,
constructionStage: false,
planningForm: false,
});
@ -719,11 +736,13 @@ function App() {
region: false,
geoLocation: false,
facilityType: false,
buildingFunction: false,
constructionStage: false,
planningForm: false,
});
const [statisticKey, setStatisticKey] = useState<StatisticKey>('avgValue');
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);
@ -733,18 +752,21 @@ function App() {
const [treeByContent, setTreeByContent] = useState<Record<ContentKey, TreeNode[]>>({
geoLocation: [],
facilityType: [],
buildingFunction: [],
constructionStage: [],
planningForm: [],
});
const [treeLoadingByContent, setTreeLoadingByContent] = useState<Record<ContentKey, boolean>>({
geoLocation: false,
facilityType: false,
buildingFunction: false,
constructionStage: false,
planningForm: false,
});
const [treeErrorByContent, setTreeErrorByContent] = useState<Record<ContentKey, string | null>>({
geoLocation: null,
facilityType: null,
buildingFunction: null,
constructionStage: null,
planningForm: null,
});
@ -761,6 +783,7 @@ function App() {
region: [],
geoLocation: [],
facilityType: [],
buildingFunction: [],
constructionStage: [],
planningForm: [],
});
@ -770,6 +793,7 @@ function App() {
region: false,
geoLocation: false,
facilityType: false,
buildingFunction: false,
constructionStage: false,
planningForm: false,
});
@ -779,6 +803,7 @@ function App() {
region: null,
geoLocation: null,
facilityType: null,
buildingFunction: null,
constructionStage: null,
planningForm: null,
});
@ -788,6 +813,7 @@ function App() {
region: [],
geoLocation: [],
facilityType: [],
buildingFunction: [],
constructionStage: [],
planningForm: [],
});
@ -797,6 +823,7 @@ function App() {
region: false,
geoLocation: false,
facilityType: false,
buildingFunction: false,
constructionStage: false,
planningForm: false,
});
@ -806,6 +833,7 @@ function App() {
region: null,
geoLocation: null,
facilityType: null,
buildingFunction: null,
constructionStage: null,
planningForm: null,
});
@ -815,6 +843,7 @@ function App() {
region: [],
geoLocation: [],
facilityType: [],
buildingFunction: [],
constructionStage: [],
planningForm: [],
});
@ -829,6 +858,7 @@ function App() {
region: 0,
geoLocation: 0,
facilityType: 0,
buildingFunction: 0,
constructionStage: 0,
planningForm: 0,
});
@ -861,7 +891,10 @@ function App() {
);
const indicatorSelectionLabel = appliedFilters.indicatorTree[0]?.label ?? defaultIndicatorTreeNodes[0]?.label ?? '';
const activeFilterCount = Object.entries(appliedFilters).reduce((total, [key, nodes]) => (
key === 'templateLibrary' && isDefaultTemplateSelection(nodes) ? total : total + nodes.length
(key === 'templateLibrary' && isDefaultTemplateSelection(nodes))
|| (key === 'indicatorTree' && isDefaultIndicatorTreeSelection(nodes, filterTreeByKey.indicatorTree))
? total
: total + nodes.length
), 0);
const chartEmptyText = selectedContentNodes.length === 0
? '请选择右侧分类项'
@ -1057,6 +1090,14 @@ function App() {
() => new Set(draftFilterNodes.map((node) => getFilterSelectionKey(node.filterKey, node.id))),
[draftFilterNodes],
);
useEffect(() => {
if (defaultIndicatorTreeNodes.length === 0) return;
setAppliedFilters((current) => {
if (current.indicatorTree.length > 0) return current;
return { ...current, indicatorTree: defaultIndicatorTreeNodes };
});
}, [defaultIndicatorTreeNodes]);
const appliedFilterPayload = useMemo(
() => filterOptions
.map((option) => ({
@ -1083,6 +1124,7 @@ function App() {
}
const treeParams = {
...browserTreeDefaults,
...('browserParams' in config ? config.browserParams : {}),
treeid: config.treeid,
cube_treeid: config.treeid,
fieldid: config.fieldid,
@ -1140,7 +1182,7 @@ function App() {
};
const fetchRegionFilterTree = async (signal?: AbortSignal) => {
const response = await fetch(`${API_BASE_URL}/zw/getBuildingFunctionCostFilterTree?${buildQuery({ key: 'region' })}`, {
const response = await fetch(`${API_BASE_URL}${API_ROUTES.filterTree}?${buildQuery({ key: 'region' })}`, {
signal,
headers: {
'X-Requested-With': 'XMLHttpRequest',
@ -1185,7 +1227,7 @@ function App() {
};
const fetchBackendFilterTreeSearch = async (filterKey: FilterKey, keyword: string, signal?: AbortSignal) => {
const response = await fetch(`${API_BASE_URL}/zw/getBuildingFunctionCostFilterTreeSearch?${buildQuery({
const response = await fetch(`${API_BASE_URL}${API_ROUTES.filterTreeSearch}?${buildQuery({
key: filterKey,
keyword,
nodePrefix: isContentFilterKey(filterKey) ? getTreeNodePrefix(filterTreeByKey[filterKey]) : '',
@ -1370,6 +1412,10 @@ function App() {
});
};
useEffect(() => {
ensureFilterTreeLoaded('indicatorTree');
}, [selectedTemplateId]);
const openFilterModal = (filterKey: FilterKey) => {
setFilterModalKey(filterKey);
if (isIndicatorTreeFilterKey(filterKey) && appliedFilters[filterKey].length === 0 && defaultIndicatorTreeNodes.length > 0) {
@ -1386,10 +1432,6 @@ function App() {
ensureFilterTreeLoaded(filterKey);
};
useEffect(() => {
ensureFilterTreeLoaded('indicatorTree');
}, [selectedTemplateId]);
const closeFilterModal = () => {
if (filterModalKey) {
filterSearchRequestSeqRef.current[filterModalKey] += 1;
@ -1544,22 +1586,18 @@ function App() {
if (nextDraftNodes.length === 0) {
nextDraftNodes = defaultIndicatorTreeNodes;
}
if (isDefaultIndicatorTreeSelection(nextDraftNodes, filterTreeByKey.indicatorTree)) {
nextDraftNodes = [];
}
}
const shouldReloadIndicatorTree = isTemplateFilterKey(filterModalKey) && !isSameFilterSelection(appliedFilters.templateLibrary, nextDraftNodes);
setAppliedFilters((current) => {
const nextFilters = {
...current,
[filterModalKey]: nextDraftNodes,
};
if (shouldReloadIndicatorTree) {
if (isTemplateFilterKey(filterModalKey)) {
nextFilters.indicatorTree = [];
}
return nextFilters;
});
if (shouldReloadIndicatorTree) {
if (isTemplateFilterKey(filterModalKey)) {
resetIndicatorTreeState();
}
setChartDataBySelection({});
@ -1579,7 +1617,7 @@ function App() {
: isIndicatorTreeFilterKey(filterKey)
? defaultIndicatorTreeNodes
: [];
if (isSameFilterSelection(appliedFilters[filterKey], nextNodes)) {
if (appliedFilters[filterKey].length === nextNodes.length && appliedFilters[filterKey].every((node, index) => node.id === nextNodes[index]?.id)) {
return;
}
setAppliedFilters((current) => {
@ -1699,14 +1737,14 @@ function App() {
return;
}
const response = await fetch(`${API_BASE_URL}/zw/getBuildingFunctionCostStatsBatch`, {
const response = await fetch(`${API_BASE_URL}${API_ROUTES.statsBatch}`, {
method: 'POST',
signal: controller.signal,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
groupBy: 'year',
groupBy: groupKey,
metric: requestMetricKey,
templateId: selectedTemplateId,
filters: appliedFilterPayload,
@ -1753,7 +1791,7 @@ function App() {
return () => {
controller.abort();
};
}, [appliedFilterPayload, chartQueryVersion, metricKey, requestMetricKey, selectedContentNodes, selectedTemplateId]);
}, [appliedFilterPayload, chartQueryVersion, groupKey, metricKey, requestMetricKey, selectedContentNodes, selectedTemplateId]);
useEffect(() => {
const frame = chartFrameRef.current;
@ -1792,7 +1830,6 @@ function App() {
indicatorButton.classList.add('chart-indicator-button');
enableCustomToolbarButton(indicatorButton);
setButtonAttribute(indicatorButton, 'aria-expanded', String(filterModalKey === 'indicatorTree'));
setButtonAttribute(indicatorButton, 'title', '指标树形');
}
const button = getFullscreenButton();
@ -1941,7 +1978,7 @@ function App() {
}, [chartViewKey, fitPivotGridColumns, metricKey, pivotGridRowData.length, rightPanelCollapsed]);
const chartOptions = useMemo<AgCartesianChartOptions>(() => {
const trendData = groupNames.map((groupName) => {
const visibleData = groupNames.map((groupName) => {
const row: Record<string, string | number | null> = { groupName };
selectedContentNodes.forEach((node) => {
const datum = chartDataBySelection[getSelectionKey(node.contentKey, node.id)]?.find((item) => item.groupName === groupName);
@ -1949,32 +1986,6 @@ function App() {
});
return row;
});
const series = selectedContentNodes.map((node) => ({
type: 'line' as const,
xKey: 'groupName',
yKey: getSeriesValueKey(node.contentKey, node.id),
yName: `${node.label} ${seriesValueLabel}`,
stroke: node.color,
strokeWidth: 2,
connectMissingData: true,
marker: {
enabled: true,
fill: node.color,
stroke: node.color,
size: 5,
},
interpolation: {
type: 'smooth' as const,
},
tooltip: {
renderer: ({ datum, yKey, yName }: { datum: Record<string, unknown>; yKey: string; yName?: string }) => ({
title: yName ?? '',
data: [
{ label: selectedMetric.label, value: formatChartValue(Number(datum[yKey]), metricKey) },
],
}),
},
}));
return {
theme: {
@ -2004,7 +2015,7 @@ function App() {
bottom: 18,
left: 24,
},
data: trendData,
data: visibleData,
zoom: {
enabled: true,
anchorPointX: 'pointer',
@ -2087,7 +2098,32 @@ function App() {
] as unknown as NonNullable<NonNullable<AgCartesianChartOptions['annotations']>['toolbar']>['buttons']),
},
},
series,
series: selectedContentNodes.map((node) => ({
type: 'line',
xKey: 'groupName',
yKey: getSeriesValueKey(node.contentKey, node.id),
yName: `${node.label} ${seriesValueLabel}`,
stroke: node.color,
strokeWidth: 2,
connectMissingData: true,
marker: {
enabled: true,
fill: node.color,
stroke: node.color,
size: 5,
},
interpolation: {
type: 'smooth',
},
tooltip: {
renderer: ({ datum, yKey, yName }) => ({
title: yName,
data: [
{ label: selectedMetric.label, value: formatChartValue(Number(datum[yKey]), metricKey) },
],
}),
},
})),
axes: {
x: {
type: 'category',
@ -2162,6 +2198,7 @@ function App() {
selectedValueKey,
statisticKey,
]);
const renderMetricSwitcher = (variant: 'chart' | 'grid') => (
<div className={`metric-switcher metric-switcher--${variant}`}>
<button
@ -2239,16 +2276,13 @@ function App() {
type="button"
title="清空全部筛选"
onClick={() => {
const shouldReloadIndicatorTree = !isDefaultTemplateSelection(appliedFilters.templateLibrary);
if (shouldReloadIndicatorTree) {
resetIndicatorTreeState();
}
setAppliedFilters({
templateLibrary: getDefaultTemplateFilterNodes(),
indicatorTree: [],
indicatorTree: defaultIndicatorTreeNodes,
region: [],
geoLocation: [],
facilityType: [],
buildingFunction: [],
constructionStage: [],
planningForm: [],
});

View File

@ -2,8 +2,6 @@ import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { AllCommunityModule, ModuleRegistry } from 'ag-charts-community';
import App from './App';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-quartz.css';
import './styles.css';
ModuleRegistry.registerModules([AllCommunityModule]);
@ -14,7 +12,7 @@ declare global {
}
}
const chartContainerId = 'ztChart';
const chartContainerId = 'sbChart';
const getRootRegistry = () => {
window.__chartReactRoots ??= new WeakMap<Element, ReturnType<typeof createRoot>>();

View File

@ -64,7 +64,7 @@ button {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: minmax(0, 7fr) minmax(320px, 3fr);
grid-template-columns: minmax(540px, 52vw) 1fr;
grid-template-rows: auto minmax(0, 1fr);
gap: 12px 28px;
height: 100vh;
@ -250,7 +250,7 @@ button {
position: relative;
z-index: 10;
display: grid;
grid-template-columns: minmax(0, 7fr) minmax(320px, 3fr);
grid-template-columns: minmax(540px, 52vw) 1fr;
grid-template-rows: auto minmax(0, 1fr);
gap: 12px 28px;
width: 100vw;

View File

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

View File

@ -1,8 +1,8 @@
VITE v7.3.2 ready in 303 ms
VITE v7.3.2 ready in 306 ms
➜ Local: http://localhost:5173/
➜ Network: http://100.106.162.120:5173/
➜ Network: http://192.168.1.155:5173/
➜ Network: http://172.31.112.1:5173/
➜ Local: http://localhost: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

View File

@ -1,5 +1,6 @@
VITE v7.3.2 ready in 283 ms
VITE v7.3.2 ready in 307 ms
➜ Local: http://127.0.0.1:5173/
 ➜ press h + enter to show help
16:10:18 [vite] (client) hmr update /src/App.tsx