[feat]:[FL-123][重构雷电流幅值统计页面:导入记录表格+操作列弹窗]
改动摘要: - 重构页面布局为用户管理页面风格,主表格展示导入记录元数据 - 添加操作列下拉菜单(MoreOutlined),包含 P 曲线、事件详情、采样预览、删除 - P 曲线弹窗展示超越概率表格 - 事件详情弹窗展示全部特征参数 - 采样预览弹窗使用 recharts 折线图展示波形(替代原表格) - 导入功能在卡片工具栏按钮 + Modal 表单中完成 - 安装 recharts npm 包用于波形图表展示 - 移除原有的多卡片布局,简化为单卡片主视图 - 保持分页和删除功能正常 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
Generated
+391
@@ -406,6 +406,42 @@
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz",
|
||||
"integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
"@standard-schema/utils": "^0.3.0",
|
||||
"immer": "^11.0.0",
|
||||
"redux": "^5.0.1",
|
||||
"redux-thunk": "^3.1.0",
|
||||
"reselect": "^5.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
|
||||
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-redux": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit/node_modules/immer": {
|
||||
"version": "11.1.8",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-11.1.8.tgz",
|
||||
"integrity": "sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/@spz-loader/core": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@spz-loader/core/-/core-0.3.1.tgz",
|
||||
@@ -416,6 +452,18 @@
|
||||
"pnpm": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@standard-schema/utils": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
|
||||
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "5.99.0",
|
||||
"license": "MIT",
|
||||
@@ -444,6 +492,69 @@
|
||||
"integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-array": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
|
||||
"integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-color": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-ease": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
|
||||
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-interpolate": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
|
||||
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-color": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-path": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
|
||||
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-scale": {
|
||||
"version": "4.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
|
||||
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-time": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-shape": {
|
||||
"version": "3.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
|
||||
"integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-path": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-time": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
|
||||
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-timer": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
||||
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
|
||||
@@ -460,6 +571,12 @@
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/use-sync-external-store": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@zip.js/zip.js": {
|
||||
"version": "2.8.26",
|
||||
"resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.26.tgz",
|
||||
@@ -632,12 +749,139 @@
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-ease": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-format": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
|
||||
"integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-path": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
|
||||
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-scale": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-array": "2.10.0 - 3",
|
||||
"d3-format": "1 - 3",
|
||||
"d3-interpolate": "1.2.0 - 3",
|
||||
"d3-time": "2.1.1 - 3",
|
||||
"d3-time-format": "2 - 4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-shape": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
|
||||
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-path": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
||||
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-array": "2 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time-format": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
|
||||
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-time": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-timer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.20",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz",
|
||||
"integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/decimal.js-light": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dom-align": {
|
||||
"version": "1.12.4",
|
||||
"resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.4.tgz",
|
||||
@@ -665,6 +909,22 @@
|
||||
"integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/es-toolkit": {
|
||||
"version": "1.47.1",
|
||||
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.1.tgz",
|
||||
"integrity": "sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"docs",
|
||||
"benchmarks"
|
||||
]
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
|
||||
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "12.38.0",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz",
|
||||
@@ -698,6 +958,25 @@
|
||||
"integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/immer": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
|
||||
"integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/internmap": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/jsep": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz",
|
||||
@@ -1461,6 +1740,80 @@
|
||||
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz",
|
||||
"integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.2.25 || ^19",
|
||||
"react": "^18.0 || ^19",
|
||||
"redux": "^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"redux": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/recharts": {
|
||||
"version": "3.8.1",
|
||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz",
|
||||
"integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"www"
|
||||
],
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.9.0 || 2.x.x",
|
||||
"clsx": "^2.1.1",
|
||||
"decimal.js-light": "^2.5.1",
|
||||
"es-toolkit": "^1.39.3",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"immer": "^10.1.1",
|
||||
"react-redux": "8.x.x || 9.x.x",
|
||||
"reselect": "5.1.1",
|
||||
"tiny-invariant": "^1.3.3",
|
||||
"use-sync-external-store": "^1.2.2",
|
||||
"victory-vendor": "^37.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/redux": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
||||
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"redux": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reselect": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/resize-observer-polyfill": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
||||
@@ -1510,6 +1863,12 @@
|
||||
"node": ">=12.22"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/toggle-selection": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
||||
@@ -1548,6 +1907,15 @@
|
||||
"integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/utility-types": {
|
||||
"version": "3.11.0",
|
||||
"resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz",
|
||||
@@ -1557,6 +1925,28 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/victory-vendor": {
|
||||
"version": "37.3.6",
|
||||
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
|
||||
"integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
|
||||
"license": "MIT AND ISC",
|
||||
"dependencies": {
|
||||
"@types/d3-array": "^3.0.3",
|
||||
"@types/d3-ease": "^3.0.0",
|
||||
"@types/d3-interpolate": "^3.0.1",
|
||||
"@types/d3-scale": "^4.0.2",
|
||||
"@types/d3-shape": "^3.1.0",
|
||||
"@types/d3-time": "^3.0.0",
|
||||
"@types/d3-timer": "^3.0.0",
|
||||
"d3-array": "^3.1.6",
|
||||
"d3-ease": "^3.0.1",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-shape": "^3.1.0",
|
||||
"d3-time": "^3.0.0",
|
||||
"d3-timer": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/web": {
|
||||
"resolved": "web",
|
||||
"link": true
|
||||
@@ -1575,6 +1965,7 @@
|
||||
"next": "16.2.3",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"recharts": "^3.8.1",
|
||||
"tailwind-merge": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"next": "16.2.3",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"recharts": "^3.8.1",
|
||||
"tailwind-merge": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -3,24 +3,26 @@
|
||||
import Link from "next/link";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Checkbox,
|
||||
Descriptions,
|
||||
Dropdown,
|
||||
Empty,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Popconfirm,
|
||||
Select,
|
||||
Space,
|
||||
Table,
|
||||
Tag,
|
||||
Typography,
|
||||
type MenuProps,
|
||||
} from "antd";
|
||||
import { MoreOutlined } from "@ant-design/icons";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts";
|
||||
|
||||
import { useAuth } from "@/components/auth-provider";
|
||||
import { AdminPageLoading } from "@/components/admin-page-loading";
|
||||
@@ -28,15 +30,11 @@ import { Card } from "@/components/ui-antd";
|
||||
import { useToastFeedback } from "@/hooks/use-toast-feedback";
|
||||
import { useTopicSubscription } from "@/hooks/use-topic-subscription";
|
||||
import { readApiError } from "@/lib/api";
|
||||
import { readLinePreparation } from "@/lib/line-preparation";
|
||||
import type {
|
||||
LineListResponse,
|
||||
LineSummary,
|
||||
LightningCurrentEventListResponse,
|
||||
LightningCurrentEventSummary,
|
||||
LightningCurrentExceedanceResponse,
|
||||
LightningCurrentImportResponse,
|
||||
LightningCurrentPreparationResponse,
|
||||
LightningCurrentSampleListResponse,
|
||||
LightningCurrentSampleItem,
|
||||
LightningPolarity,
|
||||
@@ -68,20 +66,6 @@ const INITIAL_IMPORT_VALUES: ImportFormValues = {
|
||||
notes: "",
|
||||
};
|
||||
|
||||
const POLARITY_OPTIONS = [
|
||||
{ value: "all", label: "全部极性" },
|
||||
{ value: "positive", label: "正极性" },
|
||||
{ value: "negative", label: "负极性" },
|
||||
{ value: "mixed", label: "混合" },
|
||||
{ value: "unknown", label: "未知" },
|
||||
] as const;
|
||||
|
||||
const SYNTHETIC_OPTIONS = [
|
||||
{ value: "all", label: "全部来源" },
|
||||
{ value: "false", label: "实测" },
|
||||
{ value: "true", label: "合成" },
|
||||
] as const;
|
||||
|
||||
function formatNullable(value: number | string | null | undefined): string {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return "-";
|
||||
@@ -108,64 +92,19 @@ export default function AdminLightningCurrentsPage() {
|
||||
const queryClient = useQueryClient();
|
||||
const uploadInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const [importForm] = Form.useForm<ImportFormValues>();
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [regionFilter, setRegionFilter] = useState("");
|
||||
const [polarityFilter, setPolarityFilter] = useState<(typeof POLARITY_OPTIONS)[number]["value"]>("all");
|
||||
const [syntheticFilter, setSyntheticFilter] = useState<(typeof SYNTHETIC_OPTIONS)[number]["value"]>("all");
|
||||
const [selectedEventId, setSelectedEventId] = useState<string | null>(null);
|
||||
const [selectedLineId, setSelectedLineId] = useState("");
|
||||
|
||||
const [error, setError] = useState("");
|
||||
const [success, setSuccess] = useState("");
|
||||
const [importModalOpen, setImportModalOpen] = useState(false);
|
||||
const [exceedanceModalOpen, setExceedanceModalOpen] = useState(false);
|
||||
const [detailModalOpen, setDetailModalOpen] = useState(false);
|
||||
const [sampleModalOpen, setSampleModalOpen] = useState(false);
|
||||
const [selectedEventForModal, setSelectedEventForModal] = useState<LightningCurrentEventSummary | null>(null);
|
||||
|
||||
const canRead = hasPermission("lightning.read") || hasPermission("lightning.manage");
|
||||
const canManage = hasPermission("lightning.manage");
|
||||
|
||||
const eventListPath = useMemo(() => {
|
||||
const params = new URLSearchParams();
|
||||
if (keyword.trim()) {
|
||||
params.set("keyword", keyword.trim());
|
||||
}
|
||||
if (regionFilter.trim()) {
|
||||
params.set("region_id", regionFilter.trim());
|
||||
}
|
||||
if (polarityFilter !== "all") {
|
||||
params.set("polarity", polarityFilter);
|
||||
}
|
||||
if (syntheticFilter !== "all") {
|
||||
params.set("is_synthetic", syntheticFilter);
|
||||
}
|
||||
params.set("limit", "200");
|
||||
params.set("offset", "0");
|
||||
return `/api/v1/lightning-currents?${params.toString()}`;
|
||||
}, [keyword, regionFilter, polarityFilter, syntheticFilter]);
|
||||
|
||||
const exceedancePath = useMemo(() => {
|
||||
const params = new URLSearchParams();
|
||||
if (regionFilter.trim()) {
|
||||
params.set("region_id", regionFilter.trim());
|
||||
}
|
||||
if (polarityFilter !== "all") {
|
||||
params.set("polarity", polarityFilter);
|
||||
}
|
||||
if (syntheticFilter !== "all") {
|
||||
params.set("is_synthetic", syntheticFilter);
|
||||
}
|
||||
return `/api/v1/lightning-currents/stats/exceedance${params.toString() ? `?${params.toString()}` : ""}`;
|
||||
}, [regionFilter, polarityFilter, syntheticFilter]);
|
||||
|
||||
const linesQuery = useQuery({
|
||||
queryKey: ["/api/v1/lines"],
|
||||
enabled: !!user && canRead,
|
||||
queryFn: async () => {
|
||||
const response = await fetchWithAuth("/api/v1/lines");
|
||||
if (!response.ok) {
|
||||
throw new Error(await readApiError(response));
|
||||
}
|
||||
return (await response.json()) as LineListResponse;
|
||||
},
|
||||
});
|
||||
const activeSelectedLineId = selectedLineId || linesQuery.data?.items[0]?.id || "";
|
||||
const eventListPath = "/api/v1/lightning-currents?limit=200&offset=0";
|
||||
|
||||
const eventsQuery = useQuery({
|
||||
queryKey: [eventListPath],
|
||||
@@ -178,20 +117,34 @@ export default function AdminLightningCurrentsPage() {
|
||||
return (await response.json()) as LightningCurrentEventListResponse;
|
||||
},
|
||||
});
|
||||
|
||||
const events = useMemo(() => eventsQuery.data?.items ?? [], [eventsQuery.data?.items]);
|
||||
const activeSelectedEventId = selectedEventId && events.some((item) => item.id === selectedEventId)
|
||||
? selectedEventId
|
||||
: (events[0]?.id ?? null);
|
||||
|
||||
const exceedancePath = useMemo(() => {
|
||||
if (!selectedEventForModal?.id) return "";
|
||||
return `/api/v1/lightning-currents/stats/exceedance`;
|
||||
}, [selectedEventForModal?.id]);
|
||||
|
||||
const exceedanceQuery = useQuery({
|
||||
queryKey: [exceedancePath, selectedEventForModal?.id],
|
||||
enabled: !!user && canRead && exceedanceModalOpen && !!selectedEventForModal,
|
||||
queryFn: async () => {
|
||||
const response = await fetchWithAuth(exceedancePath);
|
||||
if (!response.ok) {
|
||||
throw new Error(await readApiError(response));
|
||||
}
|
||||
return (await response.json()) as LightningCurrentExceedanceResponse;
|
||||
},
|
||||
});
|
||||
|
||||
const samplePath = useMemo(() => {
|
||||
if (!activeSelectedEventId) {
|
||||
return "";
|
||||
}
|
||||
return `/api/v1/lightning-currents/${activeSelectedEventId}/samples?limit=200&offset=0`;
|
||||
}, [activeSelectedEventId]);
|
||||
if (!selectedEventForModal?.id) return "";
|
||||
return `/api/v1/lightning-currents/${selectedEventForModal.id}/samples?limit=200&offset=0`;
|
||||
}, [selectedEventForModal?.id]);
|
||||
|
||||
const samplesQuery = useQuery({
|
||||
queryKey: [samplePath],
|
||||
enabled: !!user && canRead && !!activeSelectedEventId,
|
||||
enabled: !!user && canRead && sampleModalOpen && !!selectedEventForModal,
|
||||
queryFn: async () => {
|
||||
if (!samplePath) {
|
||||
return { items: [], total: 0, limit: 0, offset: 0 } satisfies LightningCurrentSampleListResponse;
|
||||
@@ -204,18 +157,6 @@ export default function AdminLightningCurrentsPage() {
|
||||
},
|
||||
});
|
||||
|
||||
const exceedanceQuery = useQuery({
|
||||
queryKey: [exceedancePath],
|
||||
enabled: !!user && canRead,
|
||||
queryFn: async () => {
|
||||
const response = await fetchWithAuth(exceedancePath);
|
||||
if (!response.ok) {
|
||||
throw new Error(await readApiError(response));
|
||||
}
|
||||
return (await response.json()) as LightningCurrentExceedanceResponse;
|
||||
},
|
||||
});
|
||||
|
||||
const listError = eventsQuery.error instanceof Error ? eventsQuery.error.message : "";
|
||||
const sampleError = samplesQuery.error instanceof Error ? samplesQuery.error.message : "";
|
||||
const statsError = exceedanceQuery.error instanceof Error ? exceedanceQuery.error.message : "";
|
||||
@@ -232,10 +173,7 @@ export default function AdminLightningCurrentsPage() {
|
||||
predicate: (query) =>
|
||||
Array.isArray(query.queryKey)
|
||||
&& typeof query.queryKey[0] === "string"
|
||||
&& (
|
||||
query.queryKey[0].startsWith("/api/v1/lightning-currents")
|
||||
|| query.queryKey[0].startsWith("/api/v1/lines")
|
||||
),
|
||||
&& query.queryKey[0].startsWith("/api/v1/lightning-currents"),
|
||||
});
|
||||
}, [queryClient]);
|
||||
|
||||
@@ -246,18 +184,6 @@ export default function AdminLightningCurrentsPage() {
|
||||
}, [refreshAll]),
|
||||
);
|
||||
|
||||
const samples = samplesQuery.data?.items ?? [];
|
||||
const exceedance = exceedanceQuery.data?.thresholds ?? [];
|
||||
const selectedEvent = useMemo(
|
||||
() => events.find((item) => item.id === activeSelectedEventId) ?? null,
|
||||
[activeSelectedEventId, events],
|
||||
);
|
||||
const selectedLine = useMemo(
|
||||
() => linesQuery.data?.items.find((item) => item.id === activeSelectedLineId) ?? null,
|
||||
[activeSelectedLineId, linesQuery.data?.items],
|
||||
);
|
||||
const selectedLinePreparation = useMemo(() => readLinePreparation(selectedLine), [selectedLine]);
|
||||
|
||||
const importMutation = useMutation({
|
||||
mutationFn: async (file: File) => {
|
||||
if (!canManage) {
|
||||
@@ -295,7 +221,6 @@ export default function AdminLightningCurrentsPage() {
|
||||
? `导入完成,存在 ${payload.warning_count} 条告警`
|
||||
: "导入完成并已提取防雷特征参数",
|
||||
);
|
||||
setSelectedEventId(payload.event.id);
|
||||
setImportModalOpen(false);
|
||||
importForm.resetFields();
|
||||
await refreshAll();
|
||||
@@ -316,10 +241,7 @@ export default function AdminLightningCurrentsPage() {
|
||||
}
|
||||
return eventId;
|
||||
},
|
||||
onSuccess: async (eventId) => {
|
||||
if (selectedEventId === eventId) {
|
||||
setSelectedEventId(null);
|
||||
}
|
||||
onSuccess: async () => {
|
||||
setError("");
|
||||
setSuccess("雷电流事件已删除");
|
||||
await refreshAll();
|
||||
@@ -330,35 +252,20 @@ export default function AdminLightningCurrentsPage() {
|
||||
},
|
||||
});
|
||||
|
||||
const prepareCurrentMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!activeSelectedLineId) {
|
||||
throw new Error("请选择线路");
|
||||
}
|
||||
const response = await fetchWithAuth("/api/v1/lightning-currents/prepare-current", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
line_id: activeSelectedLineId,
|
||||
region_id: regionFilter.trim() || null,
|
||||
is_synthetic: syntheticFilter === "all" ? null : syntheticFilter === "true",
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(await readApiError(response));
|
||||
}
|
||||
return (await response.json()) as LightningCurrentPreparationResponse;
|
||||
},
|
||||
onSuccess: async (payload) => {
|
||||
setError("");
|
||||
setSuccess(`已为 ${payload.line.name || payload.line.code} 回填雷电流幅值 a/b = ${payload.current_a} / ${payload.current_b}`);
|
||||
await refreshAll();
|
||||
},
|
||||
onError: (candidate) => {
|
||||
setSuccess("");
|
||||
setError(candidate instanceof Error ? candidate.message : "线路雷电流回填失败");
|
||||
},
|
||||
});
|
||||
const openExceedanceModal = (event: LightningCurrentEventSummary) => {
|
||||
setSelectedEventForModal(event);
|
||||
setExceedanceModalOpen(true);
|
||||
};
|
||||
|
||||
const openDetailModal = (event: LightningCurrentEventSummary) => {
|
||||
setSelectedEventForModal(event);
|
||||
setDetailModalOpen(true);
|
||||
};
|
||||
|
||||
const openSampleModal = (event: LightningCurrentEventSummary) => {
|
||||
setSelectedEventForModal(event);
|
||||
setSampleModalOpen(true);
|
||||
};
|
||||
|
||||
const eventColumns = useMemo<ColumnsType<LightningCurrentEventSummary>>(
|
||||
() => [
|
||||
@@ -369,35 +276,29 @@ export default function AdminLightningCurrentsPage() {
|
||||
render: (value: string) => <Typography.Text code>{value}</Typography.Text>,
|
||||
},
|
||||
{
|
||||
title: "峰值(kA)",
|
||||
dataIndex: "peak_abs_current_ka",
|
||||
width: 100,
|
||||
render: (value: number | null) => formatNumber(value, 2),
|
||||
},
|
||||
{
|
||||
title: "波形",
|
||||
dataIndex: "wave_shape",
|
||||
width: 90,
|
||||
title: "文件名称",
|
||||
dataIndex: "source_file_name",
|
||||
width: 200,
|
||||
render: (value: string | null) => value || "-",
|
||||
},
|
||||
{
|
||||
title: "T1/T2(us)",
|
||||
key: "t1t2",
|
||||
width: 140,
|
||||
render: (_: unknown, row) => `${formatNumber(row.wavefront_time_t1_us, 2)} / ${formatNumber(row.half_value_time_t2_us, 2)}`,
|
||||
title: "导入时间",
|
||||
dataIndex: "create_date",
|
||||
width: 180,
|
||||
render: (value: string) => new Date(value).toLocaleString("zh-CN", { hour12: false }),
|
||||
},
|
||||
{
|
||||
title: "陡度(kA/us)",
|
||||
dataIndex: "steepness_ka_per_us",
|
||||
width: 130,
|
||||
render: (value: number | null) => formatNumber(value, 3),
|
||||
},
|
||||
{
|
||||
title: "I²t(J/Ω)",
|
||||
dataIndex: "action_integral_j_ohm",
|
||||
width: 140,
|
||||
title: "峰值电流(kA)",
|
||||
dataIndex: "peak_abs_current_ka",
|
||||
width: 120,
|
||||
render: (value: number | null) => formatNumber(value, 2),
|
||||
},
|
||||
{
|
||||
title: "波形分类",
|
||||
dataIndex: "wave_shape",
|
||||
width: 100,
|
||||
render: (value: string | null) => value || "-",
|
||||
},
|
||||
{
|
||||
title: "极性",
|
||||
dataIndex: "polarity",
|
||||
@@ -413,14 +314,15 @@ export default function AdminLightningCurrentsPage() {
|
||||
},
|
||||
{
|
||||
title: "区域",
|
||||
dataIndex: "location_tag",
|
||||
width: 160,
|
||||
dataIndex: "region_id",
|
||||
width: 120,
|
||||
render: (value: string | null) => value || "-",
|
||||
},
|
||||
{
|
||||
title: "采样点数",
|
||||
dataIndex: "sample_count",
|
||||
width: 100,
|
||||
title: "地域标签",
|
||||
dataIndex: "location_tag",
|
||||
width: 150,
|
||||
render: (value: string | null) => value || "-",
|
||||
},
|
||||
{
|
||||
title: "来源",
|
||||
@@ -428,42 +330,74 @@ export default function AdminLightningCurrentsPage() {
|
||||
width: 80,
|
||||
render: (value: boolean) => (value ? <Tag color="purple">合成</Tag> : <Tag color="green">实测</Tag>),
|
||||
},
|
||||
{
|
||||
title: "采样点数",
|
||||
dataIndex: "sample_count",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "actions",
|
||||
width: 100,
|
||||
fixed: "right",
|
||||
render: (_: unknown, row) =>
|
||||
canManage ? (
|
||||
<Popconfirm
|
||||
title="删除事件"
|
||||
description={`确认删除事件 ${row.event_id} 吗?`}
|
||||
okText="删除"
|
||||
cancelText="取消"
|
||||
okButtonProps={{ danger: true }}
|
||||
onConfirm={async () => {
|
||||
await deleteMutation.mutateAsync(row.id);
|
||||
render: (_: unknown, row) => {
|
||||
const moreMenuItems: MenuProps["items"] = [
|
||||
{
|
||||
key: "exceedance",
|
||||
label: "峰值超越概率(P 曲线)",
|
||||
onClick: () => openExceedanceModal(row),
|
||||
},
|
||||
{
|
||||
key: "detail",
|
||||
label: "雷电流事件详情",
|
||||
onClick: () => openDetailModal(row),
|
||||
},
|
||||
{
|
||||
key: "sample",
|
||||
label: "采样预览",
|
||||
onClick: () => openSampleModal(row),
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: "删除",
|
||||
danger: true,
|
||||
disabled: !canManage,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: moreMenuItems,
|
||||
onClick: ({ key }) => {
|
||||
if (key === "delete") {
|
||||
Modal.confirm({
|
||||
title: "删除事件",
|
||||
content: `确认删除事件 ${row.event_id} 吗?`,
|
||||
okText: "删除",
|
||||
cancelText: "取消",
|
||||
okButtonProps: { danger: true },
|
||||
onOk: async () => {
|
||||
await deleteMutation.mutateAsync(row.id);
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
}}
|
||||
trigger={["click"]}
|
||||
>
|
||||
<Button size="small" danger loading={deleteMutation.isPending}>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
) : null,
|
||||
<Button size="small" icon={<MoreOutlined />} />
|
||||
</Dropdown>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[canManage, deleteMutation],
|
||||
);
|
||||
|
||||
const sampleColumns = useMemo<ColumnsType<LightningCurrentSampleItem>>(
|
||||
() => [
|
||||
{ title: "序号", dataIndex: "seq_no", width: 90 },
|
||||
{ title: "时间(us)", dataIndex: "time_us", width: 140, render: (value: number) => formatNumber(value, 6) },
|
||||
{ title: "电流(kA)", dataIndex: "current_ka", width: 140, render: (value: number) => formatNumber(value, 6) },
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
if (initializing || eventsQuery.isLoading) {
|
||||
return (
|
||||
<AdminPageLoading
|
||||
@@ -501,94 +435,34 @@ export default function AdminLightningCurrentsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
const samples = samplesQuery.data?.items ?? [];
|
||||
const exceedance = exceedanceQuery.data?.thresholds ?? [];
|
||||
|
||||
const chartData = samples.map((item) => ({
|
||||
time_us: item.time_us,
|
||||
current_ka: item.current_ka,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Space direction="vertical" size={16} className="w-full">
|
||||
<Card title="线路参数准备">
|
||||
<Space direction="vertical" size={12} className="w-full">
|
||||
<Typography.Text type="secondary">
|
||||
将当前雷电数据筛选结果按线路回填为"雷电流幅值"准备项;创建防雷分析任务前会使用这里的就绪状态做校验。
|
||||
</Typography.Text>
|
||||
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
value={activeSelectedLineId || undefined}
|
||||
onChange={setSelectedLineId}
|
||||
placeholder="选择线路"
|
||||
loading={linesQuery.isLoading}
|
||||
options={(linesQuery.data?.items ?? []).map((item: LineSummary) => ({
|
||||
value: item.id,
|
||||
label: `${item.name || item.code} / ${item.code}`,
|
||||
}))}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => prepareCurrentMutation.mutate()}
|
||||
loading={prepareCurrentMutation.isPending}
|
||||
disabled={!canManage || !activeSelectedLineId}
|
||||
block
|
||||
>
|
||||
回填雷电流幅值
|
||||
</Button>
|
||||
</div>
|
||||
{selectedLine ? (
|
||||
<Alert
|
||||
type={selectedLinePreparation.all_ready ? "success" : "warning"}
|
||||
showIcon
|
||||
message={selectedLinePreparation.all_ready ? "当前线路准备已齐备" : `缺少:${selectedLinePreparation.missing_items.join("、")}`}
|
||||
description={
|
||||
<Space size={[8, 8]} wrap>
|
||||
{[
|
||||
selectedLinePreparation.lightning_current,
|
||||
selectedLinePreparation.lightning_density,
|
||||
selectedLinePreparation.ground_slope,
|
||||
].map((item) => {
|
||||
const source = item.source;
|
||||
const preparedAt = typeof source.prepared_at === "string" ? source.prepared_at : null;
|
||||
const values = item.values;
|
||||
const currentA = typeof values.current_a === "number" ? values.current_a : null;
|
||||
const currentB = typeof values.current_b === "number" ? values.current_b : null;
|
||||
return (
|
||||
<Tag key={item.key} color={item.ready ? "green" : "red"}>
|
||||
{`${item.label}${currentA !== null && currentB !== null ? ` (${formatNumber(currentA, 3)} / ${formatNumber(currentB, 3)})` : ""} ${item.tower_ready_count}/${item.tower_total_count}${preparedAt ? ` @ ${new Date(preparedAt).toLocaleString("zh-CN", { hour12: false })}` : ""}`}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
<Card title="雷电幅值统计导入与事件管理">
|
||||
<Space direction="vertical" size={12} className="w-full">
|
||||
<Typography.Text type="secondary">
|
||||
上传原始雷电流序列后,系统将自动提取峰值、T1/T2、陡度、I²t、多回击等防雷计算参数。
|
||||
</Typography.Text>
|
||||
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
||||
<Input
|
||||
value={keyword}
|
||||
allowClear
|
||||
onChange={(event) => setKeyword(event.target.value)}
|
||||
placeholder="按事件编号/地点/城市筛选"
|
||||
/>
|
||||
<Input
|
||||
value={regionFilter}
|
||||
allowClear
|
||||
onChange={(event) => setRegionFilter(event.target.value)}
|
||||
placeholder="按 Region ID 筛选"
|
||||
/>
|
||||
<Select value={polarityFilter} options={[...POLARITY_OPTIONS]} onChange={(value) => setPolarityFilter(value)} />
|
||||
<Select value={syntheticFilter} options={[...SYNTHETIC_OPTIONS]} onChange={(value) => setSyntheticFilter(value)} />
|
||||
</div>
|
||||
|
||||
{canManage && (
|
||||
<Card
|
||||
title="导入记录管理"
|
||||
extra={
|
||||
canManage && (
|
||||
<Button type="primary" onClick={() => setImportModalOpen(true)}>
|
||||
导入雷电流数据
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Table<LightningCurrentEventSummary>
|
||||
rowKey={(row) => row.id}
|
||||
columns={eventColumns}
|
||||
dataSource={events}
|
||||
loading={eventsQuery.isFetching}
|
||||
pagination={{ pageSize: 20, showSizeChanger: true, showTotal: (total) => `共 ${total} 条` }}
|
||||
scroll={{ x: 1700 }}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
@@ -662,7 +536,17 @@ export default function AdminLightningCurrentsPage() {
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Card title="峰值超越概率(P 曲线)">
|
||||
<Modal
|
||||
title={selectedEventForModal ? `峰值超越概率(P 曲线) - ${selectedEventForModal.event_id}` : "峰值超越概率(P 曲线)"}
|
||||
open={exceedanceModalOpen}
|
||||
onCancel={() => {
|
||||
setExceedanceModalOpen(false);
|
||||
setSelectedEventForModal(null);
|
||||
}}
|
||||
footer={null}
|
||||
width={800}
|
||||
destroyOnClose
|
||||
>
|
||||
{exceedance.length === 0 ? (
|
||||
<Empty description="暂无统计数据" />
|
||||
) : (
|
||||
@@ -681,77 +565,83 @@ export default function AdminLightningCurrentsPage() {
|
||||
{ title: "超越次数", dataIndex: "exceedance_count", width: 140 },
|
||||
]}
|
||||
size="small"
|
||||
loading={exceedanceQuery.isFetching}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
</Modal>
|
||||
|
||||
<Card title="雷电流事件列表">
|
||||
<Table<LightningCurrentEventSummary>
|
||||
rowKey={(row) => row.id}
|
||||
columns={eventColumns}
|
||||
dataSource={events}
|
||||
loading={eventsQuery.isFetching}
|
||||
pagination={false}
|
||||
scroll={{ x: 1700 }}
|
||||
rowClassName={(row) => (row.id === activeSelectedEventId ? "fquiz-row-selected" : "")}
|
||||
onRow={(row) => ({
|
||||
onClick: () => setSelectedEventId(row.id),
|
||||
})}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card title={selectedEvent ? `事件详情 - ${selectedEvent.event_id}` : "事件详情"}>
|
||||
{!selectedEvent ? (
|
||||
<Empty description="请先选择一条事件" />
|
||||
) : (
|
||||
<Modal
|
||||
title={selectedEventForModal ? `事件详情 - ${selectedEventForModal.event_id}` : "事件详情"}
|
||||
open={detailModalOpen}
|
||||
onCancel={() => {
|
||||
setDetailModalOpen(false);
|
||||
setSelectedEventForModal(null);
|
||||
}}
|
||||
footer={null}
|
||||
width={900}
|
||||
destroyOnClose
|
||||
>
|
||||
{selectedEventForModal && (
|
||||
<Space direction="vertical" size={12} className="w-full">
|
||||
<Descriptions bordered size="small" column={3}>
|
||||
<Descriptions.Item label="峰值电流(kA)">{formatNumber(selectedEvent.peak_current_ka, 3)}</Descriptions.Item>
|
||||
<Descriptions.Item label="绝对峰值(kA)">{formatNumber(selectedEvent.peak_abs_current_ka, 3)}</Descriptions.Item>
|
||||
<Descriptions.Item label="波形分类">{formatNullable(selectedEvent.wave_shape)}</Descriptions.Item>
|
||||
<Descriptions.Item label="T1(us)">{formatNumber(selectedEvent.wavefront_time_t1_us, 3)}</Descriptions.Item>
|
||||
<Descriptions.Item label="T2(us)">{formatNumber(selectedEvent.half_value_time_t2_us, 3)}</Descriptions.Item>
|
||||
<Descriptions.Item label="陡度(kA/us)">{formatNumber(selectedEvent.steepness_ka_per_us, 6)}</Descriptions.Item>
|
||||
<Descriptions.Item label="I²t (J/Ω)">{formatNumber(selectedEvent.action_integral_j_ohm, 3)}</Descriptions.Item>
|
||||
<Descriptions.Item label="采样间隔(us)">{formatNumber(selectedEvent.sample_interval_us, 6)}</Descriptions.Item>
|
||||
<Descriptions.Item label="采样频率(Hz)">{formatNumber(selectedEvent.sampling_frequency_hz, 2)}</Descriptions.Item>
|
||||
<Descriptions.Item label="极性">{formatPolarity(selectedEvent.polarity)}</Descriptions.Item>
|
||||
<Descriptions.Item label="回击数">{selectedEvent.stroke_count}</Descriptions.Item>
|
||||
<Descriptions.Item label="采样点数">{selectedEvent.sample_count}</Descriptions.Item>
|
||||
<Descriptions.Item label="区域">{formatNullable(selectedEvent.region_id)}</Descriptions.Item>
|
||||
<Descriptions.Item label="地域标签">{formatNullable(selectedEvent.location_tag)}</Descriptions.Item>
|
||||
<Descriptions.Item label="城市">{formatNullable(selectedEvent.city)}</Descriptions.Item>
|
||||
<Descriptions.Item label="峰值电流(kA)">{formatNumber(selectedEventForModal.peak_current_ka, 3)}</Descriptions.Item>
|
||||
<Descriptions.Item label="绝对峰值(kA)">{formatNumber(selectedEventForModal.peak_abs_current_ka, 3)}</Descriptions.Item>
|
||||
<Descriptions.Item label="波形分类">{formatNullable(selectedEventForModal.wave_shape)}</Descriptions.Item>
|
||||
<Descriptions.Item label="T1(us)">{formatNumber(selectedEventForModal.wavefront_time_t1_us, 3)}</Descriptions.Item>
|
||||
<Descriptions.Item label="T2(us)">{formatNumber(selectedEventForModal.half_value_time_t2_us, 3)}</Descriptions.Item>
|
||||
<Descriptions.Item label="陡度(kA/us)">{formatNumber(selectedEventForModal.steepness_ka_per_us, 6)}</Descriptions.Item>
|
||||
<Descriptions.Item label="I²t (J/Ω)">{formatNumber(selectedEventForModal.action_integral_j_ohm, 3)}</Descriptions.Item>
|
||||
<Descriptions.Item label="采样间隔(us)">{formatNumber(selectedEventForModal.sample_interval_us, 6)}</Descriptions.Item>
|
||||
<Descriptions.Item label="采样频率(Hz)">{formatNumber(selectedEventForModal.sampling_frequency_hz, 2)}</Descriptions.Item>
|
||||
<Descriptions.Item label="极性">{formatPolarity(selectedEventForModal.polarity)}</Descriptions.Item>
|
||||
<Descriptions.Item label="回击数">{selectedEventForModal.stroke_count}</Descriptions.Item>
|
||||
<Descriptions.Item label="采样点数">{selectedEventForModal.sample_count}</Descriptions.Item>
|
||||
<Descriptions.Item label="区域">{formatNullable(selectedEventForModal.region_id)}</Descriptions.Item>
|
||||
<Descriptions.Item label="地域标签">{formatNullable(selectedEventForModal.location_tag)}</Descriptions.Item>
|
||||
<Descriptions.Item label="城市">{formatNullable(selectedEventForModal.city)}</Descriptions.Item>
|
||||
<Descriptions.Item label="经纬度">
|
||||
{selectedEvent.longitude !== null && selectedEvent.latitude !== null
|
||||
? `${selectedEvent.longitude}, ${selectedEvent.latitude}`
|
||||
{selectedEventForModal.longitude !== null && selectedEventForModal.latitude !== null
|
||||
? `${selectedEventForModal.longitude}, ${selectedEventForModal.latitude}`
|
||||
: "-"}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="传感器">{formatNullable(selectedEvent.sensor_model)}</Descriptions.Item>
|
||||
<Descriptions.Item label="安装位置">{formatNullable(selectedEvent.install_position)}</Descriptions.Item>
|
||||
<Descriptions.Item label="传感器">{formatNullable(selectedEventForModal.sensor_model)}</Descriptions.Item>
|
||||
<Descriptions.Item label="安装位置">{formatNullable(selectedEventForModal.install_position)}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
|
||||
<Typography.Text type="secondary">
|
||||
多回击峰值点:{JSON.stringify(selectedEvent.stroke_peaks_json)}
|
||||
多回击峰值点:{JSON.stringify(selectedEventForModal.stroke_peaks_json)}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
)}
|
||||
</Card>
|
||||
</Modal>
|
||||
|
||||
<Card title={selectedEvent ? `采样预览(前 200 点) - ${selectedEvent.event_id}` : "采样预览"}>
|
||||
{!selectedEvent ? (
|
||||
<Empty description="请先选择一条事件" />
|
||||
<Modal
|
||||
title={selectedEventForModal ? `采样预览 - ${selectedEventForModal.event_id}` : "采样预览"}
|
||||
open={sampleModalOpen}
|
||||
onCancel={() => {
|
||||
setSampleModalOpen(false);
|
||||
setSelectedEventForModal(null);
|
||||
}}
|
||||
footer={null}
|
||||
width={1000}
|
||||
destroyOnClose
|
||||
>
|
||||
{samples.length === 0 ? (
|
||||
<Empty description="暂无采样数据" />
|
||||
) : (
|
||||
<Table<LightningCurrentSampleItem>
|
||||
rowKey={(row) => row.id}
|
||||
columns={sampleColumns}
|
||||
dataSource={samples}
|
||||
loading={samplesQuery.isFetching}
|
||||
pagination={false}
|
||||
size="small"
|
||||
scroll={{ y: 420 }}
|
||||
/>
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
<LineChart data={chartData}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="time_us" label={{ value: "时间 (μs)", position: "insideBottom", offset: -5 }} />
|
||||
<YAxis label={{ value: "电流 (kA)", angle: -90, position: "insideLeft" }} />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Line type="monotone" dataKey="current_ka" stroke="#8884d8" name="电流 (kA)" dot={false} />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
)}
|
||||
</Card>
|
||||
{samplesQuery.isFetching && <Typography.Text type="secondary">加载中...</Typography.Text>}
|
||||
</Modal>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user