[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:
chengkai3
2026-06-15 10:55:56 +08:00
parent 72dca934e7
commit 2b80f72b9e
3 changed files with 613 additions and 331 deletions
+391
View File
@@ -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": {
+1
View File
@@ -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": {
+221 -331
View File
@@ -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">
线&quot;&quot;使
</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/T2I²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>
);
}