表格
This commit is contained in:
parent
af0bea2e9f
commit
6d25ac942f
16
bun.lock
16
bun.lock
@ -8,6 +8,8 @@
|
|||||||
"ag-charts-community": "^13.2.1",
|
"ag-charts-community": "^13.2.1",
|
||||||
"ag-charts-enterprise": "13.2.1",
|
"ag-charts-enterprise": "13.2.1",
|
||||||
"ag-charts-react": "^13.2.1",
|
"ag-charts-react": "^13.2.1",
|
||||||
|
"ag-grid-community": "^35.3.0",
|
||||||
|
"ag-grid-react": "^35.3.0",
|
||||||
"lucide-react": "^1.14.0",
|
"lucide-react": "^1.14.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
@ -202,6 +204,10 @@
|
|||||||
|
|
||||||
"ag-charts-types": ["ag-charts-types@13.2.1", "", {}, "sha512-r7veb3QqJtIKlXmeUsLR4/oDPwmHxFI2tmbZra/203mdaz3uwQUrrgYNg628nrK+7L2YxXnwGc6L05tWjLLjNQ=="],
|
"ag-charts-types": ["ag-charts-types@13.2.1", "", {}, "sha512-r7veb3QqJtIKlXmeUsLR4/oDPwmHxFI2tmbZra/203mdaz3uwQUrrgYNg628nrK+7L2YxXnwGc6L05tWjLLjNQ=="],
|
||||||
|
|
||||||
|
"ag-grid-community": ["ag-grid-community@35.3.0", "", { "dependencies": { "ag-charts-types": "13.3.0" } }, "sha512-c9WQWB88J965IjBC/GPUX30aAZix10o6oYT86DWipcxgLZTIQlLSilJJEr1bno/245rPEAIMjhoU1gp9VIfURg=="],
|
||||||
|
|
||||||
|
"ag-grid-react": ["ag-grid-react@35.3.0", "", { "dependencies": { "ag-grid-community": "35.3.0", "prop-types": "^15.8.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-3c6YEFGQGNZxEi1PdK0b+WhKkKRJ7KxuYzsG4UmISyax5/J7N93f8B1TZK1pq+AgzPhdk/++vjZe3KhFdF3tog=="],
|
||||||
|
|
||||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.27", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA=="],
|
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.27", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA=="],
|
||||||
|
|
||||||
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
||||||
@ -232,6 +238,8 @@
|
|||||||
|
|
||||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||||
|
|
||||||
|
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
||||||
|
|
||||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||||
|
|
||||||
"lucide-react": ["lucide-react@1.14.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA=="],
|
"lucide-react": ["lucide-react@1.14.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA=="],
|
||||||
@ -242,16 +250,22 @@
|
|||||||
|
|
||||||
"node-releases": ["node-releases@2.0.38", "", {}, "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="],
|
"node-releases": ["node-releases@2.0.38", "", {}, "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="],
|
||||||
|
|
||||||
|
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||||
|
|
||||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||||
|
|
||||||
"postcss": ["postcss@8.5.14", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg=="],
|
"postcss": ["postcss@8.5.14", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg=="],
|
||||||
|
|
||||||
|
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
||||||
|
|
||||||
"react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="],
|
"react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="],
|
||||||
|
|
||||||
"react-dom": ["react-dom@19.2.5", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="],
|
"react-dom": ["react-dom@19.2.5", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="],
|
||||||
|
|
||||||
|
"react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||||
|
|
||||||
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
|
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
|
||||||
|
|
||||||
"rollup": ["rollup@4.60.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.3", "@rollup/rollup-android-arm64": "4.60.3", "@rollup/rollup-darwin-arm64": "4.60.3", "@rollup/rollup-darwin-x64": "4.60.3", "@rollup/rollup-freebsd-arm64": "4.60.3", "@rollup/rollup-freebsd-x64": "4.60.3", "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", "@rollup/rollup-linux-arm-musleabihf": "4.60.3", "@rollup/rollup-linux-arm64-gnu": "4.60.3", "@rollup/rollup-linux-arm64-musl": "4.60.3", "@rollup/rollup-linux-loong64-gnu": "4.60.3", "@rollup/rollup-linux-loong64-musl": "4.60.3", "@rollup/rollup-linux-ppc64-gnu": "4.60.3", "@rollup/rollup-linux-ppc64-musl": "4.60.3", "@rollup/rollup-linux-riscv64-gnu": "4.60.3", "@rollup/rollup-linux-riscv64-musl": "4.60.3", "@rollup/rollup-linux-s390x-gnu": "4.60.3", "@rollup/rollup-linux-x64-gnu": "4.60.3", "@rollup/rollup-linux-x64-musl": "4.60.3", "@rollup/rollup-openbsd-x64": "4.60.3", "@rollup/rollup-openharmony-arm64": "4.60.3", "@rollup/rollup-win32-arm64-msvc": "4.60.3", "@rollup/rollup-win32-ia32-msvc": "4.60.3", "@rollup/rollup-win32-x64-gnu": "4.60.3", "@rollup/rollup-win32-x64-msvc": "4.60.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A=="],
|
"rollup": ["rollup@4.60.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.3", "@rollup/rollup-android-arm64": "4.60.3", "@rollup/rollup-darwin-arm64": "4.60.3", "@rollup/rollup-darwin-x64": "4.60.3", "@rollup/rollup-freebsd-arm64": "4.60.3", "@rollup/rollup-freebsd-x64": "4.60.3", "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", "@rollup/rollup-linux-arm-musleabihf": "4.60.3", "@rollup/rollup-linux-arm64-gnu": "4.60.3", "@rollup/rollup-linux-arm64-musl": "4.60.3", "@rollup/rollup-linux-loong64-gnu": "4.60.3", "@rollup/rollup-linux-loong64-musl": "4.60.3", "@rollup/rollup-linux-ppc64-gnu": "4.60.3", "@rollup/rollup-linux-ppc64-musl": "4.60.3", "@rollup/rollup-linux-riscv64-gnu": "4.60.3", "@rollup/rollup-linux-riscv64-musl": "4.60.3", "@rollup/rollup-linux-s390x-gnu": "4.60.3", "@rollup/rollup-linux-x64-gnu": "4.60.3", "@rollup/rollup-linux-x64-musl": "4.60.3", "@rollup/rollup-openbsd-x64": "4.60.3", "@rollup/rollup-openharmony-arm64": "4.60.3", "@rollup/rollup-win32-arm64-msvc": "4.60.3", "@rollup/rollup-win32-ia32-msvc": "4.60.3", "@rollup/rollup-win32-x64-gnu": "4.60.3", "@rollup/rollup-win32-x64-msvc": "4.60.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A=="],
|
||||||
@ -271,5 +285,7 @@
|
|||||||
"vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="],
|
"vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="],
|
||||||
|
|
||||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||||
|
|
||||||
|
"ag-grid-community/ag-charts-types": ["ag-charts-types@13.3.0", "", {}, "sha512-UMoAn908LC4ZIJSNfUckSBEFa79Mi1vFRA8qIRx+NusEuuFgXDioCZx4MxM7O3rDXlxTWH9DvQmcDjh7vyd89w=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,8 @@
|
|||||||
"ag-charts-community": "^13.2.1",
|
"ag-charts-community": "^13.2.1",
|
||||||
"ag-charts-enterprise": "13.2.1",
|
"ag-charts-enterprise": "13.2.1",
|
||||||
"ag-charts-react": "^13.2.1",
|
"ag-charts-react": "^13.2.1",
|
||||||
|
"ag-grid-community": "^35.3.0",
|
||||||
|
"ag-grid-react": "^35.3.0",
|
||||||
"lucide-react": "^1.14.0",
|
"lucide-react": "^1.14.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0"
|
"react-dom": "^19.1.0"
|
||||||
|
|||||||
427
src/App.tsx
427
src/App.tsx
@ -1,5 +1,12 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { AgGridReact } from 'ag-grid-react';
|
||||||
import { AgCharts } from 'ag-charts-react';
|
import { AgCharts } from 'ag-charts-react';
|
||||||
|
import {
|
||||||
|
AllCommunityModule as AgGridAllCommunityModule,
|
||||||
|
ModuleRegistry as AgGridModuleRegistry,
|
||||||
|
type ColDef,
|
||||||
|
type ValueFormatterParams,
|
||||||
|
} from 'ag-grid-community';
|
||||||
import type { AgCartesianChartOptions } from 'ag-charts-community';
|
import type { AgCartesianChartOptions } from 'ag-charts-community';
|
||||||
import { ModuleRegistry } from 'ag-charts-community';
|
import { ModuleRegistry } from 'ag-charts-community';
|
||||||
import { Building2, Construction, LayoutGrid, Library, LocateFixed, MapPinned, Waypoints } from 'lucide-react';
|
import { Building2, Construction, LayoutGrid, Library, LocateFixed, MapPinned, Waypoints } from 'lucide-react';
|
||||||
@ -14,6 +21,7 @@ import { AG_CHARTS_LOCALE_ZH_CN } from 'ag-charts-locale';
|
|||||||
|
|
||||||
LicenseManager.setLicenseKey('[v3][RELEASE][0102]_NDg2Njc4MzY3MDgzNw==16d78ca762fb5d2ff740aed081e2af7b');
|
LicenseManager.setLicenseKey('[v3][RELEASE][0102]_NDg2Njc4MzY3MDgzNw==16d78ca762fb5d2ff740aed081e2af7b');
|
||||||
ModuleRegistry.registerModules([AnnotationsModule, ContextMenuModule, ZoomModule, CrosshairModule]);
|
ModuleRegistry.registerModules([AnnotationsModule, ContextMenuModule, ZoomModule, CrosshairModule]);
|
||||||
|
AgGridModuleRegistry.registerModules([AgGridAllCommunityModule]);
|
||||||
|
|
||||||
const API_BASE_URL = 'https://nest.zwgczx.com/api/v1';
|
const API_BASE_URL = 'https://nest.zwgczx.com/api/v1';
|
||||||
// const API_BASE_URL = 'http://127.0.0.1:9089/api/v1';
|
// const API_BASE_URL = 'http://127.0.0.1:9089/api/v1';
|
||||||
@ -33,6 +41,14 @@ const metricOptions = [
|
|||||||
{ key: 'dataCount', label: '数据量' },
|
{ key: 'dataCount', label: '数据量' },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
const metricShortLabels: Record<MetricKey, string> = {
|
||||||
|
cost: '价',
|
||||||
|
buildingArea: '建',
|
||||||
|
builtArea: '造',
|
||||||
|
usableArea: '用',
|
||||||
|
dataCount: '数',
|
||||||
|
};
|
||||||
|
|
||||||
const contentOptions = [
|
const contentOptions = [
|
||||||
{ key: 'geoLocation', label: '自然地理区位' },
|
{ key: 'geoLocation', label: '自然地理区位' },
|
||||||
{ key: 'facilityType', label: '设施类别' },
|
{ key: 'facilityType', label: '设施类别' },
|
||||||
@ -137,7 +153,7 @@ type StatisticKey = (typeof statisticOptions)[number]['key'];
|
|||||||
type MetricKey = (typeof metricOptions)[number]['key'];
|
type MetricKey = (typeof metricOptions)[number]['key'];
|
||||||
type ContentKey = (typeof contentOptions)[number]['key'];
|
type ContentKey = (typeof contentOptions)[number]['key'];
|
||||||
type FilterKey = (typeof filterOptions)[number]['key'];
|
type FilterKey = (typeof filterOptions)[number]['key'];
|
||||||
type GroupKey = 'year';
|
type ChartViewKey = 'trend' | 'pivot';
|
||||||
type ApiBuildingFunctionStat = {
|
type ApiBuildingFunctionStat = {
|
||||||
group_key?: string | number | null;
|
group_key?: string | number | null;
|
||||||
group_name?: string | null;
|
group_name?: string | null;
|
||||||
@ -182,6 +198,14 @@ type SelectedFilterNode = {
|
|||||||
filterKey: FilterKey;
|
filterKey: FilterKey;
|
||||||
label: string;
|
label: string;
|
||||||
};
|
};
|
||||||
|
type PivotGridRow = {
|
||||||
|
year: string;
|
||||||
|
name: string;
|
||||||
|
maxValue: number | null;
|
||||||
|
minValue: number | null;
|
||||||
|
avgValue: number | null;
|
||||||
|
dataCount: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
function formatNumber(value: number, maximumFractionDigits: number) {
|
function formatNumber(value: number, maximumFractionDigits: number) {
|
||||||
return value.toLocaleString('zh-CN', {
|
return value.toLocaleString('zh-CN', {
|
||||||
@ -506,6 +530,10 @@ function isDefaultTemplateSelection(nodes: SelectedFilterNode[]) {
|
|||||||
return nodes.length === 1 && nodes[0]?.id === defaultTemplateFilterNode.id;
|
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[] {
|
function getDefaultIndicatorTreeFilterNodes(nodes: TreeNode[]): SelectedFilterNode[] {
|
||||||
const defaultNode = nodes[0];
|
const defaultNode = nodes[0];
|
||||||
if (!defaultNode) return [];
|
if (!defaultNode) return [];
|
||||||
@ -657,7 +685,7 @@ function App() {
|
|||||||
});
|
});
|
||||||
const [statisticKey, setStatisticKey] = useState<StatisticKey>('avgValue');
|
const [statisticKey, setStatisticKey] = useState<StatisticKey>('avgValue');
|
||||||
const [metricKey, setMetricKey] = useState<MetricKey>('cost');
|
const [metricKey, setMetricKey] = useState<MetricKey>('cost');
|
||||||
const [groupKey, setGroupKey] = useState<GroupKey>('year');
|
const [chartViewKey, setChartViewKey] = useState<ChartViewKey>('trend');
|
||||||
const [statisticMenuOpen, setStatisticMenuOpen] = useState(false);
|
const [statisticMenuOpen, setStatisticMenuOpen] = useState(false);
|
||||||
const [metricMenuOpen, setMetricMenuOpen] = useState(false);
|
const [metricMenuOpen, setMetricMenuOpen] = useState(false);
|
||||||
const [activeContentKey, setActiveContentKey] = useState<ContentKey>('geoLocation');
|
const [activeContentKey, setActiveContentKey] = useState<ContentKey>('geoLocation');
|
||||||
@ -766,6 +794,10 @@ function App() {
|
|||||||
|
|
||||||
const selectedStatistic = statisticOptions.find((option) => option.key === statisticKey) ?? statisticOptions[0];
|
const selectedStatistic = statisticOptions.find((option) => option.key === statisticKey) ?? statisticOptions[0];
|
||||||
const selectedMetric = metricOptions.find((option) => option.key === metricKey) ?? metricOptions[0];
|
const selectedMetric = metricOptions.find((option) => option.key === metricKey) ?? metricOptions[0];
|
||||||
|
const selectedMetricShortLabel = metricShortLabels[metricKey];
|
||||||
|
const currentViewShortLabel = chartViewKey === 'pivot' ? '表' : '趋';
|
||||||
|
const pivotToggleActionLabel = chartViewKey === 'pivot' ? '切换到趋势图' : '切换到表格';
|
||||||
|
const pivotToggleTitle = `${chartViewKey === 'pivot' ? '当前表格' : '当前趋势图'},${pivotToggleActionLabel}`;
|
||||||
const activeContent = contentOptions.find((option) => option.key === activeContentKey) ?? contentOptions[0];
|
const activeContent = contentOptions.find((option) => option.key === activeContentKey) ?? contentOptions[0];
|
||||||
const activeTree = treeByContent[activeContentKey];
|
const activeTree = treeByContent[activeContentKey];
|
||||||
const activeFilter = filterOptions.find((option) => option.key === filterModalKey);
|
const activeFilter = filterOptions.find((option) => option.key === filterModalKey);
|
||||||
@ -783,6 +815,7 @@ function App() {
|
|||||||
() => getDefaultIndicatorTreeFilterNodes(filterTreeByKey.indicatorTree),
|
() => getDefaultIndicatorTreeFilterNodes(filterTreeByKey.indicatorTree),
|
||||||
[filterTreeByKey.indicatorTree],
|
[filterTreeByKey.indicatorTree],
|
||||||
);
|
);
|
||||||
|
const indicatorSelectionLabel = appliedFilters.indicatorTree[0]?.label ?? defaultIndicatorTreeNodes[0]?.label ?? '';
|
||||||
const activeFilterCount = Object.entries(appliedFilters).reduce((total, [key, nodes]) => (
|
const activeFilterCount = Object.entries(appliedFilters).reduce((total, [key, nodes]) => (
|
||||||
key === 'templateLibrary' && isDefaultTemplateSelection(nodes) ? total : total + nodes.length
|
key === 'templateLibrary' && isDefaultTemplateSelection(nodes) ? total : total + nodes.length
|
||||||
), 0);
|
), 0);
|
||||||
@ -794,6 +827,86 @@ function App() {
|
|||||||
const selectedValueKey = metricKey === 'dataCount' ? 'dataCount' : statisticKey;
|
const selectedValueKey = metricKey === 'dataCount' ? 'dataCount' : statisticKey;
|
||||||
const requestMetricKey = metricKey === 'dataCount' ? 'cost' : metricKey;
|
const requestMetricKey = metricKey === 'dataCount' ? 'cost' : metricKey;
|
||||||
const seriesValueLabel = metricKey === 'dataCount' ? selectedMetric.label : selectedStatistic.label;
|
const seriesValueLabel = metricKey === 'dataCount' ? selectedMetric.label : selectedStatistic.label;
|
||||||
|
const groupNames = useMemo(() => {
|
||||||
|
const names: string[] = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
selectedContentNodes.forEach((node) => {
|
||||||
|
const rows = chartDataBySelection[getSelectionKey(node.contentKey, node.id)] ?? [];
|
||||||
|
rows.forEach((datum) => {
|
||||||
|
if (seen.has(datum.groupName)) return;
|
||||||
|
seen.add(datum.groupName);
|
||||||
|
names.push(datum.groupName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return names.sort(compareGroupNames);
|
||||||
|
}, [chartDataBySelection, selectedContentNodes]);
|
||||||
|
const pivotGridRowData = useMemo<PivotGridRow[]>(
|
||||||
|
() => selectedContentNodes.flatMap((node) => {
|
||||||
|
const rows = chartDataBySelection[getSelectionKey(node.contentKey, node.id)] ?? [];
|
||||||
|
return rows.map((datum) => ({
|
||||||
|
year: datum.groupName,
|
||||||
|
name: node.label,
|
||||||
|
maxValue: datum.maxValue,
|
||||||
|
minValue: datum.minValue,
|
||||||
|
avgValue: datum.avgValue,
|
||||||
|
dataCount: datum.dataCount,
|
||||||
|
}));
|
||||||
|
}),
|
||||||
|
[chartDataBySelection, selectedContentNodes],
|
||||||
|
);
|
||||||
|
const pivotGridColumnDefs = useMemo<ColDef<PivotGridRow>[]>(
|
||||||
|
() => {
|
||||||
|
const valueFormatter = ({ value }: ValueFormatterParams<PivotGridRow, number | null>) => (
|
||||||
|
value == null ? '' : formatChartValue(Number(value), requestMetricKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'year',
|
||||||
|
headerName: '年度',
|
||||||
|
minWidth: 68,
|
||||||
|
width: 74,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
headerName: '名称',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 108,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'maxValue',
|
||||||
|
headerName: '最大值',
|
||||||
|
type: 'numericColumn',
|
||||||
|
minWidth: 78,
|
||||||
|
valueFormatter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'minValue',
|
||||||
|
headerName: '最小值',
|
||||||
|
type: 'numericColumn',
|
||||||
|
minWidth: 78,
|
||||||
|
valueFormatter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'avgValue',
|
||||||
|
headerName: '平均值',
|
||||||
|
type: 'numericColumn',
|
||||||
|
minWidth: 78,
|
||||||
|
valueFormatter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'dataCount',
|
||||||
|
headerName: '数量',
|
||||||
|
type: 'numericColumn',
|
||||||
|
minWidth: 70,
|
||||||
|
valueFormatter: ({ value }: ValueFormatterParams<PivotGridRow, number | null>) => (
|
||||||
|
value == null ? '' : formatChartValue(Number(value), 'dataCount')
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
[requestMetricKey],
|
||||||
|
);
|
||||||
const selectedNodeKeys = useMemo(
|
const selectedNodeKeys = useMemo(
|
||||||
() => new Set(selectedContentNodes.map((node) => getSelectionKey(node.contentKey, node.id))),
|
() => new Set(selectedContentNodes.map((node) => getSelectionKey(node.contentKey, node.id))),
|
||||||
[selectedContentNodes],
|
[selectedContentNodes],
|
||||||
@ -1126,6 +1239,10 @@ function App() {
|
|||||||
ensureFilterTreeLoaded(filterKey);
|
ensureFilterTreeLoaded(filterKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ensureFilterTreeLoaded('indicatorTree');
|
||||||
|
}, [selectedTemplateId]);
|
||||||
|
|
||||||
const closeFilterModal = () => {
|
const closeFilterModal = () => {
|
||||||
if (filterModalKey) {
|
if (filterModalKey) {
|
||||||
filterSearchRequestSeqRef.current[filterModalKey] += 1;
|
filterSearchRequestSeqRef.current[filterModalKey] += 1;
|
||||||
@ -1284,17 +1401,18 @@ function App() {
|
|||||||
nextDraftNodes = [];
|
nextDraftNodes = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const shouldReloadIndicatorTree = isTemplateFilterKey(filterModalKey) && !isSameFilterSelection(appliedFilters.templateLibrary, nextDraftNodes);
|
||||||
setAppliedFilters((current) => {
|
setAppliedFilters((current) => {
|
||||||
const nextFilters = {
|
const nextFilters = {
|
||||||
...current,
|
...current,
|
||||||
[filterModalKey]: nextDraftNodes,
|
[filterModalKey]: nextDraftNodes,
|
||||||
};
|
};
|
||||||
if (isTemplateFilterKey(filterModalKey)) {
|
if (shouldReloadIndicatorTree) {
|
||||||
nextFilters.indicatorTree = [];
|
nextFilters.indicatorTree = [];
|
||||||
}
|
}
|
||||||
return nextFilters;
|
return nextFilters;
|
||||||
});
|
});
|
||||||
if (isTemplateFilterKey(filterModalKey)) {
|
if (shouldReloadIndicatorTree) {
|
||||||
resetIndicatorTreeState();
|
resetIndicatorTreeState();
|
||||||
}
|
}
|
||||||
setChartDataBySelection({});
|
setChartDataBySelection({});
|
||||||
@ -1313,7 +1431,7 @@ function App() {
|
|||||||
: isIndicatorTreeFilterKey(filterKey)
|
: isIndicatorTreeFilterKey(filterKey)
|
||||||
? defaultIndicatorTreeNodes
|
? defaultIndicatorTreeNodes
|
||||||
: [];
|
: [];
|
||||||
if (appliedFilters[filterKey].length === nextNodes.length && appliedFilters[filterKey].every((node, index) => node.id === nextNodes[index]?.id)) {
|
if (isSameFilterSelection(appliedFilters[filterKey], nextNodes)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setAppliedFilters((current) => {
|
setAppliedFilters((current) => {
|
||||||
@ -1347,6 +1465,18 @@ function App() {
|
|||||||
setChartQueryVersion((version) => version + 1);
|
setChartQueryVersion((version) => version + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const togglePivotView = useCallback(() => {
|
||||||
|
setChartViewKey((current) => (current === 'trend' ? 'pivot' : 'trend'));
|
||||||
|
setMetricMenuOpen(false);
|
||||||
|
setStatisticMenuOpen(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const openGridFilterModal = (filterKey: 'templateLibrary' | 'indicatorTree') => {
|
||||||
|
setMetricMenuOpen(false);
|
||||||
|
setStatisticMenuOpen(false);
|
||||||
|
openFilterModal(filterKey);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!contentTreeConfigs[activeContentKey]) return;
|
if (!contentTreeConfigs[activeContentKey]) return;
|
||||||
if (treeByContent[activeContentKey].length > 0 || treeInitialLoadStartedRef.current[activeContentKey]) return;
|
if (treeByContent[activeContentKey].length > 0 || treeInitialLoadStartedRef.current[activeContentKey]) return;
|
||||||
@ -1409,7 +1539,7 @@ function App() {
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
groupBy: groupKey,
|
groupBy: 'year',
|
||||||
metric: requestMetricKey,
|
metric: requestMetricKey,
|
||||||
templateId: selectedTemplateId,
|
templateId: selectedTemplateId,
|
||||||
filters: appliedFilterPayload,
|
filters: appliedFilterPayload,
|
||||||
@ -1447,7 +1577,7 @@ function App() {
|
|||||||
return () => {
|
return () => {
|
||||||
controller.abort();
|
controller.abort();
|
||||||
};
|
};
|
||||||
}, [appliedFilterPayload, chartQueryVersion, groupKey, metricKey, requestMetricKey, selectedContentNodes, selectedTemplateId]);
|
}, [appliedFilterPayload, chartQueryVersion, metricKey, requestMetricKey, selectedContentNodes, selectedTemplateId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const frame = chartFrameRef.current;
|
const frame = chartFrameRef.current;
|
||||||
@ -1458,6 +1588,7 @@ function App() {
|
|||||||
const getIndicatorButton = () => frame.querySelector<HTMLElement>('.ag-charts-myButton-indicator')?.closest<HTMLButtonElement>('.ag-charts-toolbar__button');
|
const getIndicatorButton = () => frame.querySelector<HTMLElement>('.ag-charts-myButton-indicator')?.closest<HTMLButtonElement>('.ag-charts-toolbar__button');
|
||||||
const getFullscreenButton = () => frame.querySelector<HTMLElement>('.ag-charts-myButton-fullScreen')?.closest<HTMLButtonElement>('.ag-charts-toolbar__button');
|
const getFullscreenButton = () => frame.querySelector<HTMLElement>('.ag-charts-myButton-fullScreen')?.closest<HTMLButtonElement>('.ag-charts-toolbar__button');
|
||||||
const getStatisticButton = () => frame.querySelector<HTMLElement>('.ag-charts-myButton-statistic')?.closest<HTMLButtonElement>('.ag-charts-toolbar__button');
|
const getStatisticButton = () => frame.querySelector<HTMLElement>('.ag-charts-myButton-statistic')?.closest<HTMLButtonElement>('.ag-charts-toolbar__button');
|
||||||
|
const getPivotButton = () => frame.querySelector<HTMLElement>('.ag-charts-myButton-pivot')?.closest<HTMLButtonElement>('.ag-charts-toolbar__button');
|
||||||
const setButtonAttribute = (button: HTMLButtonElement, name: string, value: string) => {
|
const setButtonAttribute = (button: HTMLButtonElement, name: string, value: string) => {
|
||||||
if (button.getAttribute(name) !== value) {
|
if (button.getAttribute(name) !== value) {
|
||||||
button.setAttribute(name, value);
|
button.setAttribute(name, value);
|
||||||
@ -1485,6 +1616,7 @@ function App() {
|
|||||||
indicatorButton.classList.add('chart-indicator-button');
|
indicatorButton.classList.add('chart-indicator-button');
|
||||||
enableCustomToolbarButton(indicatorButton);
|
enableCustomToolbarButton(indicatorButton);
|
||||||
setButtonAttribute(indicatorButton, 'aria-expanded', String(filterModalKey === 'indicatorTree'));
|
setButtonAttribute(indicatorButton, 'aria-expanded', String(filterModalKey === 'indicatorTree'));
|
||||||
|
setButtonAttribute(indicatorButton, 'title', '指标树形');
|
||||||
}
|
}
|
||||||
|
|
||||||
const button = getFullscreenButton();
|
const button = getFullscreenButton();
|
||||||
@ -1510,6 +1642,16 @@ function App() {
|
|||||||
enableCustomToolbarButton(statisticButton);
|
enableCustomToolbarButton(statisticButton);
|
||||||
setButtonAttribute(statisticButton, 'aria-expanded', String(statisticMenuOpen));
|
setButtonAttribute(statisticButton, 'aria-expanded', String(statisticMenuOpen));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pivotButton = getPivotButton();
|
||||||
|
if (pivotButton) {
|
||||||
|
pivotButton.classList.add('chart-pivot-button');
|
||||||
|
pivotButton.classList.toggle('ag-charts-toolbar__button--active', chartViewKey === 'pivot');
|
||||||
|
enableCustomToolbarButton(pivotButton);
|
||||||
|
setButtonAttribute(pivotButton, 'aria-pressed', String(chartViewKey === 'pivot'));
|
||||||
|
setButtonAttribute(pivotButton, 'aria-label', pivotToggleActionLabel);
|
||||||
|
setButtonAttribute(pivotButton, 'title', pivotToggleTitle);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleFullscreen = () => {
|
const toggleFullscreen = () => {
|
||||||
@ -1532,16 +1674,7 @@ function App() {
|
|||||||
syncToolbarButtons();
|
syncToolbarButtons();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToolbarClick = (event: MouseEvent) => {
|
const handleToolbarAction = (button: HTMLButtonElement) => {
|
||||||
const target = event.target as Element | null;
|
|
||||||
const button = target?.closest<HTMLButtonElement>(
|
|
||||||
'.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-template-button')) {
|
if (button.classList.contains('chart-template-button')) {
|
||||||
setMetricMenuOpen(false);
|
setMetricMenuOpen(false);
|
||||||
setStatisticMenuOpen(false);
|
setStatisticMenuOpen(false);
|
||||||
@ -1553,37 +1686,39 @@ function App() {
|
|||||||
} else if (button.classList.contains('chart-statistic-button')) {
|
} else if (button.classList.contains('chart-statistic-button')) {
|
||||||
setMetricMenuOpen(false);
|
setMetricMenuOpen(false);
|
||||||
setStatisticMenuOpen((open) => !open);
|
setStatisticMenuOpen((open) => !open);
|
||||||
|
} else if (button.classList.contains('chart-pivot-button')) {
|
||||||
|
togglePivotView();
|
||||||
} else {
|
} else {
|
||||||
toggleFullscreen();
|
toggleFullscreen();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleToolbarClick = (event: MouseEvent) => {
|
||||||
|
const target = event.target as Element | null;
|
||||||
|
const button = target?.closest<HTMLButtonElement>(
|
||||||
|
'.chart-template-button, .chart-indicator-button, .chart-fullscreen-button, .chart-statistic-button, .chart-pivot-button',
|
||||||
|
);
|
||||||
|
if (!button || !frame.contains(button)) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
handleToolbarAction(button);
|
||||||
|
};
|
||||||
|
|
||||||
const handleToolbarKeyDown = (event: KeyboardEvent) => {
|
const handleToolbarKeyDown = (event: KeyboardEvent) => {
|
||||||
if (event.key !== ' ' && event.key !== 'Enter') return;
|
if (event.key !== ' ' && event.key !== 'Enter') return;
|
||||||
|
|
||||||
const target = event.target as Element | null;
|
const target = event.target as Element | null;
|
||||||
const button = target?.closest<HTMLButtonElement>(
|
const button = target?.closest<HTMLButtonElement>(
|
||||||
'.chart-template-button, .chart-indicator-button, .chart-fullscreen-button, .chart-statistic-button',
|
'.chart-template-button, .chart-indicator-button, .chart-fullscreen-button, .chart-statistic-button, .chart-pivot-button',
|
||||||
);
|
);
|
||||||
if (!button || !frame.contains(button)) return;
|
if (!button || !frame.contains(button)) return;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
if (button.classList.contains('chart-template-button')) {
|
handleToolbarAction(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 {
|
|
||||||
toggleFullscreen();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const suppressBrowserContextMenu = (event: MouseEvent) => {
|
const suppressBrowserContextMenu = (event: MouseEvent) => {
|
||||||
@ -1616,21 +1751,21 @@ function App() {
|
|||||||
frame.removeEventListener('keydown', handleToolbarKeyDown, true);
|
frame.removeEventListener('keydown', handleToolbarKeyDown, true);
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
};
|
};
|
||||||
}, [activeContentKey, appliedFilters, filterModalKey, filterTreeByKey, selectedContentNodes.length, statisticMenuOpen]);
|
}, [
|
||||||
|
activeContentKey,
|
||||||
|
appliedFilters,
|
||||||
|
filterModalKey,
|
||||||
|
filterTreeByKey,
|
||||||
|
chartViewKey,
|
||||||
|
pivotToggleActionLabel,
|
||||||
|
pivotToggleTitle,
|
||||||
|
selectedContentNodes.length,
|
||||||
|
statisticMenuOpen,
|
||||||
|
togglePivotView,
|
||||||
|
]);
|
||||||
|
|
||||||
const chartOptions = useMemo<AgCartesianChartOptions>(() => {
|
const chartOptions = useMemo<AgCartesianChartOptions>(() => {
|
||||||
const groupNames: string[] = [];
|
const trendData = groupNames.map((groupName) => {
|
||||||
const groupNameSeen = new Set<string>();
|
|
||||||
selectedContentNodes.forEach((node) => {
|
|
||||||
const rows = chartDataBySelection[getSelectionKey(node.contentKey, node.id)] ?? [];
|
|
||||||
rows.forEach((datum) => {
|
|
||||||
if (groupNameSeen.has(datum.groupName)) return;
|
|
||||||
groupNameSeen.add(datum.groupName);
|
|
||||||
groupNames.push(datum.groupName);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
groupNames.sort(compareGroupNames);
|
|
||||||
const visibleData = groupNames.map((groupName) => {
|
|
||||||
const row: Record<string, string | number | null> = { groupName };
|
const row: Record<string, string | number | null> = { groupName };
|
||||||
selectedContentNodes.forEach((node) => {
|
selectedContentNodes.forEach((node) => {
|
||||||
const datum = chartDataBySelection[getSelectionKey(node.contentKey, node.id)]?.find((item) => item.groupName === groupName);
|
const datum = chartDataBySelection[getSelectionKey(node.contentKey, node.id)]?.find((item) => item.groupName === groupName);
|
||||||
@ -1638,6 +1773,32 @@ function App() {
|
|||||||
});
|
});
|
||||||
return row;
|
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 {
|
return {
|
||||||
theme: {
|
theme: {
|
||||||
@ -1667,7 +1828,7 @@ function App() {
|
|||||||
bottom: 18,
|
bottom: 18,
|
||||||
left: 24,
|
left: 24,
|
||||||
},
|
},
|
||||||
data: visibleData,
|
data: trendData,
|
||||||
zoom: {
|
zoom: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
anchorPointX: 'pointer',
|
anchorPointX: 'pointer',
|
||||||
@ -1705,7 +1866,7 @@ function App() {
|
|||||||
{
|
{
|
||||||
value: 'text',
|
value: 'text',
|
||||||
tooltip: '指标树形',
|
tooltip: '指标树形',
|
||||||
label: '<span class="ag-charts-myButton-indicator ag-charts-diy-button">树</span>',
|
label: '<span class="ag-charts-myButton-indicator ag-charts-diy-button">指</span>',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'note',
|
value: 'note',
|
||||||
@ -1742,35 +1903,15 @@ function App() {
|
|||||||
value: 'clear',
|
value: 'clear',
|
||||||
tooltip: 'Clear annotations',
|
tooltip: 'Clear annotations',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: 'comment',
|
||||||
|
tooltip: pivotToggleActionLabel,
|
||||||
|
label: `<span class="ag-charts-myButton-pivot ag-charts-diy-button">${currentViewShortLabel}</span>`,
|
||||||
|
},
|
||||||
] as unknown as NonNullable<NonNullable<AgCartesianChartOptions['annotations']>['toolbar']>['buttons']),
|
] as unknown as NonNullable<NonNullable<AgCartesianChartOptions['annotations']>['toolbar']>['buttons']),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
series: selectedContentNodes.map((node) => ({
|
series,
|
||||||
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: {
|
axes: {
|
||||||
x: {
|
x: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
@ -1830,7 +1971,58 @@ function App() {
|
|||||||
pagination: true,
|
pagination: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}, [activeFilterCount, chartDataBySelection, chartEmptyText, metricKey, requestMetricKey, selectedContentNodes, selectedMetric.label, seriesValueLabel, selectedValueKey, statisticKey]);
|
}, [
|
||||||
|
activeFilterCount,
|
||||||
|
chartDataBySelection,
|
||||||
|
chartEmptyText,
|
||||||
|
groupNames,
|
||||||
|
metricKey,
|
||||||
|
currentViewShortLabel,
|
||||||
|
pivotToggleActionLabel,
|
||||||
|
requestMetricKey,
|
||||||
|
selectedContentNodes,
|
||||||
|
selectedMetric.label,
|
||||||
|
seriesValueLabel,
|
||||||
|
selectedValueKey,
|
||||||
|
statisticKey,
|
||||||
|
]);
|
||||||
|
const renderMetricSwitcher = (variant: 'chart' | 'grid') => (
|
||||||
|
<div className={`metric-switcher metric-switcher--${variant}`}>
|
||||||
|
<button
|
||||||
|
className={`metric-switcher-button metric-switcher-button--${variant}`}
|
||||||
|
type="button"
|
||||||
|
title={`切换纵坐标指标:${selectedMetric.label}`}
|
||||||
|
aria-expanded={metricMenuOpen}
|
||||||
|
aria-haspopup="menu"
|
||||||
|
aria-label={`纵坐标:${selectedMetric.label}`}
|
||||||
|
onClick={() => {
|
||||||
|
setStatisticMenuOpen(false);
|
||||||
|
setMetricMenuOpen((open) => !open);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{variant === 'grid' ? selectedMetricShortLabel : selectedMetric.label}
|
||||||
|
</button>
|
||||||
|
{metricMenuOpen ? (
|
||||||
|
<div className="metric-switcher-menu" role="menu" aria-label="切换纵坐标指标">
|
||||||
|
{metricOptions.map((option) => (
|
||||||
|
<button
|
||||||
|
className="metric-switcher-menu-item"
|
||||||
|
type="button"
|
||||||
|
role="menuitem"
|
||||||
|
key={option.key}
|
||||||
|
aria-current={option.key === metricKey}
|
||||||
|
onClick={() => {
|
||||||
|
updateMetricKey(option.key);
|
||||||
|
setMetricMenuOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="dashboard-shell">
|
<main className="dashboard-shell">
|
||||||
@ -1841,6 +2033,7 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section className="workspace" aria-label="年度费用模板" ref={workspaceRef}>
|
<section className="workspace" 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="筛选条件">
|
<div className="chart-filter-bar chart-filter-bar--workspace" aria-label="筛选条件">
|
||||||
{chartFilterOptions.map((option) => {
|
{chartFilterOptions.map((option) => {
|
||||||
const count = appliedFilters[option.key].length;
|
const count = appliedFilters[option.key].length;
|
||||||
@ -1866,7 +2059,10 @@ function App() {
|
|||||||
type="button"
|
type="button"
|
||||||
title="清空全部筛选"
|
title="清空全部筛选"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
const shouldReloadIndicatorTree = !isDefaultTemplateSelection(appliedFilters.templateLibrary);
|
||||||
|
if (shouldReloadIndicatorTree) {
|
||||||
resetIndicatorTreeState();
|
resetIndicatorTreeState();
|
||||||
|
}
|
||||||
setAppliedFilters({
|
setAppliedFilters({
|
||||||
templateLibrary: getDefaultTemplateFilterNodes(),
|
templateLibrary: getDefaultTemplateFilterNodes(),
|
||||||
indicatorTree: [],
|
indicatorTree: [],
|
||||||
@ -1910,43 +2106,54 @@ function App() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="metric-switcher">
|
{chartViewKey === 'trend' ? renderMetricSwitcher('chart') : null}
|
||||||
<button
|
|
||||||
className="metric-switcher-button"
|
|
||||||
type="button"
|
|
||||||
title="切换纵坐标指标"
|
|
||||||
aria-expanded={metricMenuOpen}
|
|
||||||
aria-haspopup="menu"
|
|
||||||
aria-label={`纵坐标:${selectedMetric.label}`}
|
|
||||||
onClick={() => {
|
|
||||||
setStatisticMenuOpen(false);
|
|
||||||
setMetricMenuOpen((open) => !open);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{selectedMetric.label}
|
|
||||||
</button>
|
|
||||||
{metricMenuOpen ? (
|
|
||||||
<div className="metric-switcher-menu" role="menu" aria-label="切换纵坐标指标">
|
|
||||||
{metricOptions.map((option) => (
|
|
||||||
<button
|
|
||||||
className="metric-switcher-menu-item"
|
|
||||||
type="button"
|
|
||||||
role="menuitem"
|
|
||||||
key={option.key}
|
|
||||||
aria-current={option.key === metricKey}
|
|
||||||
onClick={() => {
|
|
||||||
updateMetricKey(option.key);
|
|
||||||
setMetricMenuOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
{loading || loadError ? <div className="chart-status">{loading ? loadingHint || '加载中' : loadError}</div> : null}
|
{loading || loadError ? <div className="chart-status">{loading ? loadingHint || '加载中' : loadError}</div> : null}
|
||||||
<AgCharts options={chartOptions} />
|
<AgCharts options={chartOptions} />
|
||||||
|
{chartViewKey === 'pivot' ? (
|
||||||
|
<div className="chart-pivot-grid-panel ag-theme-quartz">
|
||||||
|
<button
|
||||||
|
className="chart-grid-tool-button chart-grid-tool-button--template"
|
||||||
|
type="button"
|
||||||
|
title="模板库"
|
||||||
|
aria-label="模板库"
|
||||||
|
onClick={() => openGridFilterModal('templateLibrary')}
|
||||||
|
>
|
||||||
|
库
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="chart-grid-tool-button chart-grid-tool-button--indicator"
|
||||||
|
type="button"
|
||||||
|
title="指标树形"
|
||||||
|
aria-label="指标树形"
|
||||||
|
onClick={() => openGridFilterModal('indicatorTree')}
|
||||||
|
>
|
||||||
|
指
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="chart-grid-tool-button chart-grid-tool-button--trend"
|
||||||
|
type="button"
|
||||||
|
title={pivotToggleTitle}
|
||||||
|
aria-label={pivotToggleActionLabel}
|
||||||
|
onClick={togglePivotView}
|
||||||
|
>
|
||||||
|
{currentViewShortLabel}
|
||||||
|
</button>
|
||||||
|
{renderMetricSwitcher('grid')}
|
||||||
|
<AgGridReact<PivotGridRow>
|
||||||
|
rowData={pivotGridRowData}
|
||||||
|
columnDefs={pivotGridColumnDefs}
|
||||||
|
containerStyle={{ width: '100%', height: '100%' }}
|
||||||
|
theme="legacy"
|
||||||
|
defaultColDef={{
|
||||||
|
sortable: true,
|
||||||
|
resizable: true,
|
||||||
|
filter: true,
|
||||||
|
}}
|
||||||
|
suppressCellFocus
|
||||||
|
overlayNoRowsTemplate={selectedContentNodes.length === 0 ? '请选择右侧分类项' : '暂无透视数据'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="chart-loading-mask" aria-live="polite" aria-busy="true">
|
<div className="chart-loading-mask" aria-live="polite" aria-busy="true">
|
||||||
<div className="chart-loading-panel">{loadingHint || '加载中'}</div>
|
<div className="chart-loading-panel">{loadingHint || '加载中'}</div>
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import { StrictMode } from 'react';
|
|||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { AllCommunityModule, ModuleRegistry } from 'ag-charts-community';
|
import { AllCommunityModule, ModuleRegistry } from 'ag-charts-community';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
import 'ag-grid-community/styles/ag-grid.css';
|
||||||
|
import 'ag-grid-community/styles/ag-theme-quartz.css';
|
||||||
import './styles.css';
|
import './styles.css';
|
||||||
|
|
||||||
ModuleRegistry.registerModules([AllCommunityModule]);
|
ModuleRegistry.registerModules([AllCommunityModule]);
|
||||||
|
|||||||
132
src/styles.css
132
src/styles.css
@ -64,7 +64,7 @@ button {
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(540px, 52vw) 1fr;
|
grid-template-columns: minmax(0, 7fr) minmax(320px, 3fr);
|
||||||
grid-template-rows: auto minmax(0, 1fr);
|
grid-template-rows: auto minmax(0, 1fr);
|
||||||
gap: 12px 28px;
|
gap: 12px 28px;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
@ -242,7 +242,7 @@ button {
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(540px, 52vw) 1fr;
|
grid-template-columns: minmax(0, 7fr) minmax(320px, 3fr);
|
||||||
grid-template-rows: auto minmax(0, 1fr);
|
grid-template-rows: auto minmax(0, 1fr);
|
||||||
gap: 12px 28px;
|
gap: 12px 28px;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
@ -362,15 +362,19 @@ button {
|
|||||||
.chart-frame .chart-template-button,
|
.chart-frame .chart-template-button,
|
||||||
.chart-frame .chart-indicator-button,
|
.chart-frame .chart-indicator-button,
|
||||||
.chart-frame .chart-statistic-button,
|
.chart-frame .chart-statistic-button,
|
||||||
|
.chart-frame .chart-pivot-button,
|
||||||
.chart-frame .chart-fullscreen-button {
|
.chart-frame .chart-fullscreen-button {
|
||||||
color: #46413b;
|
color: #46413b;
|
||||||
cursor: pointer !important;
|
cursor: pointer !important;
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
|
overflow: visible !important;
|
||||||
pointer-events: auto !important;
|
pointer-events: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-frame .ag-charts-myButton-template,
|
.chart-frame .ag-charts-myButton-template,
|
||||||
.chart-frame .ag-charts-myButton-indicator {
|
.chart-frame .ag-charts-myButton-indicator,
|
||||||
|
.chart-frame .ag-charts-myButton-pivot {
|
||||||
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
min-width: 16px;
|
min-width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
@ -380,9 +384,36 @@ button {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chart-indicator-selection-label {
|
||||||
|
position: absolute;
|
||||||
|
left: 28px;
|
||||||
|
top: 6px;
|
||||||
|
z-index: 3;
|
||||||
|
max-width: min(560px, calc(100vw - 56px));
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
color: #0078a8;
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace:fullscreen .chart-indicator-selection-label {
|
||||||
|
left: 28px;
|
||||||
|
top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.chart-frame .chart-template-button[aria-expanded="true"],
|
.chart-frame .chart-template-button[aria-expanded="true"],
|
||||||
.chart-frame .chart-indicator-button[aria-expanded="true"],
|
.chart-frame .chart-indicator-button[aria-expanded="true"],
|
||||||
.chart-frame .chart-statistic-button[aria-expanded="true"],
|
.chart-frame .chart-statistic-button[aria-expanded="true"],
|
||||||
|
.chart-frame .chart-pivot-button.ag-charts-toolbar__button--active,
|
||||||
.chart-frame .chart-fullscreen-button.ag-charts-toolbar__button--active {
|
.chart-frame .chart-fullscreen-button.ag-charts-toolbar__button--active {
|
||||||
color: #0078a8;
|
color: #0078a8;
|
||||||
border-color: rgba(0, 120, 168, 0.36);
|
border-color: rgba(0, 120, 168, 0.36);
|
||||||
@ -390,11 +421,87 @@ button {
|
|||||||
box-shadow: 0 1px 5px rgba(69, 54, 36, 0.12);
|
box-shadow: 0 1px 5px rgba(69, 54, 36, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chart-pivot-grid-panel {
|
||||||
|
position: absolute;
|
||||||
|
inset: 16px 0 0;
|
||||||
|
z-index: 10;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 12px 16px 16px 74px;
|
||||||
|
border: 1px solid rgba(90, 82, 72, 0.22);
|
||||||
|
border-radius: 3px;
|
||||||
|
background: rgba(255, 249, 241, 0.96);
|
||||||
|
box-shadow: 0 6px 18px rgba(69, 54, 36, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-pivot-grid-panel .ag-root-wrapper {
|
||||||
|
border-color: rgba(90, 82, 72, 0.18);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-grid-tool-button {
|
||||||
|
position: absolute;
|
||||||
|
left: 24px;
|
||||||
|
z-index: 12;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
min-width: 26px;
|
||||||
|
min-height: 26px;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid rgba(90, 82, 72, 0.22);
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #46413b;
|
||||||
|
background: rgba(255, 249, 241, 0.72);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-grid-tool-button--template {
|
||||||
|
top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-grid-tool-button--indicator {
|
||||||
|
top: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-grid-tool-button--trend {
|
||||||
|
top: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-grid-tool-button:hover {
|
||||||
|
color: #0078a8;
|
||||||
|
border-color: rgba(0, 120, 168, 0.36);
|
||||||
|
background: rgba(255, 252, 248, 0.94);
|
||||||
|
box-shadow: 0 1px 5px rgba(69, 54, 36, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-pivot-grid-panel.ag-theme-quartz {
|
||||||
|
--ag-active-color: #0078a8;
|
||||||
|
--ag-background-color: #fffaf4;
|
||||||
|
--ag-border-color: rgba(90, 82, 72, 0.18);
|
||||||
|
--ag-font-family: "Microsoft YaHei", "PingFang SC", "Segoe UI", Arial, sans-serif;
|
||||||
|
--ag-font-size: 12px;
|
||||||
|
--ag-foreground-color: #262a33;
|
||||||
|
--ag-header-background-color: #f6eadc;
|
||||||
|
--ag-row-hover-color: rgba(0, 120, 168, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
.metric-switcher {
|
.metric-switcher {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
z-index: 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-switcher--chart {
|
||||||
left: 74px;
|
left: 74px;
|
||||||
top: 272px;
|
top: 272px;
|
||||||
z-index: 12;
|
}
|
||||||
|
|
||||||
|
.metric-switcher--grid {
|
||||||
|
left: 24px;
|
||||||
|
top: 114px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metric-switcher-button {
|
.metric-switcher-button {
|
||||||
@ -420,6 +527,23 @@ button {
|
|||||||
text-orientation: upright;
|
text-orientation: upright;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.metric-switcher-button--grid {
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
min-width: 26px;
|
||||||
|
min-height: 26px;
|
||||||
|
padding: 0;
|
||||||
|
border-color: rgba(90, 82, 72, 0.22);
|
||||||
|
color: #46413b;
|
||||||
|
background: rgba(255, 249, 241, 0.72);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0;
|
||||||
|
line-height: 24px;
|
||||||
|
writing-mode: horizontal-tb;
|
||||||
|
text-orientation: mixed;
|
||||||
|
}
|
||||||
|
|
||||||
.metric-switcher-button:hover,
|
.metric-switcher-button:hover,
|
||||||
.metric-switcher-button[aria-expanded="true"] {
|
.metric-switcher-button[aria-expanded="true"] {
|
||||||
color: #0078a8;
|
color: #0078a8;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user