risk-archive.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. "use client"
  2. import {useState} from "react"
  3. import {
  4. Button,
  5. Card,
  6. Col,
  7. DatePicker,
  8. Descriptions,
  9. Form,
  10. Input,
  11. Modal,
  12. Popconfirm,
  13. Progress,
  14. Row,
  15. Select,
  16. Space,
  17. Table,
  18. Tag,
  19. } from "antd"
  20. import {DeleteOutlined, EditOutlined, EyeOutlined, PlusOutlined} from "@ant-design/icons"
  21. import {Radar} from "@ant-design/plots"
  22. import dayjs from "dayjs"
  23. import globalMessage from "@/app/_modules/globalMessage";
  24. const { Option } = Select
  25. const { TextArea } = Input
  26. // 模拟风险档案数据
  27. const mockRiskData = [
  28. {
  29. id: "R001",
  30. title: "解放路段高压管线风险",
  31. location: "解放路与建设路交叉口",
  32. riskType: "管线老化",
  33. riskLevel: "高",
  34. riskScore: 85,
  35. assessmentDate: "2024-01-15",
  36. assessor: "张三",
  37. status: "待处理",
  38. description: "高压管线使用年限超过20年,存在老化风险",
  39. impactArea: "500m范围内",
  40. affectedPopulation: 2000,
  41. economicLoss: 500000,
  42. mitigationMeasures: "定期检测,制定更换计划",
  43. monitoringFrequency: "每月",
  44. lastUpdate: "2024-01-15",
  45. factors: {
  46. 管线状态: 80,
  47. 环境因素: 70,
  48. 人员密度: 90,
  49. 设备完好性: 75,
  50. 管理水平: 85,
  51. },
  52. },
  53. {
  54. id: "R002",
  55. title: "人民路段泄漏风险",
  56. location: "人民路中段",
  57. riskType: "泄漏隐患",
  58. riskLevel: "中",
  59. riskScore: 65,
  60. assessmentDate: "2024-01-10",
  61. assessor: "李四",
  62. status: "监控中",
  63. description: "管道接头处存在微量泄漏风险",
  64. impactArea: "200m范围内",
  65. affectedPopulation: 800,
  66. economicLoss: 200000,
  67. mitigationMeasures: "加强巡检,安装泄漏检测设备",
  68. monitoringFrequency: "每周",
  69. lastUpdate: "2024-01-12",
  70. factors: {
  71. 管线状态: 60,
  72. 环境因素: 65,
  73. 人员密度: 70,
  74. 设备完好性: 80,
  75. 管理水平: 75,
  76. },
  77. },
  78. {
  79. id: "R003",
  80. title: "建设路段压力风险",
  81. location: "建设路北段",
  82. riskType: "压力异常",
  83. riskLevel: "低",
  84. riskScore: 35,
  85. assessmentDate: "2024-01-08",
  86. assessor: "王五",
  87. status: "已控制",
  88. description: "调压设备运行正常,风险可控",
  89. impactArea: "100m范围内",
  90. affectedPopulation: 300,
  91. economicLoss: 50000,
  92. mitigationMeasures: "定期维护调压设备",
  93. monitoringFrequency: "每季度",
  94. lastUpdate: "2024-01-10",
  95. factors: {
  96. 管线状态: 40,
  97. 环境因素: 35,
  98. 人员密度: 30,
  99. 设备完好性: 45,
  100. 管理水平: 40,
  101. },
  102. },
  103. ]
  104. export default function RiskArchive() {
  105. const [isModalVisible, setIsModalVisible] = useState(false)
  106. const [isDetailModalVisible, setIsDetailModalVisible] = useState(false)
  107. const [editingRecord, setEditingRecord] = useState(null)
  108. const [viewingRecord, setViewingRecord] = useState(null)
  109. const [form] = Form.useForm()
  110. const handleAdd = () => {
  111. setEditingRecord(null)
  112. form.resetFields()
  113. setIsModalVisible(true)
  114. }
  115. const handleEdit = (record: any) => {
  116. setEditingRecord(record)
  117. form.setFieldsValue({
  118. ...record,
  119. assessmentDate: record.assessmentDate ? dayjs(record.assessmentDate) : null,
  120. })
  121. setIsModalVisible(true)
  122. }
  123. const handleView = (record: any) => {
  124. setViewingRecord(record)
  125. setIsDetailModalVisible(true)
  126. }
  127. const handleDelete = (id: string) => {
  128. globalMessage.success("删除成功")
  129. }
  130. const handleModalOk = () => {
  131. form.validateFields().then((values) => {
  132. if (editingRecord) {
  133. globalMessage.success("更新成功")
  134. } else {
  135. globalMessage.success("添加成功")
  136. }
  137. setIsModalVisible(false)
  138. form.resetFields()
  139. })
  140. }
  141. const getRiskLevelColor = (level: string) => {
  142. switch (level) {
  143. case "高":
  144. return "red"
  145. case "中":
  146. return "orange"
  147. case "低":
  148. return "blue"
  149. default:
  150. return "default"
  151. }
  152. }
  153. const getStatusColor = (status: string) => {
  154. switch (status) {
  155. case "待处理":
  156. return "red"
  157. case "监控中":
  158. return "orange"
  159. case "已控制":
  160. return "green"
  161. default:
  162. return "default"
  163. }
  164. }
  165. const getRiskScoreColor = (score: number) => {
  166. if (score >= 80) return "#f5222d"
  167. if (score >= 60) return "#faad14"
  168. if (score >= 40) return "#52c41a"
  169. return "#1890ff"
  170. }
  171. const columns = [
  172. { title: "风险编号", dataIndex: "id", key: "id", width: 100 },
  173. { title: "风险标题", dataIndex: "title", key: "title", width: 200 },
  174. { title: "位置", dataIndex: "location", key: "location", width: 150 },
  175. { title: "风险类型", dataIndex: "riskType", key: "riskType", width: 120 },
  176. {
  177. title: "风险等级",
  178. dataIndex: "riskLevel",
  179. key: "riskLevel",
  180. width: 100,
  181. render: (level: string) => <Tag color={getRiskLevelColor(level)}>{level}</Tag>,
  182. },
  183. {
  184. title: "风险评分",
  185. dataIndex: "riskScore",
  186. key: "riskScore",
  187. width: 120,
  188. render: (score: number) => (
  189. <div>
  190. <Progress
  191. percent={score}
  192. size="small"
  193. strokeColor={getRiskScoreColor(score)}
  194. format={(percent) => `${percent}`}
  195. />
  196. </div>
  197. ),
  198. },
  199. { title: "评估日期", dataIndex: "assessmentDate", key: "assessmentDate", width: 120 },
  200. { title: "评估人", dataIndex: "assessor", key: "assessor", width: 100 },
  201. {
  202. title: "状态",
  203. dataIndex: "status",
  204. key: "status",
  205. width: 100,
  206. render: (status: string) => <Tag color={getStatusColor(status)}>{status}</Tag>,
  207. },
  208. { title: "影响人口", dataIndex: "affectedPopulation", key: "affectedPopulation", width: 100 },
  209. {
  210. title: "操作",
  211. key: "action",
  212. width: 200,
  213. fixed: "right",
  214. render: (_, record) => (
  215. <Space>
  216. <Button type="link" icon={<EyeOutlined />} onClick={() => handleView(record)}>
  217. 查看
  218. </Button>
  219. <Button type="link" icon={<EditOutlined />} onClick={() => handleEdit(record)}>
  220. 编辑
  221. </Button>
  222. <Popconfirm title="确定删除吗?" onConfirm={() => handleDelete(record.id)}>
  223. <Button type="link" danger icon={<DeleteOutlined />}>
  224. 删除
  225. </Button>
  226. </Popconfirm>
  227. </Space>
  228. ),
  229. },
  230. ]
  231. const radarConfig = viewingRecord
  232. ? {
  233. data: Object.entries(viewingRecord.factors).map(([key, value]) => ({
  234. factor: key,
  235. value: value,
  236. })),
  237. xField: "factor",
  238. yField: "value",
  239. area: {
  240. style: {
  241. fill: getRiskScoreColor(viewingRecord.riskScore),
  242. fillOpacity: 0.3,
  243. },
  244. },
  245. point: {
  246. size: 3,
  247. },
  248. line: {
  249. style: {
  250. stroke: getRiskScoreColor(viewingRecord.riskScore),
  251. lineWidth: 2,
  252. },
  253. },
  254. }
  255. : null
  256. const highRiskCount = mockRiskData.filter((item) => item.riskLevel === "高").length
  257. const mediumRiskCount = mockRiskData.filter((item) => item.riskLevel === "中").length
  258. const lowRiskCount = mockRiskData.filter((item) => item.riskLevel === "低").length
  259. return (
  260. <div className="p-6">
  261. <Card title="风险档案管理">
  262. {/* 风险概览 */}
  263. <Row gutter={16} className="mb-6">
  264. <Col span={6}>
  265. <Card size="small">
  266. <div className="text-center">
  267. <div className="text-2xl font-bold text-red-600">{highRiskCount}</div>
  268. <div className="text-gray-600">高风险</div>
  269. </div>
  270. </Card>
  271. </Col>
  272. <Col span={6}>
  273. <Card size="small">
  274. <div className="text-center">
  275. <div className="text-2xl font-bold text-orange-600">{mediumRiskCount}</div>
  276. <div className="text-gray-600">中风险</div>
  277. </div>
  278. </Card>
  279. </Col>
  280. <Col span={6}>
  281. <Card size="small">
  282. <div className="text-center">
  283. <div className="text-2xl font-bold text-blue-600">{lowRiskCount}</div>
  284. <div className="text-gray-600">低风险</div>
  285. </div>
  286. </Card>
  287. </Col>
  288. <Col span={6}>
  289. <Card size="small">
  290. <div className="text-center">
  291. <div className="text-2xl font-bold text-gray-600">{mockRiskData.length}</div>
  292. <div className="text-gray-600">风险总数</div>
  293. </div>
  294. </Card>
  295. </Col>
  296. </Row>
  297. {/* 筛选控件 */}
  298. <div className="mb-4">
  299. <Space>
  300. <Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}>
  301. 新增风险
  302. </Button>
  303. <Input.Search placeholder="搜索风险..." style={{ width: 300 }} />
  304. <Select placeholder="风险类型" style={{ width: 120 }}>
  305. <Option value="aging">管线老化</Option>
  306. <Option value="leak">泄漏隐患</Option>
  307. <Option value="pressure">压力异常</Option>
  308. <Option value="other">其他</Option>
  309. </Select>
  310. <Select placeholder="风险等级" style={{ width: 120 }}>
  311. <Option value="high">高</Option>
  312. <Option value="medium">中</Option>
  313. <Option value="low">低</Option>
  314. </Select>
  315. <Select placeholder="状态" style={{ width: 120 }}>
  316. <Option value="pending">待处理</Option>
  317. <Option value="monitoring">监控中</Option>
  318. <Option value="controlled">已控制</Option>
  319. </Select>
  320. </Space>
  321. </div>
  322. {/* 风险列表 */}
  323. <Table
  324. columns={columns}
  325. dataSource={mockRiskData}
  326. rowKey="id"
  327. pagination={{ pageSize: 10 }}
  328. scroll={{ x: 1400 }}
  329. />
  330. {/* 编辑/新增模态框 */}
  331. <Modal
  332. title={editingRecord ? "编辑风险" : "新增风险"}
  333. open={isModalVisible}
  334. onOk={handleModalOk}
  335. onCancel={() => setIsModalVisible(false)}
  336. width={800}
  337. >
  338. <Form form={form} layout="vertical">
  339. <Row gutter={16}>
  340. <Col span={12}>
  341. <Form.Item name="title" label="风险标题" rules={[{ required: true }]}>
  342. <Input />
  343. </Form.Item>
  344. </Col>
  345. <Col span={12}>
  346. <Form.Item name="location" label="位置" rules={[{ required: true }]}>
  347. <Input />
  348. </Form.Item>
  349. </Col>
  350. </Row>
  351. <Row gutter={16}>
  352. <Col span={12}>
  353. <Form.Item name="riskType" label="风险类型" rules={[{ required: true }]}>
  354. <Select>
  355. <Option value="管线老化">管线老化</Option>
  356. <Option value="泄漏隐患">泄漏隐患</Option>
  357. <Option value="压力异常">压力异常</Option>
  358. <Option value="其他">其他</Option>
  359. </Select>
  360. </Form.Item>
  361. </Col>
  362. <Col span={12}>
  363. <Form.Item name="riskLevel" label="风险等级" rules={[{ required: true }]}>
  364. <Select>
  365. <Option value="高">高</Option>
  366. <Option value="中">中</Option>
  367. <Option value="低">低</Option>
  368. </Select>
  369. </Form.Item>
  370. </Col>
  371. </Row>
  372. <Row gutter={16}>
  373. <Col span={12}>
  374. <Form.Item name="riskScore" label="风险评分" rules={[{ required: true }]}>
  375. <Input type="number" min={0} max={100} />
  376. </Form.Item>
  377. </Col>
  378. <Col span={12}>
  379. <Form.Item name="assessmentDate" label="评估日期" rules={[{ required: true }]}>
  380. <DatePicker style={{ width: "100%" }} />
  381. </Form.Item>
  382. </Col>
  383. </Row>
  384. <Form.Item name="description" label="风险描述" rules={[{ required: true }]}>
  385. <TextArea rows={3} />
  386. </Form.Item>
  387. <Row gutter={16}>
  388. <Col span={12}>
  389. <Form.Item name="affectedPopulation" label="影响人口" rules={[{ required: true }]}>
  390. <Input type="number" />
  391. </Form.Item>
  392. </Col>
  393. <Col span={12}>
  394. <Form.Item name="economicLoss" label="潜在经济损失" rules={[{ required: true }]}>
  395. <Input type="number" />
  396. </Form.Item>
  397. </Col>
  398. </Row>
  399. <Form.Item name="mitigationMeasures" label="缓解措施">
  400. <TextArea rows={3} />
  401. </Form.Item>
  402. </Form>
  403. </Modal>
  404. {/* 详情查看模态框 */}
  405. <Modal
  406. title="风险详情"
  407. open={isDetailModalVisible}
  408. onCancel={() => setIsDetailModalVisible(false)}
  409. footer={[
  410. <Button key="close" onClick={() => setIsDetailModalVisible(false)}>
  411. 关闭
  412. </Button>,
  413. ]}
  414. width={1000}
  415. >
  416. {viewingRecord && (
  417. <Row gutter={16}>
  418. <Col span={14}>
  419. <Descriptions column={2} bordered>
  420. <Descriptions.Item label="风险编号">{viewingRecord.id}</Descriptions.Item>
  421. <Descriptions.Item label="风险标题">{viewingRecord.title}</Descriptions.Item>
  422. <Descriptions.Item label="位置">{viewingRecord.location}</Descriptions.Item>
  423. <Descriptions.Item label="风险类型">{viewingRecord.riskType}</Descriptions.Item>
  424. <Descriptions.Item label="风险等级">
  425. <Tag color={getRiskLevelColor(viewingRecord.riskLevel)}>{viewingRecord.riskLevel}</Tag>
  426. </Descriptions.Item>
  427. <Descriptions.Item label="风险评分">
  428. <Progress
  429. percent={viewingRecord.riskScore}
  430. size="small"
  431. strokeColor={getRiskScoreColor(viewingRecord.riskScore)}
  432. />
  433. </Descriptions.Item>
  434. <Descriptions.Item label="评估日期">{viewingRecord.assessmentDate}</Descriptions.Item>
  435. <Descriptions.Item label="评估人">{viewingRecord.assessor}</Descriptions.Item>
  436. <Descriptions.Item label="影响范围">{viewingRecord.impactArea}</Descriptions.Item>
  437. <Descriptions.Item label="影响人口">{viewingRecord.affectedPopulation}人</Descriptions.Item>
  438. <Descriptions.Item label="潜在损失">¥{viewingRecord.economicLoss.toLocaleString()}</Descriptions.Item>
  439. <Descriptions.Item label="监测频率">{viewingRecord.monitoringFrequency}</Descriptions.Item>
  440. </Descriptions>
  441. <div className="mt-4">
  442. <h4 className="font-medium mb-2">风险描述</h4>
  443. <div className="p-3 bg-gray-50 rounded">{viewingRecord.description}</div>
  444. </div>
  445. <div className="mt-4">
  446. <h4 className="font-medium mb-2">缓解措施</h4>
  447. <div className="p-3 bg-blue-50 rounded">{viewingRecord.mitigationMeasures}</div>
  448. </div>
  449. </Col>
  450. <Col span={10}>
  451. <Card title="风险因子分析" size="small">
  452. {radarConfig && <Radar {...radarConfig} height={300} />}
  453. </Card>
  454. </Col>
  455. </Row>
  456. )}
  457. </Modal>
  458. </Card>
  459. </div>
  460. )
  461. }