diff --git a/.playwright-mcp/console-2026-05-12T03-32-51-357Z.log b/.playwright-mcp/console-2026-05-12T03-32-51-357Z.log new file mode 100644 index 0000000..33593cb --- /dev/null +++ b/.playwright-mcp/console-2026-05-12T03-32-51-357Z.log @@ -0,0 +1,2 @@ +[ 1001ms] [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 +[ 1337ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://127.0.0.1:5173/favicon.ico:0 diff --git a/.playwright-mcp/console-2026-05-12T03-33-46-269Z.log b/.playwright-mcp/console-2026-05-12T03-33-46-269Z.log new file mode 100644 index 0000000..ea07102 --- /dev/null +++ b/.playwright-mcp/console-2026-05-12T03-33-46-269Z.log @@ -0,0 +1,8 @@ +[ 79ms] [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 +[ 255622ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"reset"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://127.0.0.1:5173/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=ae174d51:834 +[ 255658ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"reset"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://127.0.0.1:5173/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=ae174d51:834 +[ 268128ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"reset"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://127.0.0.1:5173/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=ae174d51:834 +[ 268149ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"reset"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://127.0.0.1:5173/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=ae174d51:834 +[ 282018ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"reset"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://127.0.0.1:5173/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=ae174d51:834 +[ 282037ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"reset"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://127.0.0.1:5173/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=ae174d51:834 +[ 282064ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"reset"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://127.0.0.1:5173/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=ae174d51:834 diff --git a/.playwright-mcp/console-2026-05-12T03-38-57-686Z.log b/.playwright-mcp/console-2026-05-12T03-38-57-686Z.log new file mode 100644 index 0000000..76d949d --- /dev/null +++ b/.playwright-mcp/console-2026-05-12T03-38-57-686Z.log @@ -0,0 +1,4 @@ +[ 47ms] [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 +[ 194ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"reset"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://127.0.0.1:5173/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=ae174d51:834 +[ 244ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"reset"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://127.0.0.1:5173/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=ae174d51:834 +[ 293ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"reset"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://127.0.0.1:5173/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=ae174d51:834 diff --git a/.playwright-mcp/console-2026-05-12T03-39-25-518Z.log b/.playwright-mcp/console-2026-05-12T03-39-25-518Z.log new file mode 100644 index 0000000..6615f89 --- /dev/null +++ b/.playwright-mcp/console-2026-05-12T03-39-25-518Z.log @@ -0,0 +1 @@ +[ 26ms] [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 diff --git a/.playwright-mcp/console-2026-05-12T03-41-17-981Z.log b/.playwright-mcp/console-2026-05-12T03-41-17-981Z.log new file mode 100644 index 0000000..44865ab --- /dev/null +++ b/.playwright-mcp/console-2026-05-12T03-41-17-981Z.log @@ -0,0 +1,2 @@ +[ 32ms] [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 +[ 371068ms] [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 diff --git a/.playwright-mcp/console-2026-05-12T03-48-27-372Z.log b/.playwright-mcp/console-2026-05-12T03-48-27-372Z.log new file mode 100644 index 0000000..5d4153c --- /dev/null +++ b/.playwright-mcp/console-2026-05-12T03-48-27-372Z.log @@ -0,0 +1 @@ +[ 99ms] [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 diff --git a/.playwright-mcp/console-2026-05-12T03-50-50-051Z.log b/.playwright-mcp/console-2026-05-12T03-50-50-051Z.log new file mode 100644 index 0000000..cf863b3 --- /dev/null +++ b/.playwright-mcp/console-2026-05-12T03-50-50-051Z.log @@ -0,0 +1,5 @@ +[ 77ms] [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 +[ 1186370ms] [ERROR] The final argument passed to %s changed size between renders. The order and size of this array must remain constant. + +Previous: %s +Incoming: %s useEffect [false] [[object Object], , [object Object], false] @ http://127.0.0.1:5173/node_modules/.vite/deps/react-dom_client.js?v=ae174d51:5627 diff --git a/.playwright-mcp/console-2026-05-12T04-17-37-255Z.log b/.playwright-mcp/console-2026-05-12T04-17-37-255Z.log new file mode 100644 index 0000000..38910c8 --- /dev/null +++ b/.playwright-mcp/console-2026-05-12T04-17-37-255Z.log @@ -0,0 +1 @@ +[ 854ms] [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 diff --git a/.playwright-mcp/console-2026-05-12T04-19-14-036Z.log b/.playwright-mcp/console-2026-05-12T04-19-14-036Z.log new file mode 100644 index 0000000..68165fe --- /dev/null +++ b/.playwright-mcp/console-2026-05-12T04-19-14-036Z.log @@ -0,0 +1 @@ +[ 88ms] [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 diff --git a/.playwright-mcp/console-2026-05-12T04-27-12-424Z.log b/.playwright-mcp/console-2026-05-12T04-27-12-424Z.log new file mode 100644 index 0000000..1aee6e2 --- /dev/null +++ b/.playwright-mcp/console-2026-05-12T04-27-12-424Z.log @@ -0,0 +1,5 @@ +[ 219ms] [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 +[ 139728ms] [ERROR] Failed to load resource: the server responded with a status of 401 () @ https://nest.zwgczx.com/api/v1/zw/getBuildingFunctionCostFilterTree?key=templateLibrary¤ttime=1778560172021&__random__=1778560172021:0 +[ 142705ms] [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¤ttime=1778560175114&__random__=1778560175114:0 +[ 304362ms] [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 +[ 307069ms] [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¤ttime=1778560339458&__random__=1778560339458:0 diff --git a/.playwright-mcp/page-2026-05-12T03-32-52-698Z.yml b/.playwright-mcp/page-2026-05-12T03-32-52-698Z.yml new file mode 100644 index 0000000..9cd8a9d --- /dev/null +++ b/.playwright-mcp/page-2026-05-12T03-32-52-698Z.yml @@ -0,0 +1,81 @@ +- 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 "模板库 1" [pressed] [ref=e6] [cursor=pointer]: + - img [ref=e7] + - generic [ref=e9]: 模板库 + - strong [ref=e10]: "1" + - button "指标树形" [ref=e11] [cursor=pointer]: + - img [ref=e12] + - generic [ref=e19]: 指标树形 + - button "省市区" [ref=e20] [cursor=pointer]: + - img [ref=e21] + - generic [ref=e25]: 省市区 + - button "自然地理区位" [ref=e26] [cursor=pointer]: + - img [ref=e27] + - generic [ref=e30]: 自然地理区位 + - button "设施类别" [ref=e31] [cursor=pointer]: + - img [ref=e32] + - generic [ref=e36]: 设施类别 + - button "建设阶段" [ref=e37] [cursor=pointer]: + - img [ref=e38] + - generic [ref=e43]: 建设阶段 + - button "规划形式" [ref=e44] [cursor=pointer]: + - img [ref=e45] + - generic [ref=e50]: 规划形式 + - region "年度总费用图表" [ref=e51]: + - generic [ref=e52]: + - button "纵坐标:造价(元)" [ref=e54] [cursor=pointer]: 造价(元) + - button "全屏(F11)" [ref=e55] [cursor=pointer] + - generic [ref=e57]: + - figure "图表,共有0个系列": + - generic [ref=e58]: + - img "interactive chart": + - generic: + - img + - img + - region [ref=e59] + - toolbar "标注" [ref=e60]: + - button "均" [disabled] [ref=e61]: + - generic: 均 + - button "Line Tool" [disabled] [ref=e62] + - button "Text Tool" [disabled] [ref=e63] + - button "Shape Tool" [disabled] [ref=e64] + - button "Fibonacci Tool" [disabled] [ref=e65] + - button "Clear annotations" [disabled] [ref=e66] + - status: + - generic: 请选择右侧分类项 + - toolbar "缩放" [ref=e67]: + - button "缩小" [disabled] [ref=e68] + - button "放大" [ref=e69] [cursor=pointer] + - button "左移" [disabled] [ref=e70] + - button "右移" [disabled] [ref=e71] + - button "重置" [disabled] [ref=e72] + - complementary "选择内容" [ref=e73]: + - tablist "选择内容切换项" [ref=e74]: + - tab "自然地理区位" [selected] [ref=e75] [cursor=pointer] + - tab "设施类别" [ref=e76] [cursor=pointer] + - tab "建设阶段" [ref=e77] [cursor=pointer] + - tab "规划形式" [ref=e78] [cursor=pointer] + - generic [ref=e79]: + - generic [ref=e80]: 自然地理区位 + - generic [ref=e81]: 加载中 \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-12T03-33-46-589Z.yml b/.playwright-mcp/page-2026-05-12T03-33-46-589Z.yml new file mode 100644 index 0000000..9cd8a9d --- /dev/null +++ b/.playwright-mcp/page-2026-05-12T03-33-46-589Z.yml @@ -0,0 +1,81 @@ +- 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 "模板库 1" [pressed] [ref=e6] [cursor=pointer]: + - img [ref=e7] + - generic [ref=e9]: 模板库 + - strong [ref=e10]: "1" + - button "指标树形" [ref=e11] [cursor=pointer]: + - img [ref=e12] + - generic [ref=e19]: 指标树形 + - button "省市区" [ref=e20] [cursor=pointer]: + - img [ref=e21] + - generic [ref=e25]: 省市区 + - button "自然地理区位" [ref=e26] [cursor=pointer]: + - img [ref=e27] + - generic [ref=e30]: 自然地理区位 + - button "设施类别" [ref=e31] [cursor=pointer]: + - img [ref=e32] + - generic [ref=e36]: 设施类别 + - button "建设阶段" [ref=e37] [cursor=pointer]: + - img [ref=e38] + - generic [ref=e43]: 建设阶段 + - button "规划形式" [ref=e44] [cursor=pointer]: + - img [ref=e45] + - generic [ref=e50]: 规划形式 + - region "年度总费用图表" [ref=e51]: + - generic [ref=e52]: + - button "纵坐标:造价(元)" [ref=e54] [cursor=pointer]: 造价(元) + - button "全屏(F11)" [ref=e55] [cursor=pointer] + - generic [ref=e57]: + - figure "图表,共有0个系列": + - generic [ref=e58]: + - img "interactive chart": + - generic: + - img + - img + - region [ref=e59] + - toolbar "标注" [ref=e60]: + - button "均" [disabled] [ref=e61]: + - generic: 均 + - button "Line Tool" [disabled] [ref=e62] + - button "Text Tool" [disabled] [ref=e63] + - button "Shape Tool" [disabled] [ref=e64] + - button "Fibonacci Tool" [disabled] [ref=e65] + - button "Clear annotations" [disabled] [ref=e66] + - status: + - generic: 请选择右侧分类项 + - toolbar "缩放" [ref=e67]: + - button "缩小" [disabled] [ref=e68] + - button "放大" [ref=e69] [cursor=pointer] + - button "左移" [disabled] [ref=e70] + - button "右移" [disabled] [ref=e71] + - button "重置" [disabled] [ref=e72] + - complementary "选择内容" [ref=e73]: + - tablist "选择内容切换项" [ref=e74]: + - tab "自然地理区位" [selected] [ref=e75] [cursor=pointer] + - tab "设施类别" [ref=e76] [cursor=pointer] + - tab "建设阶段" [ref=e77] [cursor=pointer] + - tab "规划形式" [ref=e78] [cursor=pointer] + - generic [ref=e79]: + - generic [ref=e80]: 自然地理区位 + - generic [ref=e81]: 加载中 \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-12T03-38-57-987Z.yml b/.playwright-mcp/page-2026-05-12T03-38-57-987Z.yml new file mode 100644 index 0000000..2e2288d --- /dev/null +++ b/.playwright-mcp/page-2026-05-12T03-38-57-987Z.yml @@ -0,0 +1,81 @@ +- 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 "模板库 1" [pressed] [ref=e6] [cursor=pointer]: + - img [ref=e7] + - generic [ref=e9]: 模板库 + - strong [ref=e10]: "1" + - button "指标树形" [ref=e11] [cursor=pointer]: + - img [ref=e12] + - generic [ref=e19]: 指标树形 + - button "省市区" [ref=e20] [cursor=pointer]: + - img [ref=e21] + - generic [ref=e25]: 省市区 + - button "自然地理区位" [ref=e26] [cursor=pointer]: + - img [ref=e27] + - generic [ref=e30]: 自然地理区位 + - button "设施类别" [ref=e31] [cursor=pointer]: + - img [ref=e32] + - generic [ref=e36]: 设施类别 + - button "建设阶段" [ref=e37] [cursor=pointer]: + - img [ref=e38] + - generic [ref=e43]: 建设阶段 + - button "规划形式" [ref=e44] [cursor=pointer]: + - img [ref=e45] + - generic [ref=e50]: 规划形式 + - region "年度总费用图表" [ref=e51]: + - generic [ref=e52]: + - button "纵坐标:造价(元)" [ref=e54] [cursor=pointer]: 造价(元) + - generic [ref=e55]: + - figure "图表,共有0个系列": + - generic [ref=e56]: + - img "interactive chart": + - generic: + - img + - img + - region [ref=e57] + - toolbar "标注" [ref=e58]: + - button "全屏(F11)" [disabled] [ref=e59] + - button "均" [disabled] [ref=e60]: + - generic: 均 + - button "Line Tool" [disabled] [ref=e61] + - button "Text Tool" [disabled] [ref=e62] + - button "Shape Tool" [disabled] [ref=e63] + - button "Fibonacci Tool" [disabled] [ref=e64] + - button "Clear annotations" [disabled] [ref=e65] + - status: + - generic: 请选择右侧分类项 + - toolbar "缩放" [ref=e66]: + - button "缩小" [disabled] [ref=e67] + - button "放大" [ref=e68] [cursor=pointer] + - button "左移" [disabled] [ref=e69] + - button "右移" [disabled] [ref=e70] + - button "重置" [disabled] [ref=e71] + - complementary "选择内容" [ref=e72]: + - tablist "选择内容切换项" [ref=e73]: + - tab "自然地理区位" [selected] [ref=e74] [cursor=pointer] + - tab "设施类别" [ref=e75] [cursor=pointer] + - tab "建设阶段" [ref=e76] [cursor=pointer] + - tab "规划形式" [ref=e77] [cursor=pointer] + - generic [ref=e78]: + - generic [ref=e79]: 自然地理区位 + - generic [ref=e80]: 加载中 \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-12T03-39-25-782Z.yml b/.playwright-mcp/page-2026-05-12T03-39-25-782Z.yml new file mode 100644 index 0000000..2e2288d --- /dev/null +++ b/.playwright-mcp/page-2026-05-12T03-39-25-782Z.yml @@ -0,0 +1,81 @@ +- 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 "模板库 1" [pressed] [ref=e6] [cursor=pointer]: + - img [ref=e7] + - generic [ref=e9]: 模板库 + - strong [ref=e10]: "1" + - button "指标树形" [ref=e11] [cursor=pointer]: + - img [ref=e12] + - generic [ref=e19]: 指标树形 + - button "省市区" [ref=e20] [cursor=pointer]: + - img [ref=e21] + - generic [ref=e25]: 省市区 + - button "自然地理区位" [ref=e26] [cursor=pointer]: + - img [ref=e27] + - generic [ref=e30]: 自然地理区位 + - button "设施类别" [ref=e31] [cursor=pointer]: + - img [ref=e32] + - generic [ref=e36]: 设施类别 + - button "建设阶段" [ref=e37] [cursor=pointer]: + - img [ref=e38] + - generic [ref=e43]: 建设阶段 + - button "规划形式" [ref=e44] [cursor=pointer]: + - img [ref=e45] + - generic [ref=e50]: 规划形式 + - region "年度总费用图表" [ref=e51]: + - generic [ref=e52]: + - button "纵坐标:造价(元)" [ref=e54] [cursor=pointer]: 造价(元) + - generic [ref=e55]: + - figure "图表,共有0个系列": + - generic [ref=e56]: + - img "interactive chart": + - generic: + - img + - img + - region [ref=e57] + - toolbar "标注" [ref=e58]: + - button "全屏(F11)" [disabled] [ref=e59] + - button "均" [disabled] [ref=e60]: + - generic: 均 + - button "Line Tool" [disabled] [ref=e61] + - button "Text Tool" [disabled] [ref=e62] + - button "Shape Tool" [disabled] [ref=e63] + - button "Fibonacci Tool" [disabled] [ref=e64] + - button "Clear annotations" [disabled] [ref=e65] + - status: + - generic: 请选择右侧分类项 + - toolbar "缩放" [ref=e66]: + - button "缩小" [disabled] [ref=e67] + - button "放大" [ref=e68] [cursor=pointer] + - button "左移" [disabled] [ref=e69] + - button "右移" [disabled] [ref=e70] + - button "重置" [disabled] [ref=e71] + - complementary "选择内容" [ref=e72]: + - tablist "选择内容切换项" [ref=e73]: + - tab "自然地理区位" [selected] [ref=e74] [cursor=pointer] + - tab "设施类别" [ref=e75] [cursor=pointer] + - tab "建设阶段" [ref=e76] [cursor=pointer] + - tab "规划形式" [ref=e77] [cursor=pointer] + - generic [ref=e78]: + - generic [ref=e79]: 自然地理区位 + - generic [ref=e80]: 加载中 \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-12T03-41-18-222Z.yml b/.playwright-mcp/page-2026-05-12T03-41-18-222Z.yml new file mode 100644 index 0000000..2e2288d --- /dev/null +++ b/.playwright-mcp/page-2026-05-12T03-41-18-222Z.yml @@ -0,0 +1,81 @@ +- 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 "模板库 1" [pressed] [ref=e6] [cursor=pointer]: + - img [ref=e7] + - generic [ref=e9]: 模板库 + - strong [ref=e10]: "1" + - button "指标树形" [ref=e11] [cursor=pointer]: + - img [ref=e12] + - generic [ref=e19]: 指标树形 + - button "省市区" [ref=e20] [cursor=pointer]: + - img [ref=e21] + - generic [ref=e25]: 省市区 + - button "自然地理区位" [ref=e26] [cursor=pointer]: + - img [ref=e27] + - generic [ref=e30]: 自然地理区位 + - button "设施类别" [ref=e31] [cursor=pointer]: + - img [ref=e32] + - generic [ref=e36]: 设施类别 + - button "建设阶段" [ref=e37] [cursor=pointer]: + - img [ref=e38] + - generic [ref=e43]: 建设阶段 + - button "规划形式" [ref=e44] [cursor=pointer]: + - img [ref=e45] + - generic [ref=e50]: 规划形式 + - region "年度总费用图表" [ref=e51]: + - generic [ref=e52]: + - button "纵坐标:造价(元)" [ref=e54] [cursor=pointer]: 造价(元) + - generic [ref=e55]: + - figure "图表,共有0个系列": + - generic [ref=e56]: + - img "interactive chart": + - generic: + - img + - img + - region [ref=e57] + - toolbar "标注" [ref=e58]: + - button "全屏(F11)" [disabled] [ref=e59] + - button "均" [disabled] [ref=e60]: + - generic: 均 + - button "Line Tool" [disabled] [ref=e61] + - button "Text Tool" [disabled] [ref=e62] + - button "Shape Tool" [disabled] [ref=e63] + - button "Fibonacci Tool" [disabled] [ref=e64] + - button "Clear annotations" [disabled] [ref=e65] + - status: + - generic: 请选择右侧分类项 + - toolbar "缩放" [ref=e66]: + - button "缩小" [disabled] [ref=e67] + - button "放大" [ref=e68] [cursor=pointer] + - button "左移" [disabled] [ref=e69] + - button "右移" [disabled] [ref=e70] + - button "重置" [disabled] [ref=e71] + - complementary "选择内容" [ref=e72]: + - tablist "选择内容切换项" [ref=e73]: + - tab "自然地理区位" [selected] [ref=e74] [cursor=pointer] + - tab "设施类别" [ref=e75] [cursor=pointer] + - tab "建设阶段" [ref=e76] [cursor=pointer] + - tab "规划形式" [ref=e77] [cursor=pointer] + - generic [ref=e78]: + - generic [ref=e79]: 自然地理区位 + - generic [ref=e80]: 加载中 \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-12T03-48-27-706Z.yml b/.playwright-mcp/page-2026-05-12T03-48-27-706Z.yml new file mode 100644 index 0000000..39185e9 --- /dev/null +++ b/.playwright-mcp/page-2026-05-12T03-48-27-706Z.yml @@ -0,0 +1,81 @@ +- 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 "模板库 1" [pressed] [ref=e6] [cursor=pointer]: + - img [ref=e7] + - generic [ref=e9]: 模板库 + - strong [ref=e10]: "1" + - button "指标树形" [ref=e11] [cursor=pointer]: + - img [ref=e12] + - generic [ref=e19]: 指标树形 + - button "省市区" [ref=e20] [cursor=pointer]: + - img [ref=e21] + - generic [ref=e25]: 省市区 + - button "自然地理区位" [ref=e26] [cursor=pointer]: + - img [ref=e27] + - generic [ref=e30]: 自然地理区位 + - button "设施类别" [ref=e31] [cursor=pointer]: + - img [ref=e32] + - generic [ref=e36]: 设施类别 + - button "建设阶段" [ref=e37] [cursor=pointer]: + - img [ref=e38] + - generic [ref=e43]: 建设阶段 + - button "规划形式" [ref=e44] [cursor=pointer]: + - img [ref=e45] + - generic [ref=e50]: 规划形式 + - region "年度总费用图表" [ref=e51]: + - generic [ref=e52]: + - button "纵坐标:造价(元)" [ref=e54] [cursor=pointer]: 造价(元) + - generic [ref=e55]: + - figure "图表,共有0个系列": + - generic [ref=e56]: + - img "interactive chart": + - generic: + - img + - img + - region [ref=e57] + - toolbar "标注" [ref=e58]: + - button "均" [disabled] [ref=e59]: + - generic: 均 + - button "Line Tool" [disabled] [ref=e60] + - button "Text Tool" [disabled] [ref=e61] + - button "Shape Tool" [disabled] [ref=e62] + - button "Fibonacci Tool" [disabled] [ref=e63] + - button "全屏(F11)" [disabled] [ref=e64] + - button "Clear annotations" [disabled] [ref=e65] + - status: + - generic: 请选择右侧分类项 + - toolbar "缩放" [ref=e66]: + - button "缩小" [disabled] [ref=e67] + - button "放大" [ref=e68] [cursor=pointer] + - button "左移" [disabled] [ref=e69] + - button "右移" [disabled] [ref=e70] + - button "重置" [disabled] [ref=e71] + - complementary "选择内容" [ref=e72]: + - tablist "选择内容切换项" [ref=e73]: + - tab "自然地理区位" [selected] [ref=e74] [cursor=pointer] + - tab "设施类别" [ref=e75] [cursor=pointer] + - tab "建设阶段" [ref=e76] [cursor=pointer] + - tab "规划形式" [ref=e77] [cursor=pointer] + - generic [ref=e78]: + - generic [ref=e79]: 自然地理区位 + - generic [ref=e80]: 加载中 \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-12T03-50-50-349Z.yml b/.playwright-mcp/page-2026-05-12T03-50-50-349Z.yml new file mode 100644 index 0000000..39185e9 --- /dev/null +++ b/.playwright-mcp/page-2026-05-12T03-50-50-349Z.yml @@ -0,0 +1,81 @@ +- 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 "模板库 1" [pressed] [ref=e6] [cursor=pointer]: + - img [ref=e7] + - generic [ref=e9]: 模板库 + - strong [ref=e10]: "1" + - button "指标树形" [ref=e11] [cursor=pointer]: + - img [ref=e12] + - generic [ref=e19]: 指标树形 + - button "省市区" [ref=e20] [cursor=pointer]: + - img [ref=e21] + - generic [ref=e25]: 省市区 + - button "自然地理区位" [ref=e26] [cursor=pointer]: + - img [ref=e27] + - generic [ref=e30]: 自然地理区位 + - button "设施类别" [ref=e31] [cursor=pointer]: + - img [ref=e32] + - generic [ref=e36]: 设施类别 + - button "建设阶段" [ref=e37] [cursor=pointer]: + - img [ref=e38] + - generic [ref=e43]: 建设阶段 + - button "规划形式" [ref=e44] [cursor=pointer]: + - img [ref=e45] + - generic [ref=e50]: 规划形式 + - region "年度总费用图表" [ref=e51]: + - generic [ref=e52]: + - button "纵坐标:造价(元)" [ref=e54] [cursor=pointer]: 造价(元) + - generic [ref=e55]: + - figure "图表,共有0个系列": + - generic [ref=e56]: + - img "interactive chart": + - generic: + - img + - img + - region [ref=e57] + - toolbar "标注" [ref=e58]: + - button "均" [disabled] [ref=e59]: + - generic: 均 + - button "Line Tool" [disabled] [ref=e60] + - button "Text Tool" [disabled] [ref=e61] + - button "Shape Tool" [disabled] [ref=e62] + - button "Fibonacci Tool" [disabled] [ref=e63] + - button "全屏(F11)" [disabled] [ref=e64] + - button "Clear annotations" [disabled] [ref=e65] + - status: + - generic: 请选择右侧分类项 + - toolbar "缩放" [ref=e66]: + - button "缩小" [disabled] [ref=e67] + - button "放大" [ref=e68] [cursor=pointer] + - button "左移" [disabled] [ref=e69] + - button "右移" [disabled] [ref=e70] + - button "重置" [disabled] [ref=e71] + - complementary "选择内容" [ref=e72]: + - tablist "选择内容切换项" [ref=e73]: + - tab "自然地理区位" [selected] [ref=e74] [cursor=pointer] + - tab "设施类别" [ref=e75] [cursor=pointer] + - tab "建设阶段" [ref=e76] [cursor=pointer] + - tab "规划形式" [ref=e77] [cursor=pointer] + - generic [ref=e78]: + - generic [ref=e79]: 自然地理区位 + - generic [ref=e80]: 加载中 \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-12T04-17-38-470Z.yml b/.playwright-mcp/page-2026-05-12T04-17-38-470Z.yml new file mode 100644 index 0000000..9dc7157 --- /dev/null +++ b/.playwright-mcp/page-2026-05-12T04-17-38-470Z.yml @@ -0,0 +1,80 @@ +- 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]: + - generic: + - generic: 库 + - button "树" [disabled] [ref=e46]: + - generic: + - generic: 树 + - button "均" [disabled] [ref=e47]: + - 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] + - 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]: 加载中 \ No newline at end of file diff --git a/fullscreen-button-position.png b/fullscreen-button-position.png new file mode 100644 index 0000000..52f4792 Binary files /dev/null and b/fullscreen-button-position.png differ diff --git a/src/App.tsx b/src/App.tsx index f0d1000..e707e96 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import { AgCharts } from 'ag-charts-react'; import type { AgCartesianChartOptions } from 'ag-charts-community'; import { ModuleRegistry } from 'ag-charts-community'; -import { Building2, Construction, LayoutGrid, LocateFixed, MapPinned } from 'lucide-react'; +import { Building2, Construction, LayoutGrid, Library, LocateFixed, MapPinned, Waypoints } from 'lucide-react'; import { AnnotationsModule, ContextMenuModule, @@ -41,12 +41,15 @@ const contentOptions = [ ] as const; const filterOptions = [ + { key: 'templateLibrary', label: '模板库', icon: Library }, + { key: 'indicatorTree', label: '指标树形', icon: Waypoints }, { key: 'region', label: '省市区', icon: MapPinned }, { key: 'geoLocation', label: '自然地理区位', icon: LocateFixed }, { key: 'facilityType', label: '设施类别', icon: Building2 }, { key: 'constructionStage', label: '建设阶段', icon: Construction }, { key: 'planningForm', label: '规划形式', icon: LayoutGrid }, ] as const; +const chartFilterOptions = filterOptions.filter((option) => option.key !== 'templateLibrary' && option.key !== 'indicatorTree'); const browserTreeDefaults = { treetype: '256', @@ -96,6 +99,11 @@ const contentTreeConfigs = { } as const; const chartLineColors = ['#0078a8', '#d14d72', '#1f8f4d', '#d96f23', '#6b5cc8', '#0d7680', '#9a6b12', '#b24b38']; +const defaultTemplateFilterNode = { + id: '3', + filterKey: 'templateLibrary', + label: '默认模板', +} as const; // const mockGeoLocationPayload = { // checkStrictly: true, @@ -291,6 +299,60 @@ function normalizeTreeRows(rows: unknown[]): TreeNode[] { }); } +function normalizeFlatTemplateLibraryRows(rows: unknown[]): TreeNode[] { + return rows + .filter((row): row is Record => !!row && typeof row === 'object') + .map((row, index) => { + const id = readText(row, ['id', 'mainid', 'mbid']) || `node-${index}`; + const label = readText(row, ['mbmc', 'label', 'name', 'title', 'text']) || id; + return createFilterTreeNode(id, label); + }); +} + +function normalizeFlatIndicatorRows(rows: unknown[]): TreeNode[] { + const sourceRows = rows.filter((row): row is Record => !!row && typeof row === 'object'); + const rowsById = new Map>(); + const childrenByParent = new Map(); + + sourceRows.forEach((row, index) => { + const id = readText(row, ['zbid', 'zjzbk', 'id']) || `node-${index}`; + if (!id) return; + rowsById.set(id, row); + + const parentId = readText(row, ['sj', 'parentId', 'parentid', 'pid', 'pId']) || ''; + const children = childrenByParent.get(parentId) || []; + children.push(id); + childrenByParent.set(parentId, children); + }); + + const buildNode = (id: string): TreeNode => { + const row = rowsById.get(id) || {}; + const children = (childrenByParent.get(id) || []) + .filter((childId) => rowsById.has(childId)) + .map(buildNode); + const label = readText(row, ['label', 'name', 'title', 'text', 'zbbh', 'mbmc']) || id; + return { + id, + label, + children, + hasChildren: children.length > 0, + canClick: true, + expanded: children.length > 0, + loading: false, + loaded: true, + }; + }; + + return Array.from(rowsById.keys()) + .filter((id) => { + const row = rowsById.get(id); + if (!row) return false; + const parentId = readText(row, ['sj', 'parentId', 'parentid', 'pid', 'pId']) || ''; + return !parentId || parentId === '0' || parentId === '0_0' || !rowsById.has(parentId); + }) + .map(buildNode); +} + const regionFieldKeys = { provinceId: ['provinceId', 'province_id', 'provinceid', 'sfid', 'sf_id', 'shengId', 'sheng_id', 'sheng'], provinceName: ['provinceName', 'province_name', 'province', 'sfmc', 'sf', 'shengName', 'sheng_name', 'shengmc'], @@ -384,12 +446,56 @@ function getFilterSelectionKey(filterKey: FilterKey, nodeId: string) { return `${filterKey}:${nodeId}`; } -function getSeriesValueKey(index: number) { - return `amount${index}`; +function getSeriesValueKey(contentKey: ContentKey, nodeId: string) { + return `amount_${getSelectionKey(contentKey, nodeId).replace(/[^a-zA-Z0-9_]/g, '_')}`; +} + +function compareGroupNames(a: string, b: string) { + const numberA = Number(a); + const numberB = Number(b); + if (Number.isFinite(numberA) && Number.isFinite(numberB)) { + return numberA - numberB; + } + return a.localeCompare(b, 'zh-CN', { numeric: true }); } function isContentFilterKey(filterKey: FilterKey): filterKey is ContentKey { - return filterKey !== 'region'; + return Object.prototype.hasOwnProperty.call(contentTreeConfigs, filterKey); +} + +function isTemplateFilterKey(filterKey: FilterKey): filterKey is 'templateLibrary' { + return filterKey === 'templateLibrary'; +} + +function isIndicatorTreeFilterKey(filterKey: FilterKey): filterKey is 'indicatorTree' { + return filterKey === 'indicatorTree'; +} + +function isSingleSelectFilterKey(filterKey: FilterKey) { + return isTemplateFilterKey(filterKey) || isIndicatorTreeFilterKey(filterKey); +} + +function getDefaultTemplateFilterNodes(): SelectedFilterNode[] { + return [{ ...defaultTemplateFilterNode }]; +} + +function isDefaultTemplateSelection(nodes: SelectedFilterNode[]) { + return nodes.length === 1 && nodes[0]?.id === defaultTemplateFilterNode.id; +} + +function getDefaultIndicatorTreeFilterNodes(nodes: TreeNode[]): SelectedFilterNode[] { + const defaultNode = nodes[0]; + if (!defaultNode) return []; + return [{ + id: defaultNode.id, + filterKey: 'indicatorTree', + label: defaultNode.label, + }]; +} + +function isDefaultIndicatorTreeSelection(nodes: SelectedFilterNode[], treeNodes: TreeNode[]) { + const defaultNode = treeNodes[0]; + return Boolean(defaultNode) && nodes.length === 1 && nodes[0]?.id === defaultNode?.id; } function filterTreeNodesByKeyword(nodes: TreeNode[], keyword: string): TreeNode[] { @@ -516,6 +622,8 @@ function App() { planningForm: false, }); const filterTreeInitialLoadStartedRef = useRef>({ + templateLibrary: false, + indicatorTree: false, region: false, geoLocation: false, facilityType: false, @@ -553,6 +661,8 @@ function App() { const [loadError, setLoadError] = useState(null); const [loadingHint, setLoadingHint] = useState(''); const [filterTreeByKey, setFilterTreeByKey] = useState>({ + templateLibrary: [], + indicatorTree: [], region: [], geoLocation: [], facilityType: [], @@ -560,6 +670,8 @@ function App() { planningForm: [], }); const [filterTreeLoadingByKey, setFilterTreeLoadingByKey] = useState>({ + templateLibrary: false, + indicatorTree: false, region: false, geoLocation: false, facilityType: false, @@ -567,6 +679,8 @@ function App() { planningForm: false, }); const [filterTreeErrorByKey, setFilterTreeErrorByKey] = useState>({ + templateLibrary: null, + indicatorTree: null, region: null, geoLocation: null, facilityType: null, @@ -574,6 +688,8 @@ function App() { planningForm: null, }); const [filterSearchTreeByKey, setFilterSearchTreeByKey] = useState>({ + templateLibrary: [], + indicatorTree: [], region: [], geoLocation: [], facilityType: [], @@ -581,6 +697,8 @@ function App() { planningForm: [], }); const [filterSearchLoadingByKey, setFilterSearchLoadingByKey] = useState>({ + templateLibrary: false, + indicatorTree: false, region: false, geoLocation: false, facilityType: false, @@ -588,6 +706,8 @@ function App() { planningForm: false, }); const [filterSearchErrorByKey, setFilterSearchErrorByKey] = useState>({ + templateLibrary: null, + indicatorTree: null, region: null, geoLocation: null, facilityType: null, @@ -595,6 +715,8 @@ function App() { planningForm: null, }); const [appliedFilters, setAppliedFilters] = useState>({ + templateLibrary: getDefaultTemplateFilterNodes(), + indicatorTree: [], region: [], geoLocation: [], facilityType: [], @@ -607,6 +729,8 @@ function App() { const filterSearchComposingRef = useRef(false); const filterSearchTimerRef = useRef(null); const filterSearchRequestSeqRef = useRef>({ + templateLibrary: 0, + indicatorTree: 0, region: 0, geoLocation: 0, facilityType: 0, @@ -629,7 +753,14 @@ function App() { const activeFilterTreeError = filterModalKey ? trimmedFilterSearchValue ? filterSearchErrorByKey[filterModalKey] : filterTreeErrorByKey[filterModalKey] : null; - const activeFilterCount = Object.values(appliedFilters).reduce((total, nodes) => total + nodes.length, 0); + const selectedTemplateId = appliedFilters.templateLibrary[0]?.id || defaultTemplateFilterNode.id; + const defaultIndicatorTreeNodes = useMemo( + () => getDefaultIndicatorTreeFilterNodes(filterTreeByKey.indicatorTree), + [filterTreeByKey.indicatorTree], + ); + const activeFilterCount = Object.entries(appliedFilters).reduce((total, [key, nodes]) => ( + key === 'templateLibrary' && isDefaultTemplateSelection(nodes) ? total : total + nodes.length + ), 0); const chartEmptyText = selectedContentNodes.length === 0 ? '请选择右侧分类项' : activeFilterCount > 0 @@ -743,6 +874,36 @@ function App() { return flatRegionTree.length > 0 ? flatRegionTree : normalizeBackendTree(normalizeTreeRows(rows)); }; + const fetchTemplateLibraryTree = async (signal?: AbortSignal) => { + const response = await fetch(`${API_BASE_URL}/zw/getBuildingFunctionCostFilterTree?${buildQuery({ key: 'templateLibrary' })}`, { + signal, + headers: { + 'X-Requested-With': 'XMLHttpRequest', + }, + }); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + const rows = pickArray(await response.json()); + const templateRows = normalizeFlatTemplateLibraryRows(rows); + return templateRows.length > 0 ? templateRows : normalizeBackendTree(normalizeTreeRows(rows)); + }; + + const fetchIndicatorTree = async (signal?: AbortSignal) => { + const response = await fetch(`${API_BASE_URL}/zw/getBuildingFunctionCostFilterTree?${buildQuery({ key: 'indicatorTree', templateId: selectedTemplateId })}`, { + signal, + headers: { + 'X-Requested-With': 'XMLHttpRequest', + }, + }); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + const rows = pickArray(await response.json()); + const indicatorRows = normalizeFlatIndicatorRows(rows); + return indicatorRows.length > 0 ? indicatorRows : normalizeBackendTree(normalizeTreeRows(rows)); + }; + const fetchBackendFilterTreeSearch = async (filterKey: FilterKey, keyword: string, signal?: AbortSignal) => { const response = await fetch(`${API_BASE_URL}/zw/getBuildingFunctionCostFilterTreeSearch?${buildQuery({ key: filterKey, @@ -791,10 +952,21 @@ function App() { const nodes = await fetchRegionFilterTree(signal); return keyword?.trim() ? filterTreeNodesByKeyword(nodes, keyword) : nodes; } - if (keyword?.trim()) { + if (isTemplateFilterKey(filterKey)) { + const nodes = await fetchTemplateLibraryTree(signal); + return keyword?.trim() ? filterTreeNodesByKeyword(nodes, keyword) : nodes; + } + if (filterKey === 'indicatorTree') { + const nodes = await fetchIndicatorTree(signal); + return keyword?.trim() ? filterTreeNodesByKeyword(nodes, keyword) : nodes; + } + if (keyword?.trim() && isContentFilterKey(filterKey)) { return fetchBackendFilterTreeSearch(filterKey, keyword, signal); } - return loadContentTreeWithDefaultExpansion(filterKey); + if (isContentFilterKey(filterKey)) { + return loadContentTreeWithDefaultExpansion(filterKey); + } + return []; }; const toggleContentNode = (nodeId: string) => { @@ -880,6 +1052,17 @@ function App() { setLoading(false); }; + const resetIndicatorTreeState = () => { + filterTreeInitialLoadStartedRef.current.indicatorTree = false; + filterSearchRequestSeqRef.current.indicatorTree += 1; + setFilterTreeByKey((current) => ({ ...current, indicatorTree: [] })); + setFilterTreeLoadingByKey((current) => ({ ...current, indicatorTree: false })); + setFilterTreeErrorByKey((current) => ({ ...current, indicatorTree: null })); + setFilterSearchTreeByKey((current) => ({ ...current, indicatorTree: [] })); + setFilterSearchLoadingByKey((current) => ({ ...current, indicatorTree: false })); + setFilterSearchErrorByKey((current) => ({ ...current, indicatorTree: null })); + }; + const ensureFilterTreeLoaded = (filterKey: FilterKey) => { if (filterTreeByKey[filterKey].length > 0 || filterTreeInitialLoadStartedRef.current[filterKey]) return; @@ -904,7 +1087,11 @@ function App() { const openFilterModal = (filterKey: FilterKey) => { setFilterModalKey(filterKey); - setDraftFilterNodes(appliedFilters[filterKey]); + if (isIndicatorTreeFilterKey(filterKey) && appliedFilters[filterKey].length === 0 && defaultIndicatorTreeNodes.length > 0) { + setDraftFilterNodes(defaultIndicatorTreeNodes); + } else { + setDraftFilterNodes(appliedFilters[filterKey]); + } setFilterSearchValue(''); lastFilterSearchRef.current = ''; if (filterSearchTimerRef.current != null) { @@ -950,6 +1137,19 @@ function App() { return; } + if (isTemplateFilterKey(filterKey) || filterKey === 'indicatorTree') { + filterSearchTimerRef.current = window.setTimeout(() => { + setFilterSearchLoadingByKey((current) => ({ ...current, [filterKey]: true })); + setFilterSearchErrorByKey((current) => ({ ...current, [filterKey]: null })); + setFilterSearchTreeByKey((current) => ({ + ...current, + [filterKey]: filterTreeNodesByKeyword(filterTreeByKey[filterKey], keyword), + })); + setFilterSearchLoadingByKey((current) => ({ ...current, [filterKey]: false })); + }, 300); + return; + } + filterSearchTimerRef.current = window.setTimeout(() => { setFilterSearchLoadingByKey((current) => ({ ...current, [filterKey]: true })); setFilterSearchErrorByKey((current) => ({ ...current, [filterKey]: null })); @@ -1032,6 +1232,12 @@ function App() { setDraftFilterNodes((current) => { const selectionKey = getFilterSelectionKey(currentFilterKey, node.id); const exists = current.some((item) => getFilterSelectionKey(item.filterKey, item.id) === selectionKey); + if (isSingleSelectFilterKey(currentFilterKey)) { + if (exists) { + return current; + } + return [{ id: node.id, filterKey: currentFilterKey, label: node.label }]; + } if (exists) { return current.filter((item) => getFilterSelectionKey(item.filterKey, item.id) !== selectionKey); } @@ -1041,10 +1247,31 @@ function App() { const applyFilterModal = () => { if (!filterModalKey) return; - setAppliedFilters((current) => ({ - ...current, - [filterModalKey]: draftFilterNodes, - })); + let nextDraftNodes = draftFilterNodes; + if (isTemplateFilterKey(filterModalKey) && nextDraftNodes.length === 0) { + nextDraftNodes = getDefaultTemplateFilterNodes(); + } + if (isIndicatorTreeFilterKey(filterModalKey)) { + if (nextDraftNodes.length === 0) { + nextDraftNodes = defaultIndicatorTreeNodes; + } + if (isDefaultIndicatorTreeSelection(nextDraftNodes, filterTreeByKey.indicatorTree)) { + nextDraftNodes = []; + } + } + setAppliedFilters((current) => { + const nextFilters = { + ...current, + [filterModalKey]: nextDraftNodes, + }; + if (isTemplateFilterKey(filterModalKey)) { + nextFilters.indicatorTree = []; + } + return nextFilters; + }); + if (isTemplateFilterKey(filterModalKey)) { + resetIndicatorTreeState(); + } setChartDataBySelection({}); setLoadError(null); if (selectedContentNodes.length > 0) { @@ -1056,11 +1283,27 @@ function App() { }; const clearFilter = (filterKey: FilterKey) => { - if (appliedFilters[filterKey].length === 0) return; - setAppliedFilters((current) => ({ - ...current, - [filterKey]: [], - })); + const nextNodes = isTemplateFilterKey(filterKey) + ? getDefaultTemplateFilterNodes() + : isIndicatorTreeFilterKey(filterKey) + ? defaultIndicatorTreeNodes + : []; + if (appliedFilters[filterKey].length === nextNodes.length && appliedFilters[filterKey].every((node, index) => node.id === nextNodes[index]?.id)) { + return; + } + setAppliedFilters((current) => { + const nextFilters = { + ...current, + [filterKey]: nextNodes, + }; + if (isTemplateFilterKey(filterKey)) { + nextFilters.indicatorTree = []; + } + return nextFilters; + }); + if (isTemplateFilterKey(filterKey)) { + resetIndicatorTreeState(); + } setChartDataBySelection({}); setLoadError(null); if (selectedContentNodes.length > 0) { @@ -1104,6 +1347,13 @@ function App() { }); }, [activeContentKey, treeByContent]); + useEffect(() => { + if (filterModalKey !== 'indicatorTree') return; + if (draftFilterNodes.length > 0) return; + if (defaultIndicatorTreeNodes.length === 0) return; + setDraftFilterNodes(defaultIndicatorTreeNodes); + }, [defaultIndicatorTreeNodes, draftFilterNodes.length, filterModalKey]); + useEffect(() => { const controller = new AbortController(); @@ -1136,6 +1386,7 @@ function App() { body: JSON.stringify({ groupBy: groupKey, metric: requestMetricKey, + templateId: selectedTemplateId, filters: appliedFilterPayload, nodes: selectedContentNodes.map((node) => ({ key: getSelectionKey(node.contentKey, node.id), @@ -1171,16 +1422,37 @@ function App() { return () => { controller.abort(); }; - }, [appliedFilterPayload, chartQueryVersion, groupKey, metricKey, requestMetricKey, selectedContentNodes]); + }, [appliedFilterPayload, chartQueryVersion, groupKey, metricKey, requestMetricKey, selectedContentNodes, selectedTemplateId]); useEffect(() => { const frame = chartFrameRef.current; const fullscreenTarget = workspaceRef.current; if (!frame || !fullscreenTarget) return; - const getFullscreenButton = () => frame.querySelector('.chart-fullscreen-button'); + const getTemplateButton = () => frame.querySelector('.ag-charts-myButton-template')?.closest('.ag-charts-toolbar__button'); + const getIndicatorButton = () => frame.querySelector('.ag-charts-myButton-indicator')?.closest('.ag-charts-toolbar__button'); + const getFullscreenButton = () => frame.querySelector('.ag-charts-myButton-fullScreen')?.closest('.ag-charts-toolbar__button'); const getStatisticButton = () => frame.querySelector('.ag-charts-myButton-statistic')?.closest('.ag-charts-toolbar__button'); + const setButtonAttribute = (button: HTMLButtonElement, name: string, value: string) => { + if (button.getAttribute(name) !== value) { + button.setAttribute(name, value); + } + }; const syncToolbarButtons = () => { + const templateButton = getTemplateButton(); + if (templateButton) { + templateButton.classList.add('chart-template-button'); + setButtonAttribute(templateButton, 'aria-disabled', 'false'); + setButtonAttribute(templateButton, 'aria-expanded', String(filterModalKey === 'templateLibrary')); + } + + const indicatorButton = getIndicatorButton(); + if (indicatorButton) { + indicatorButton.classList.add('chart-indicator-button'); + setButtonAttribute(indicatorButton, 'aria-disabled', 'false'); + setButtonAttribute(indicatorButton, 'aria-expanded', String(filterModalKey === 'indicatorTree')); + } + const button = getFullscreenButton(); if (button) { let icon = button.querySelector('.ag-charts-myButton-fullScreen'); @@ -1190,7 +1462,10 @@ function App() { } const isFullscreen = document.fullscreenElement === fullscreenTarget; + button.classList.add('chart-fullscreen-button'); button.classList.toggle('ag-charts-toolbar__button--active', isFullscreen); + setButtonAttribute(button, 'aria-disabled', 'false'); + setButtonAttribute(button, 'aria-pressed', String(isFullscreen)); icon?.classList.toggle('anticon-arrow-salt', !isFullscreen); icon?.classList.toggle('anticon-shrink', isFullscreen); } @@ -1198,7 +1473,8 @@ function App() { const statisticButton = getStatisticButton(); if (statisticButton) { statisticButton.classList.add('chart-statistic-button'); - statisticButton.setAttribute('aria-expanded', String(statisticMenuOpen)); + setButtonAttribute(statisticButton, 'aria-disabled', 'false'); + setButtonAttribute(statisticButton, 'aria-expanded', String(statisticMenuOpen)); } }; @@ -1225,14 +1501,22 @@ function App() { const handleToolbarClick = (event: MouseEvent) => { const target = event.target as Element | null; const button = target?.closest( - '.chart-fullscreen-button, .chart-statistic-button', + '.chart-template-button, .chart-indicator-button, .chart-fullscreen-button, .chart-statistic-button', ); if (!button || !frame.contains(button)) return; event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); - if (button.classList.contains('chart-statistic-button')) { + if (button.classList.contains('chart-template-button')) { + setMetricMenuOpen(false); + setStatisticMenuOpen(false); + openFilterModal('templateLibrary'); + } else if (button.classList.contains('chart-indicator-button')) { + setMetricMenuOpen(false); + setStatisticMenuOpen(false); + openFilterModal('indicatorTree'); + } else if (button.classList.contains('chart-statistic-button')) { setMetricMenuOpen(false); setStatisticMenuOpen((open) => !open); } else { @@ -1245,14 +1529,22 @@ function App() { const target = event.target as Element | null; const button = target?.closest( - '.chart-fullscreen-button, .chart-statistic-button', + '.chart-template-button, .chart-indicator-button, .chart-fullscreen-button, .chart-statistic-button', ); if (!button || !frame.contains(button)) return; event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); - if (button.classList.contains('chart-statistic-button')) { + if (button.classList.contains('chart-template-button')) { + setMetricMenuOpen(false); + setStatisticMenuOpen(false); + openFilterModal('templateLibrary'); + } else if (button.classList.contains('chart-indicator-button')) { + setMetricMenuOpen(false); + setStatisticMenuOpen(false); + openFilterModal('indicatorTree'); + } else if (button.classList.contains('chart-statistic-button')) { setMetricMenuOpen(false); setStatisticMenuOpen((open) => !open); } else { @@ -1265,6 +1557,11 @@ function App() { }; const observer = new MutationObserver(syncToolbarButtons); + const initialSyncTimers = [ + window.setTimeout(syncToolbarButtons, 0), + window.setTimeout(syncToolbarButtons, 100), + window.setTimeout(syncToolbarButtons, 500), + ]; document.addEventListener('keydown', handleKeyDown, true); document.addEventListener('fullscreenchange', handleFullscreenChange); document.addEventListener('contextmenu', suppressBrowserContextMenu); @@ -1275,6 +1572,7 @@ function App() { syncToolbarButtons(); return () => { + initialSyncTimers.forEach((timer) => window.clearTimeout(timer)); document.removeEventListener('keydown', handleKeyDown, true); document.removeEventListener('fullscreenchange', handleFullscreenChange); document.removeEventListener('contextmenu', suppressBrowserContextMenu); @@ -1283,7 +1581,7 @@ function App() { frame.removeEventListener('keydown', handleToolbarKeyDown, true); observer.disconnect(); }; - }, [statisticMenuOpen]); + }, [appliedFilters, filterModalKey, filterTreeByKey, statisticMenuOpen]); const chartOptions = useMemo(() => { const groupNames: string[] = []; @@ -1296,11 +1594,12 @@ function App() { groupNames.push(datum.groupName); }); }); + groupNames.sort(compareGroupNames); const visibleData = groupNames.map((groupName) => { const row: Record = { groupName }; - selectedContentNodes.forEach((node, index) => { + selectedContentNodes.forEach((node) => { const datum = chartDataBySelection[getSelectionKey(node.contentKey, node.id)]?.find((item) => item.groupName === groupName); - row[getSeriesValueKey(index)] = datum?.[selectedValueKey] ?? null; + row[getSeriesValueKey(node.contentKey, node.id)] = datum?.[selectedValueKey] ?? null; }); return row; }); @@ -1363,6 +1662,16 @@ function App() { enabled: true, toolbar: { buttons: ([ + { + value: 'callout', + tooltip: '模板库', + label: '', + }, + { + value: 'text', + tooltip: '指标树形', + label: '', + }, { value: 'note', tooltip: '切换统计指标', @@ -1388,6 +1697,11 @@ function App() { value: 'fibonacci-menu', tooltip: 'Fibonacci Tool', }, + { + value: 'comment', + tooltip: '全屏(F11)', + label: '', + }, { icon: 'delete', value: 'clear', @@ -1396,13 +1710,14 @@ function App() { ] as unknown as NonNullable['toolbar']>['buttons']), }, }, - series: selectedContentNodes.map((node, index) => ({ + series: selectedContentNodes.map((node) => ({ type: 'line', xKey: 'groupName', - yKey: getSeriesValueKey(index), + yKey: getSeriesValueKey(node.contentKey, node.id), yName: `${node.label} ${seriesValueLabel}`, stroke: node.color, strokeWidth: 2, + connectMissingData: true, marker: { enabled: true, fill: node.color, @@ -1492,7 +1807,7 @@ function App() {
- {filterOptions.map((option) => { + {chartFilterOptions.map((option) => { const count = appliedFilters[option.key].length; const FilterIcon = option.icon; return ( @@ -1516,7 +1831,10 @@ function App() { type="button" title="清空全部筛选" onClick={() => { + resetIndicatorTreeState(); setAppliedFilters({ + templateLibrary: getDefaultTemplateFilterNodes(), + indicatorTree: [], region: [], geoLocation: [], facilityType: [], @@ -1593,9 +1911,6 @@ function App() { ) : null}
{loading || loadError ?
{loading ? loadingHint || '加载中' : loadError}
: null} - {loading ? (
@@ -1687,7 +2002,17 @@ function App() { )}