VideoMonitoring.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. "use client"
  2. import {useState} from "react"
  3. import dynamic from "next/dynamic"
  4. import {Button, Card, Checkbox, Col, Input, Row, Select, Space, Tabs} from "antd"
  5. import {FullscreenOutlined, PlayCircleOutlined, SearchOutlined} from "@ant-design/icons"
  6. const MapView = dynamic(() => import("./MapView"), {
  7. ssr: false,
  8. loading: () => <div className="flex items-center justify-center h-full">地图加载中...</div>,
  9. })
  10. const { Option } = Select
  11. const { TabPane } = Tabs
  12. export default function VideoMonitoring() {
  13. const [selectedVideos, setSelectedVideos] = useState<string[]>([])
  14. const [viewMode, setViewMode] = useState<"1" | "4">("4")
  15. // 模拟视频数据
  16. const videoData = [
  17. {
  18. id: "V001",
  19. name: "人民路积水点监控",
  20. type: "积水点",
  21. location: "人民路与南京路交叉口",
  22. status: "online",
  23. associatedDevice: "LV002",
  24. },
  25. {
  26. id: "V002",
  27. name: "第一泵站监控",
  28. type: "泵站",
  29. location: "城东泵站",
  30. status: "online",
  31. associatedDevice: "PS001",
  32. },
  33. {
  34. id: "V003",
  35. name: "主干道管网监控",
  36. type: "管网",
  37. location: "淮海路主干管网",
  38. status: "online",
  39. associatedDevice: "FL001",
  40. },
  41. {
  42. id: "V004",
  43. name: "南京路积水点监控",
  44. type: "积水点",
  45. location: "南京路商业区",
  46. status: "offline",
  47. associatedDevice: "LV003",
  48. },
  49. ]
  50. const handleVideoSelect = (videoId: string, checked: boolean) => {
  51. if (checked) {
  52. setSelectedVideos([...selectedVideos, videoId])
  53. } else {
  54. setSelectedVideos(selectedVideos.filter((id) => id !== videoId))
  55. }
  56. }
  57. const renderVideoGrid = () => {
  58. const gridCols = viewMode === "1" ? 1 : viewMode === "4" ? 2 : 3
  59. const selectedVideoData = videoData.filter((video) => selectedVideos.includes(video.id))
  60. return (
  61. <div className={`grid grid-cols-${gridCols} gap-4 h-96`}>
  62. {selectedVideoData.slice(0, Number.parseInt(viewMode)).map((video) => (
  63. <Card
  64. key={video.id}
  65. title={video.name}
  66. size="small"
  67. className="h-full"
  68. extra={
  69. <Space>
  70. <Button size="small" icon={<PlayCircleOutlined />} />
  71. <Button size="small" icon={<FullscreenOutlined />} />
  72. </Space>
  73. }
  74. >
  75. <div className="bg-gray-800 h-full flex items-center justify-center text-white">
  76. {video.status === "online" ? (
  77. <div className="text-center">
  78. <PlayCircleOutlined className="text-4xl mb-2" />
  79. <div>视频监控画面</div>
  80. <div className="text-sm text-gray-300">{video.location}</div>
  81. </div>
  82. ) : (
  83. <div className="text-center text-gray-400">
  84. <div>设备离线</div>
  85. <div className="text-sm">{video.location}</div>
  86. </div>
  87. )}
  88. </div>
  89. </Card>
  90. ))}
  91. {/* 填充空白格子 */}
  92. {Array.from({ length: Number.parseInt(viewMode) - selectedVideoData.length }).map((_, index) => (
  93. <Card key={`empty-${index}`} className="h-full">
  94. <div className="bg-gray-100 h-full flex items-center justify-center text-gray-400">
  95. <div className="text-center">
  96. <div>请选择视频</div>
  97. </div>
  98. </div>
  99. </Card>
  100. ))}
  101. </div>
  102. )
  103. }
  104. return (
  105. <div className="space-y-6">
  106. <Tabs defaultActiveKey="batch-monitoring">
  107. <TabPane tab="视频批量监控" key="batch-monitoring">
  108. <Row gutter={[16, 16]}>
  109. <Col span={6}>
  110. <Card title="视频列表" className="h-full overflow-auto">
  111. <div className="mb-4">
  112. <Space direction="vertical" className="w-full">
  113. <Input placeholder="搜索视频" prefix={<SearchOutlined />} />
  114. <Select placeholder="视频类型" className="w-full">
  115. <Option value="waterlogging">积水点</Option>
  116. <Option value="pump">泵站</Option>
  117. <Option value="pipeline">管网</Option>
  118. </Select>
  119. </Space>
  120. </div>
  121. <div className="space-y-2">
  122. {videoData.map((video) => (
  123. <div key={video.id} className="flex items-center space-x-2 p-2 border rounded">
  124. <Checkbox
  125. checked={selectedVideos.includes(video.id)}
  126. onChange={(e) => handleVideoSelect(video.id, e.target.checked)}
  127. />
  128. <div className="flex-1">
  129. <div className="font-medium text-sm">{video.name}</div>
  130. <div className="text-xs text-gray-500">{video.location}</div>
  131. <div className="text-xs">
  132. <span
  133. className={`inline-block w-2 h-2 rounded-full mr-1 ${
  134. video.status === "online" ? "bg-green-500" : "bg-gray-400"
  135. }`}
  136. />
  137. {video.status === "online" ? "在线" : "离线"}
  138. </div>
  139. </div>
  140. </div>
  141. ))}
  142. </div>
  143. </Card>
  144. </Col>
  145. <Col span={18}>
  146. <Card
  147. title="视频监控"
  148. extra={
  149. <Space>
  150. <span>显示模式:</span>
  151. <Select value={viewMode} onChange={setViewMode} style={{ width: 80 }}>
  152. <Option value="1">1画面</Option>
  153. <Option value="4">4画面</Option>
  154. </Select>
  155. </Space>
  156. }
  157. >
  158. {renderVideoGrid()}
  159. </Card>
  160. </Col>
  161. </Row>
  162. </TabPane>
  163. <TabPane tab="视频地图" key="video-map">
  164. <Card title="监测视频 GIS " className="h-96">
  165. <MapView type="overview" />
  166. </Card>
  167. </TabPane>
  168. <TabPane tab="设备关联" key="device-association">
  169. <Card title="设备关联管理">
  170. <div className="mb-4">
  171. <Space>
  172. <Input placeholder="设备编号" prefix={<SearchOutlined />} />
  173. <Select placeholder="设备类型" style={{ width: 120 }}>
  174. <Option value="flow">流量计</Option>
  175. <Option value="level">液位计</Option>
  176. <Option value="pump">泵站</Option>
  177. </Select>
  178. <Button type="primary" icon={<SearchOutlined />}>
  179. 查询
  180. </Button>
  181. </Space>
  182. </div>
  183. <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  184. {videoData.map((video) => (
  185. <Card key={video.id} size="small">
  186. <div className="space-y-2">
  187. <div>
  188. <strong>视频:</strong> {video.name}
  189. </div>
  190. <div>
  191. <strong>关联设备:</strong> {video.associatedDevice}
  192. </div>
  193. <div>
  194. <strong>位置:</strong> {video.location}
  195. </div>
  196. <div>
  197. <strong>状态:</strong>
  198. <span className={`ml-1 ${video.status === "online" ? "text-green-600" : "text-gray-400"}`}>
  199. {video.status === "online" ? "在线" : "离线"}
  200. </span>
  201. </div>
  202. <div className="pt-2">
  203. <Button size="small" type="primary">
  204. 查看视频
  205. </Button>
  206. </div>
  207. </div>
  208. </Card>
  209. ))}
  210. </div>
  211. </Card>
  212. </TabPane>
  213. </Tabs>
  214. </div>
  215. )
  216. }