|
|
@@ -1,85 +1,2363 @@
|
|
|
<!DOCTYPE html>
|
|
|
-<html lang="zh-CN">
|
|
|
+<html lang="zh-CN" class="iframe-content">
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
- <title>设备监控实时展示 - 爱智农</title>
|
|
|
+ <title>设备监控汇总 - 爱智农</title>
|
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css">
|
|
|
<link rel="stylesheet" href="https://at.alicdn.com/t/font_3114978_qe0b39no76.css">
|
|
|
<link rel="stylesheet" href="../assets/css/global.css">
|
|
|
+ <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
|
|
|
+ <script src="https://cdn.jsdelivr.net/npm/dayjs@1.10.7/dayjs.min.js"></script>
|
|
|
+ <script src="https://cdn.jsdelivr.net/npm/dayjs@1.10.7/locale/zh-cn.js"></script>
|
|
|
<style>
|
|
|
:root {
|
|
|
- --primary: #4CAF50;
|
|
|
- --primary-dark: #388E3C;
|
|
|
- --primary-light: #A5D6A7;
|
|
|
- --primary-bg: #F1F8E9;
|
|
|
- --success: #4CAF50;
|
|
|
- --warning: #FFC107;
|
|
|
- --danger: #F44336;
|
|
|
- --info: #2196F3;
|
|
|
- --disabled: #9E9E9E;
|
|
|
- --border: #E0E0E0;
|
|
|
- --text-primary: #212121;
|
|
|
- --text-secondary: #757575;
|
|
|
+ --primary: #0ea5e9;
|
|
|
+ --primary-dark: #0369a1;
|
|
|
+ --primary-light: #7dd3fc;
|
|
|
+ --bg-dark: #0f172a;
|
|
|
+ --bg-card: #1e293b;
|
|
|
+ --text-primary: #f1f5f9;
|
|
|
+ --text-secondary: #94a3b8;
|
|
|
+ --success: #10b981;
|
|
|
+ --warning: #f59e0b;
|
|
|
+ --danger: #ef4444;
|
|
|
+ --info: #3b82f6;
|
|
|
+ --border: #334155;
|
|
|
--radius: 8px;
|
|
|
}
|
|
|
|
|
|
body {
|
|
|
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
|
- background-color: #f5f7f9;
|
|
|
+ background-color: var(--bg-dark);
|
|
|
color: var(--text-primary);
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
- height: 100vh;
|
|
|
- width: 100vw;
|
|
|
+ min-height: 100vh;
|
|
|
+ overflow-x: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-container {
|
|
|
+ padding: 60px 20px 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 卡片组件 */
|
|
|
+ .dashboard-card {
|
|
|
+ background-color: var(--bg-card);
|
|
|
+ border-radius: var(--radius);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dashboard-card:hover {
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
|
|
+ border-color: var(--primary);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 告警状态卡片 */
|
|
|
+ .dashboard-card.alert {
|
|
|
+ border: 1px solid var(--danger);
|
|
|
+ background: linear-gradient(to right, rgba(239, 68, 68, 0.05), transparent);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 数据更新闪烁效果 */
|
|
|
+ .data-update {
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-update.flash {
|
|
|
+ animation: flash 0.5s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 视频监控卡片 */
|
|
|
+ .video-card {
|
|
|
+ position: relative;
|
|
|
+ background: var(--bg-dark);
|
|
|
+ border-radius: var(--radius);
|
|
|
overflow: hidden;
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ transition: all 0.3s ease;
|
|
|
}
|
|
|
|
|
|
- /* 内容页面框架 */
|
|
|
- #content-frame {
|
|
|
+ .video-preview {
|
|
|
+ position: relative;
|
|
|
+ padding-top: 75%; /* 4:3 aspect ratio */
|
|
|
+ background: var(--bg-dark);
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-preview > div {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-preview .camera-icon {
|
|
|
+ width: 48px;
|
|
|
+ height: 48px;
|
|
|
+ color: var(--text-secondary);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 摄像头全屏按钮样式 */
|
|
|
+ .camera-fullscreen-btn {
|
|
|
+ position: absolute;
|
|
|
+ top: 12px;
|
|
|
+ right: 12px;
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: rgba(0, 0, 0, 0.5);
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
+ border-radius: 4px;
|
|
|
+ color: var(--text-secondary);
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ z-index: 10;
|
|
|
+ }
|
|
|
+
|
|
|
+ .camera-fullscreen-btn:hover {
|
|
|
+ background: rgba(0, 0, 0, 0.7);
|
|
|
+ color: var(--text-primary);
|
|
|
+ border-color: var(--primary);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 状态标签 */
|
|
|
+ .status-badge {
|
|
|
+ font-size: 12px;
|
|
|
+ padding: 2px 8px;
|
|
|
+ border-radius: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-badge.online {
|
|
|
+ background-color: rgba(16, 185, 129, 0.1);
|
|
|
+ color: #10b981;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-badge.offline {
|
|
|
+ background-color: rgba(239, 68, 68, 0.2);
|
|
|
+ color: var(--danger);
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-badge.warning {
|
|
|
+ background-color: rgba(245, 158, 11, 0.1);
|
|
|
+ color: #f59e0b;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 动画效果 */
|
|
|
+ @keyframes pulse {
|
|
|
+ 0% {
|
|
|
+ box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4);
|
|
|
+ }
|
|
|
+ 70% {
|
|
|
+ box-shadow: 0 0 0 10px rgba(239, 68, 68, 0);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes flash {
|
|
|
+ 0% {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ opacity: 0.5;
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 下拉选择器样式 */
|
|
|
+ select {
|
|
|
+ background-color: var(--bg-card);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ color: var(--text-primary);
|
|
|
+ padding: 8px 12px;
|
|
|
+ border-radius: var(--radius);
|
|
|
+ appearance: none;
|
|
|
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2394a3b8'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-position: right 8px center;
|
|
|
+ background-size: 16px;
|
|
|
+ padding-right: 32px;
|
|
|
+ }
|
|
|
+
|
|
|
+ select:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: var(--primary);
|
|
|
+ box-shadow: 0 0 0 2px rgba(14, 165, 233, 0.2);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 按钮样式 */
|
|
|
+ .btn {
|
|
|
+ padding: 8px 16px;
|
|
|
+ border-radius: var(--radius);
|
|
|
+ font-weight: 500;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-primary {
|
|
|
+ background-color: var(--primary);
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-primary:hover {
|
|
|
+ background-color: var(--primary-dark);
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-outline {
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ color: var(--text-primary);
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-outline:hover {
|
|
|
+ border-color: var(--primary);
|
|
|
+ background-color: rgba(15, 23, 42, 0.5);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 全屏按钮 */
|
|
|
+ .fullscreen-btn {
|
|
|
+ position: absolute;
|
|
|
+ top: 8px;
|
|
|
+ right: 8px;
|
|
|
+ background: rgba(0, 0, 0, 0.5);
|
|
|
+ color: white;
|
|
|
border: none;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .fullscreen-btn:hover {
|
|
|
+ background: rgba(0, 0, 0, 0.8);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 设备总览卡片样式优化 */
|
|
|
+ .overview-card {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
+ gap: 1.5rem;
|
|
|
+ padding: 1.5rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .overview-stat {
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 数据单位样式 */
|
|
|
+ .data-unit {
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: var(--text-secondary);
|
|
|
+ margin-left: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 刷新按钮动画 */
|
|
|
+ .refresh-spin {
|
|
|
+ animation: spin 1s linear infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes spin {
|
|
|
+ from {
|
|
|
+ transform: rotate(0deg);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ transform: rotate(360deg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 视频网格优化 */
|
|
|
+ .video-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
+ gap: 1rem;
|
|
|
+ margin-bottom: 1rem;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 视频监控相关样式 */
|
|
|
+ .video-container {
|
|
|
+ padding: 1.5rem;
|
|
|
+ background: var(--bg-card);
|
|
|
+ border-radius: var(--radius);
|
|
|
+ margin-bottom: 2rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-card {
|
|
|
+ display: block;
|
|
|
+ position: relative;
|
|
|
+ background: var(--bg-dark);
|
|
|
+ border-radius: var(--radius);
|
|
|
overflow: hidden;
|
|
|
- margin-top: 0; /* 移除顶部外边距 */
|
|
|
- padding-top: 0; /* 移除顶部内边距 */
|
|
|
- }
|
|
|
-
|
|
|
- /* 移除白色圆形按钮 */
|
|
|
- .circle-btn,
|
|
|
- .floating-button,
|
|
|
- .round-button,
|
|
|
- button[class*='circle'],
|
|
|
- div[class*='circle'],
|
|
|
- [class*='float-btn'],
|
|
|
- body > div:not(#app),
|
|
|
- body > button {
|
|
|
- display: none !important;
|
|
|
- visibility: hidden !important;
|
|
|
- opacity: 0 !important;
|
|
|
- position: absolute !important;
|
|
|
- top: -9999px !important;
|
|
|
- left: -9999px !important;
|
|
|
- z-index: -9999 !important;
|
|
|
- pointer-events: none !important;
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-card:hover {
|
|
|
+ border-color: var(--primary);
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-preview {
|
|
|
+ position: relative;
|
|
|
+ padding-top: 75%; /* 4:3 aspect ratio */
|
|
|
+ background: var(--bg-dark);
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-preview > div {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-info {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ padding: 0.75rem;
|
|
|
+ background: linear-gradient(to top, rgba(0,0,0,0.8), transparent);
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-title {
|
|
|
+ color: white;
|
|
|
+ font-size: 0.875rem;
|
|
|
+ margin-bottom: 0.25rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-location {
|
|
|
+ color: var(--text-secondary);
|
|
|
+ font-size: 0.75rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 主视频样式 */
|
|
|
+ .video-card.main-video {
|
|
|
+ grid-column: span 1;
|
|
|
+ border-width: 2px;
|
|
|
+ border-color: var(--primary);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 16:9 宽高比容器 */
|
|
|
+ .aspect-w-16.aspect-h-9 {
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 视频导航按钮样式 */
|
|
|
+ .video-nav-btn {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: rgba(15, 23, 42, 0.8);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ border-radius: 4px;
|
|
|
+ color: var(--text-secondary);
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-nav-btn:hover {
|
|
|
+ background: var(--bg-card);
|
|
|
+ color: var(--text-primary);
|
|
|
+ border-color: var(--primary);
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-nav-btn:disabled {
|
|
|
+ opacity: 0.5;
|
|
|
+ cursor: not-allowed;
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-nav-btn i {
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 页码显示样式 */
|
|
|
+ .page-indicator {
|
|
|
+ padding: 0 12px;
|
|
|
+ height: 32px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ background: rgba(15, 23, 42, 0.8);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ border-radius: 4px;
|
|
|
+ color: var(--text-secondary);
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 摄像头控制按钮 */
|
|
|
+ .camera-control-btn {
|
|
|
+ background: rgba(0, 0, 0, 0.5);
|
|
|
+ color: var(--text-secondary);
|
|
|
+ padding: 0.5rem;
|
|
|
+ border-radius: var(--radius);
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .camera-control-btn:hover {
|
|
|
+ background: rgba(0, 0, 0, 0.7);
|
|
|
+ color: var(--text-primary);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 云台控制按钮 */
|
|
|
+ .ptz-btn {
|
|
|
+ background: var(--bg-card);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ color: var(--text-primary);
|
|
|
+ padding: 0.75rem;
|
|
|
+ border-radius: var(--radius);
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ptz-btn:hover {
|
|
|
+ background: var(--primary);
|
|
|
+ border-color: var(--primary);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 预置位按钮 */
|
|
|
+ .preset-btn {
|
|
|
+ background: var(--bg-card);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ color: var(--text-primary);
|
|
|
+ padding: 0.5rem;
|
|
|
+ border-radius: var(--radius);
|
|
|
+ font-size: 0.875rem;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .preset-btn:hover {
|
|
|
+ background: var(--primary);
|
|
|
+ border-color: var(--primary);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 全屏模式样式 */
|
|
|
+ .fullscreen-mode {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100vw;
|
|
|
+ height: 100vh;
|
|
|
+ background: var(--bg-dark);
|
|
|
+ padding: 1rem;
|
|
|
+ z-index: 40;
|
|
|
+ }
|
|
|
+
|
|
|
+ .fullscreen-mode .video-grid {
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
+ grid-template-rows: repeat(2, 1fr);
|
|
|
+ height: calc(100vh - 2rem);
|
|
|
+ gap: 1rem;
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 单个摄像头全屏模式 */
|
|
|
+ #singleCameraFullscreen {
|
|
|
+ display: none;
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100vw;
|
|
|
+ height: 100vh;
|
|
|
+ background-color: var(--bg-dark);
|
|
|
+ z-index: 50;
|
|
|
+ }
|
|
|
+
|
|
|
+ #singleCameraFullscreen.active {
|
|
|
+ display: flex;
|
|
|
+ }
|
|
|
+
|
|
|
+ #singleCameraFullscreen .video-area {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ #singleCameraFullscreen .control-panel {
|
|
|
+ width: 280px;
|
|
|
+ background: var(--bg-card);
|
|
|
+ border-left: 1px solid var(--border);
|
|
|
+ padding: 1.5rem;
|
|
|
+ overflow-y: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 云台控制按钮网格 */
|
|
|
+ .ptz-controls {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
+ gap: 0.5rem;
|
|
|
+ margin-bottom: 1.5rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ptz-btn {
|
|
|
+ aspect-ratio: 1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: var(--bg-dark);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ color: var(--text-secondary);
|
|
|
+ border-radius: var(--radius);
|
|
|
+ font-size: 1.25rem;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ptz-btn:hover {
|
|
|
+ background: var(--primary);
|
|
|
+ border-color: var(--primary);
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ptz-btn:active {
|
|
|
+ transform: scale(0.95);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 预置位按钮网格 */
|
|
|
+ .preset-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ gap: 0.5rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .preset-btn {
|
|
|
+ padding: 0.75rem;
|
|
|
+ background: var(--bg-dark);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ color: var(--text-secondary);
|
|
|
+ border-radius: var(--radius);
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .preset-btn:hover {
|
|
|
+ background: var(--primary);
|
|
|
+ border-color: var(--primary);
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 更新数据显示样式 */
|
|
|
+ .data-update {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-update .text-3xl {
|
|
|
+ display: flex;
|
|
|
+ align-items: baseline;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 设备监控区域样式 */
|
|
|
+ .device-monitor-section {
|
|
|
+ margin-bottom: 0; /* 移除底部边距,因为现在在卡片内部 */
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 设备卡片网格布局 */
|
|
|
+ .device-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(1, 1fr);
|
|
|
+ gap: 1rem; /* 稍微减小间距,因为现在在卡片内部 */
|
|
|
+ padding: 0.5rem; /* 添加内边距,避免卡片贴边 */
|
|
|
+ min-height: 400px;
|
|
|
+ }
|
|
|
+
|
|
|
+ @media (min-width: 768px) {
|
|
|
+ .device-grid {
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @media (min-width: 1280px) {
|
|
|
+ .device-grid {
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @media (min-width: 1536px) {
|
|
|
+ .device-grid {
|
|
|
+ grid-template-columns: repeat(4, 1fr);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 设备卡片基础样式 */
|
|
|
+ .device-card {
|
|
|
+ background-color: var(--bg-card);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ border-radius: var(--radius);
|
|
|
+ padding: 1.5rem;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ height: 100%;
|
|
|
+ min-height: 250px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ .device-card:hover {
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 告警状态卡片 */
|
|
|
+ .device-card.alert {
|
|
|
+ border-color: var(--danger);
|
|
|
+ background: linear-gradient(to right, rgba(239, 68, 68, 0.05), transparent);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 离线状态卡片 */
|
|
|
+ .device-card.offline {
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 骨架屏动画 */
|
|
|
+ @keyframes pulse {
|
|
|
+ 0%, 100% {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ opacity: .5;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .animate-pulse {
|
|
|
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 筛选按钮样式 */
|
|
|
+ .filter-btn {
|
|
|
+ padding: 0.5rem 1rem;
|
|
|
+ border-radius: var(--radius);
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: var(--text-secondary);
|
|
|
+ background: transparent;
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 0.5rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-btn:hover {
|
|
|
+ background: rgba(255, 255, 255, 0.05);
|
|
|
+ border-color: var(--primary);
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-btn.active {
|
|
|
+ background: var(--primary);
|
|
|
+ border-color: var(--primary);
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 状态点样式 */
|
|
|
+ .status-dot {
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: inline-block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-dot.warning {
|
|
|
+ background-color: var(--warning);
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-dot.offline {
|
|
|
+ background-color: var(--danger);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 排序选择器样式 */
|
|
|
+ .sort-select {
|
|
|
+ background-color: var(--bg-card);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ color: var(--text-primary);
|
|
|
+ padding: 0.5rem 2rem 0.5rem 1rem;
|
|
|
+ border-radius: var(--radius);
|
|
|
+ font-size: 0.875rem;
|
|
|
+ appearance: none;
|
|
|
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2394a3b8'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-position: right 0.75rem center;
|
|
|
+ background-size: 1rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-select:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: var(--primary);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 分页按钮样式 */
|
|
|
+ .page-btn {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ border-radius: var(--radius);
|
|
|
+ color: var(--text-secondary);
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ background: var(--bg-card);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-btn:hover:not(:disabled) {
|
|
|
+ background: var(--primary);
|
|
|
+ border-color: var(--primary);
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-btn:disabled {
|
|
|
+ opacity: 0.5;
|
|
|
+ cursor: not-allowed;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 空状态样式 */
|
|
|
+ .device-card.empty {
|
|
|
+ justify-content: center;
|
|
|
+ background: transparent;
|
|
|
+ border: 2px dashed var(--border);
|
|
|
+ }
|
|
|
+
|
|
|
+ .device-card.empty:hover {
|
|
|
+ transform: none;
|
|
|
+ box-shadow: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 添加到现有的 style 标签中 */
|
|
|
+ .time-range-btn,
|
|
|
+ .indicator-btn {
|
|
|
+ padding: 0.5rem 1rem;
|
|
|
+ border-radius: var(--radius);
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: var(--text-secondary);
|
|
|
+ background: transparent;
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .time-range-btn:hover,
|
|
|
+ .indicator-btn:hover {
|
|
|
+ background: rgba(255, 255, 255, 0.05);
|
|
|
+ border-color: var(--primary);
|
|
|
+ }
|
|
|
+
|
|
|
+ .time-range-btn.active,
|
|
|
+ .indicator-btn.active {
|
|
|
+ background: var(--primary);
|
|
|
+ border-color: var(--primary);
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 添加到现有的 style 标签中 */
|
|
|
+ .history-data {
|
|
|
+ display: none;
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: var(--bg-card);
|
|
|
+ z-index: 1;
|
|
|
+ padding: 1rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .history-data.active {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .history-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 1rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .history-tabs {
|
|
|
+ display: flex;
|
|
|
+ gap: 0.5rem;
|
|
|
+ margin-bottom: 1rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .history-tab {
|
|
|
+ padding: 0.5rem 1rem;
|
|
|
+ border-radius: var(--radius);
|
|
|
+ font-size: 0.875rem;
|
|
|
+ color: var(--text-secondary);
|
|
|
+ background: transparent;
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .history-tab.active {
|
|
|
+ background: var(--primary);
|
|
|
+ border-color: var(--primary);
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .close-history {
|
|
|
+ padding: 0.5rem;
|
|
|
+ color: var(--text-secondary);
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .close-history:hover {
|
|
|
+ color: var(--text-primary);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 修改设备卡片样式 */
|
|
|
+ .device-card {
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 添加或修改以下样式 */
|
|
|
+ .modal-backdrop {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100vw;
|
|
|
+ height: 100vh;
|
|
|
+ background-color: rgba(15, 23, 42, 0.75);
|
|
|
+ backdrop-filter: blur(4px);
|
|
|
+ z-index: 50;
|
|
|
+ display: none; /* 默认隐藏 */
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 1rem;
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-backdrop.active {
|
|
|
+ display: flex;
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-content {
|
|
|
+ background-color: var(--bg-dark);
|
|
|
+ border-radius: var(--radius);
|
|
|
+ width: 100%;
|
|
|
+ max-width: 1000px;
|
|
|
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 1rem 1.5rem;
|
|
|
+ border-bottom: 1px solid var(--border);
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-body {
|
|
|
+ padding: 1.5rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-close {
|
|
|
+ background: transparent;
|
|
|
+ border: none;
|
|
|
+ color: var(--text-secondary);
|
|
|
+ cursor: pointer;
|
|
|
+ padding: 0.5rem;
|
|
|
+ transition: color 0.2s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-close:hover {
|
|
|
+ color: var(--text-primary);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 修改历史数据图表模态框的HTML结构 */
|
|
|
+ .device-card {
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ .history-btn {
|
|
|
+ position: absolute;
|
|
|
+ top: 1rem;
|
|
|
+ right: 1rem;
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: var(--bg-dark);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ border-radius: var(--radius);
|
|
|
+ color: var(--text-secondary);
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ z-index: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .history-btn:hover {
|
|
|
+ background: var(--primary);
|
|
|
+ border-color: var(--primary);
|
|
|
+ color: white;
|
|
|
+ transform: translateY(-1px);
|
|
|
+ }
|
|
|
+
|
|
|
+ .history-btn svg {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 调整设备卡片头部布局 */
|
|
|
+ .device-card .card-header {
|
|
|
+ padding-right: 3rem; /* 为历史按钮留出空间 */
|
|
|
}
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
- <!-- 使用iframe来完全隔离和控制页面内容 -->
|
|
|
- <iframe id="content-frame" src="device-monitor-content.html" sandbox="allow-scripts allow-forms allow-same-origin allow-popups" loading="eager" scrolling="auto" frameborder="0" allowfullscreen></iframe>
|
|
|
+ <div class="page-container">
|
|
|
+ <!-- 顶部控制区域 -->
|
|
|
+ <div class="mb-6 grid grid-cols-12 gap-4">
|
|
|
+ <!-- 农场和地块选择器 -->
|
|
|
+ <div class="col-span-12 lg:col-span-8">
|
|
|
+ <div class="dashboard-card p-4">
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
|
+ <div>
|
|
|
+ <label class="block text-sm text-gray-400 mb-2">农场选择</label>
|
|
|
+ <select id="farmSelector" class="w-full">
|
|
|
+ <option value="all">全部农场</option>
|
|
|
+ <option value="farm1">东湖智慧农场</option>
|
|
|
+ <option value="farm2">西湖有机农场</option>
|
|
|
+ <option value="farm3">南山生态农场</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <label class="block text-sm text-gray-400 mb-2">地块选择</label>
|
|
|
+ <select id="areaSelector" class="w-full">
|
|
|
+ <option value="all">全部地块</option>
|
|
|
+ <option value="area1">东区1号地块</option>
|
|
|
+ <option value="area2">东区2号地块</option>
|
|
|
+ <option value="area3">西区1号地块</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <label class="block text-sm text-gray-400 mb-2">时间范围</label>
|
|
|
+ <select id="timeRange" class="w-full">
|
|
|
+ <option value="realtime">实时数据</option>
|
|
|
+ <option value="1h">近1小时</option>
|
|
|
+ <option value="24h">近24小时</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 刷新控制区 -->
|
|
|
+ <div class="col-span-12 lg:col-span-4">
|
|
|
+ <div class="dashboard-card p-4">
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <div class="flex items-center gap-4">
|
|
|
+ <button id="refreshBtn" class="btn btn-primary">
|
|
|
+ <i class="iconfont icon-refresh"></i>
|
|
|
+ 刷新数据
|
|
|
+ </button>
|
|
|
+ <div class="text-sm text-gray-400">
|
|
|
+ 自动刷新:
|
|
|
+ <select id="autoRefresh" class="text-sm">
|
|
|
+ <option value="15">15秒</option>
|
|
|
+ <option value="30" selected>30秒</option>
|
|
|
+ <option value="60">60秒</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-400">
|
|
|
+ 最后更新:<span id="lastUpdate">刚刚</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 设备总览统计区 -->
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-7 gap-4 mb-6">
|
|
|
+ <!-- 总体统计 -->
|
|
|
+ <div class="dashboard-card xl:col-span-2">
|
|
|
+ <div class="overview-card">
|
|
|
+ <div class="overview-stat">
|
|
|
+ <div class="text-2xl font-bold text-green-500">42</div>
|
|
|
+ <div class="text-sm text-gray-400">在线设备</div>
|
|
|
+ </div>
|
|
|
+ <div class="overview-stat">
|
|
|
+ <div class="text-2xl font-bold text-red-500">3</div>
|
|
|
+ <div class="text-sm text-gray-400">离线设备</div>
|
|
|
+ </div>
|
|
|
+ <div class="overview-stat">
|
|
|
+ <div class="text-2xl font-bold text-yellow-500">5</div>
|
|
|
+ <div class="text-sm text-gray-400">告警设备</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 分类统计 -->
|
|
|
+ <div class="dashboard-card p-4">
|
|
|
+ <div class="text-center">
|
|
|
+ <div class="text-3xl font-bold text-blue-500 mb-2">12</div>
|
|
|
+ <div class="text-sm text-gray-400">摄像头</div>
|
|
|
+ <div class="text-xs text-gray-500 mt-1">11在线 / 1离线</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="dashboard-card p-4">
|
|
|
+ <div class="text-center">
|
|
|
+ <div class="text-3xl font-bold text-green-500 mb-2">15</div>
|
|
|
+ <div class="text-sm text-gray-400">传感器</div>
|
|
|
+ <div class="text-xs text-gray-500 mt-1">14在线 / 1告警</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="dashboard-card p-4">
|
|
|
+ <div class="text-center">
|
|
|
+ <div class="text-3xl font-bold text-purple-500 mb-2">8</div>
|
|
|
+ <div class="text-sm text-gray-400">气象设备</div>
|
|
|
+ <div class="text-xs text-gray-500 mt-1">7在线 / 1离线</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="dashboard-card p-4">
|
|
|
+ <div class="text-center">
|
|
|
+ <div class="text-3xl font-bold text-yellow-500 mb-2">10</div>
|
|
|
+ <div class="text-sm text-gray-400">控制设备</div>
|
|
|
+ <div class="text-xs text-gray-500 mt-1">10在线</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 今日统计 -->
|
|
|
+ <div class="dashboard-card p-4">
|
|
|
+ <div class="text-center">
|
|
|
+ <div class="text-3xl font-bold text-red-500 mb-2">7</div>
|
|
|
+ <div class="text-sm text-gray-400">今日告警</div>
|
|
|
+ <div class="text-xs text-green-500 mt-1">5已处理</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 主要内容区域 -->
|
|
|
+ <div class="flex flex-col gap-6">
|
|
|
+ <!-- 视频监控区域 -->
|
|
|
+ <div class="w-full">
|
|
|
+ <div class="dashboard-card p-4">
|
|
|
+ <div class="flex justify-between items-center mb-4">
|
|
|
+ <div class="flex items-center gap-4">
|
|
|
+ <h3 class="text-lg font-medium">视频监控</h3>
|
|
|
+ <div class="text-sm text-gray-400">
|
|
|
+ 共 12 个摄像头
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <button class="video-nav-btn" id="prevPage" title="上一页">
|
|
|
+ <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
+ <path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="page-indicator">
|
|
|
+ <span>1-6 / 12</span>
|
|
|
+ </div>
|
|
|
+ <button class="video-nav-btn" id="nextPage" title="下一页">
|
|
|
+ <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
+ <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <button class="video-nav-btn" id="gridFullscreen" title="全屏显示">
|
|
|
+ <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
+ <path fill-rule="evenodd" d="M3 4a1 1 0 011-1h4a1 1 0 010 2H6.414l2.293 2.293a1 1 0 11-1.414 1.414L5 6.414V8a1 1 0 01-2 0V4zm9 1a1 1 0 010-2h4a1 1 0 011 1v4a1 1 0 01-2 0V6.414l-2.293 2.293a1 1 0 11-1.414-1.414L13.586 5H12zm-9 7a1 1 0 012 0v1.586l2.293-2.293a1 1 0 111.414 1.414L6.414 15H8a1 1 0 010 2H4a1 1 0 01-1-1v-4zm13-1a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 010-2h1.586l-2.293-2.293a1 1 0 111.414-1.414L15 13.586V12a1 1 0 011-1z" clip-rule="evenodd" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 视频卡片网格 -->
|
|
|
+ <div id="videoGrid" class="video-container">
|
|
|
+ <div class="video-grid">
|
|
|
+ <!-- 摄像头 1-3 -->
|
|
|
+ <div class="video-card">
|
|
|
+ <button class="camera-fullscreen-btn" title="全屏查看">
|
|
|
+ <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
+ <path fill-rule="evenodd" d="M3 4a1 1 0 011-1h4a1 1 0 010 2H6.414l2.293 2.293a1 1 0 11-1.414 1.414L5 6.414V8a1 1 0 01-2 0V4zm9 1a1 1 0 010-2h4a1 1 0 011 1v4a1 1 0 01-2 0V6.414l-2.293 2.293a1 1 0 11-1.414-1.414L13.586 5H12zm-9 7a1 1 0 012 0v1.586l2.293-2.293a1 1 0 111.414 1.414L6.414 15H8a1 1 0 010 2H4a1 1 0 01-1-1v-4zm13-1a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 010-2h1.586l-2.293-2.293a1 1 0 111.414-1.414L15 13.586V12a1 1 0 011-1z" clip-rule="evenodd" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="video-preview">
|
|
|
+ <div class="bg-slate-800">
|
|
|
+ <svg class="camera-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M23 7l-7 5 7 5V7z"/>
|
|
|
+ <rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="video-info">
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div>
|
|
|
+ <div class="text-white">东区1号摄像头</div>
|
|
|
+ <div class="text-sm text-gray-400">东区1号地块</div>
|
|
|
+ </div>
|
|
|
+ <span class="status-badge online">在线</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 摄像头 4 -->
|
|
|
+ <div class="video-card">
|
|
|
+ <button class="camera-fullscreen-btn" title="全屏查看">
|
|
|
+ <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
+ <path fill-rule="evenodd" d="M3 4a1 1 0 011-1h4a1 1 0 010 2H6.414l2.293 2.293a1 1 0 11-1.414 1.414L5 6.414V8a1 1 0 01-2 0V4zm9 1a1 1 0 010-2h4a1 1 0 011 1v4a1 1 0 01-2 0V6.414l-2.293 2.293a1 1 0 11-1.414-1.414L13.586 5H12zm-9 7a1 1 0 012 0v1.586l2.293-2.293a1 1 0 111.414 1.414L6.414 15H8a1 1 0 010 2H4a1 1 0 01-1-1v-4zm13-1a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 010-2h1.586l-2.293-2.293a1 1 0 111.414-1.414L15 13.586V12a1 1 0 011-1z" clip-rule="evenodd" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="video-preview">
|
|
|
+ <div class="bg-slate-800">
|
|
|
+ <svg class="camera-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M23 7l-7 5 7 5V7z"/>
|
|
|
+ <rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="video-info">
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div>
|
|
|
+ <div class="text-white">东区2号摄像头</div>
|
|
|
+ <div class="text-sm text-gray-400">东区2号地块</div>
|
|
|
+ </div>
|
|
|
+ <span class="status-badge online">在线</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 摄像头 5 -->
|
|
|
+ <div class="video-card">
|
|
|
+ <button class="camera-fullscreen-btn" title="全屏查看">
|
|
|
+ <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
+ <path fill-rule="evenodd" d="M3 4a1 1 0 011-1h4a1 1 0 010 2H6.414l2.293 2.293a1 1 0 11-1.414 1.414L5 6.414V8a1 1 0 01-2 0V4zm9 1a1 1 0 010-2h4a1 1 0 011 1v4a1 1 0 01-2 0V6.414l-2.293 2.293a1 1 0 11-1.414-1.414L13.586 5H12zm-9 7a1 1 0 012 0v1.586l2.293-2.293a1 1 0 111.414 1.414L6.414 15H8a1 1 0 010 2H4a1 1 0 01-1-1v-4zm13-1a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 010-2h1.586l-2.293-2.293a1 1 0 111.414-1.414L15 13.586V12a1 1 0 011-1z" clip-rule="evenodd" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="video-preview">
|
|
|
+ <div class="bg-slate-800">
|
|
|
+ <svg class="camera-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M23 7l-7 5 7 5V7z"/>
|
|
|
+ <rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="video-info">
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div>
|
|
|
+ <div class="text-white">东区3号摄像头</div>
|
|
|
+ <div class="text-sm text-gray-400">东区3号地块</div>
|
|
|
+ </div>
|
|
|
+ <span class="status-badge offline">离线</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 摄像头 6 -->
|
|
|
+ <div class="video-card">
|
|
|
+ <button class="camera-fullscreen-btn" title="全屏查看">
|
|
|
+ <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
+ <path fill-rule="evenodd" d="M3 4a1 1 0 011-1h4a1 1 0 010 2H6.414l2.293 2.293a1 1 0 11-1.414 1.414L5 6.414V8a1 1 0 01-2 0V4zm9 1a1 1 0 010-2h4a1 1 0 011 1v4a1 1 0 01-2 0V6.414l-2.293 2.293a1 1 0 11-1.414-1.414L13.586 5H12zm-9 7a1 1 0 012 0v1.586l2.293-2.293a1 1 0 111.414 1.414L6.414 15H8a1 1 0 010 2H4a1 1 0 01-1-1v-4zm13-1a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 010-2h1.586l-2.293-2.293a1 1 0 111.414-1.414L15 13.586V12a1 1 0 011-1z" clip-rule="evenodd" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="video-preview">
|
|
|
+ <div class="bg-slate-800">
|
|
|
+ <svg class="camera-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M23 7l-7 5 7 5V7z"/>
|
|
|
+ <rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="video-info">
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div>
|
|
|
+ <div class="text-white">西区1号摄像头</div>
|
|
|
+ <div class="text-sm text-gray-400">西区1号地块</div>
|
|
|
+ </div>
|
|
|
+ <span class="status-badge online">在线</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 摄像头 7 -->
|
|
|
+ <div class="video-card">
|
|
|
+ <button class="camera-fullscreen-btn" title="全屏查看">
|
|
|
+ <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
+ <path fill-rule="evenodd" d="M3 4a1 1 0 011-1h4a1 1 0 010 2H6.414l2.293 2.293a1 1 0 11-1.414 1.414L5 6.414V8a1 1 0 01-2 0V4zm9 1a1 1 0 010-2h4a1 1 0 011 1v4a1 1 0 01-2 0V6.414l-2.293 2.293a1 1 0 11-1.414-1.414L13.586 5H12zm-9 7a1 1 0 012 0v1.586l2.293-2.293a1 1 0 111.414 1.414L6.414 15H8a1 1 0 010 2H4a1 1 0 01-1-1v-4zm13-1a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 010-2h1.586l-2.293-2.293a1 1 0 111.414-1.414L15 13.586V12a1 1 0 011-1z" clip-rule="evenodd" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="video-preview">
|
|
|
+ <div class="bg-slate-800">
|
|
|
+ <svg class="camera-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M23 7l-7 5 7 5V7z"/>
|
|
|
+ <rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="video-info">
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div>
|
|
|
+ <div class="text-white">西区2号摄像头</div>
|
|
|
+ <div class="text-sm text-gray-400">西区2号地块</div>
|
|
|
+ </div>
|
|
|
+ <span class="status-badge online">在线</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 摄像头 8 -->
|
|
|
+ <div class="video-card">
|
|
|
+ <button class="camera-fullscreen-btn" title="全屏查看">
|
|
|
+ <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
+ <path fill-rule="evenodd" d="M3 4a1 1 0 011-1h4a1 1 0 010 2H6.414l2.293 2.293a1 1 0 11-1.414 1.414L5 6.414V8a1 1 0 01-2 0V4zm9 1a1 1 0 010-2h4a1 1 0 011 1v4a1 1 0 01-2 0V6.414l-2.293 2.293a1 1 0 11-1.414-1.414L13.586 5H12zm-9 7a1 1 0 012 0v1.586l2.293-2.293a1 1 0 111.414 1.414L6.414 15H8a1 1 0 010 2H4a1 1 0 01-1-1v-4zm13-1a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 010-2h1.586l-2.293-2.293a1 1 0 111.414-1.414L15 13.586V12a1 1 0 011-1z" clip-rule="evenodd" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="video-preview">
|
|
|
+ <div class="bg-slate-800">
|
|
|
+ <svg class="camera-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M23 7l-7 5 7 5V7z"/>
|
|
|
+ <rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="video-info">
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div>
|
|
|
+ <div class="text-white">西区3号摄像头</div>
|
|
|
+ <div class="text-sm text-gray-400">西区3号地块</div>
|
|
|
+ </div>
|
|
|
+ <span class="status-badge online">在线</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 设备数据展示区域 -->
|
|
|
+ <div class="w-full">
|
|
|
+ <div class="dashboard-card p-4">
|
|
|
+ <div class="device-monitor-section">
|
|
|
+ <!-- 顶部控制栏 -->
|
|
|
+ <div class="flex justify-between items-center mb-6">
|
|
|
+ <div class="flex items-center gap-4">
|
|
|
+ <h3 class="text-lg font-medium">设备监控</h3>
|
|
|
+ <div class="flex gap-2">
|
|
|
+ <button class="filter-btn active">
|
|
|
+ 全部设备
|
|
|
+ <span class="ml-1 text-xs text-gray-400">(45)</span>
|
|
|
+ </button>
|
|
|
+ <button class="filter-btn">
|
|
|
+ <span class="status-dot warning"></span>
|
|
|
+ 告警设备
|
|
|
+ <span class="ml-1 text-xs text-gray-400">(5)</span>
|
|
|
+ </button>
|
|
|
+ <button class="filter-btn">
|
|
|
+ <span class="status-dot offline"></span>
|
|
|
+ 离线设备
|
|
|
+ <span class="ml-1 text-xs text-gray-400">(3)</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="border-l border-slate-600 h-6"></div>
|
|
|
+ <div class="flex gap-2">
|
|
|
+ <button class="filter-btn">土壤监测</button>
|
|
|
+ <button class="filter-btn">水质监测</button>
|
|
|
+ <button class="filter-btn">气象监测</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-4">
|
|
|
+ <select class="sort-select">
|
|
|
+ <option value="alert">告警优先</option>
|
|
|
+ <option value="time">更新时间</option>
|
|
|
+ <option value="name">设备名称</option>
|
|
|
+ </select>
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <button class="page-btn" disabled>
|
|
|
+ <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
+ <path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <span class="text-sm text-gray-400">1-6 / 45</span>
|
|
|
+ <button class="page-btn">
|
|
|
+ <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
+ <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 设备卡片网格 -->
|
|
|
+ <div class="device-grid">
|
|
|
+ <!-- 第一行设备 -->
|
|
|
+ <!-- 土壤监测器卡片 -->
|
|
|
+ <div class="device-card">
|
|
|
+ <button class="history-btn" onclick="showDeviceHistory(this.closest('.device-card'))" title="查看历史数据">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M12 20v-6M6 20V10M18 20V4"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="card-header">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <h4 class="text-lg font-medium">土壤监测器 #1</h4>
|
|
|
+ <span class="status-badge online">在线</span>
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-400 mt-1">东区1号地块</div>
|
|
|
+ </div>
|
|
|
+ <div class="grid grid-cols-2 gap-8 mb-6">
|
|
|
+ <div class="data-update">
|
|
|
+ <div class="text-sm text-gray-400 mb-1">土壤湿度</div>
|
|
|
+ <div class="text-3xl font-medium">32.5<span class="text-xl ml-1">%</span></div>
|
|
|
+ </div>
|
|
|
+ <div class="data-update">
|
|
|
+ <div class="text-sm text-gray-400 mb-1">土壤温度</div>
|
|
|
+ <div class="text-3xl font-medium">24.2<span class="text-xl ml-1">℃</span></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div class="text-sm text-gray-400">2分钟前更新</div>
|
|
|
+ <button class="btn btn-outline text-sm px-4 py-2">查看详情</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 水质监测器卡片(告警状态) -->
|
|
|
+ <div class="device-card alert">
|
|
|
+ <button class="history-btn" onclick="showDeviceHistory(this.closest('.device-card'))" title="查看历史数据">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M12 20v-6M6 20V10M18 20V4"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="card-header">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <h4 class="text-lg font-medium">水质监测器 #2</h4>
|
|
|
+ <span class="status-badge warning">告警</span>
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-400 mt-1">西区1号地块</div>
|
|
|
+ </div>
|
|
|
+ <div class="grid grid-cols-2 gap-8 mb-6">
|
|
|
+ <div class="data-update">
|
|
|
+ <div class="text-sm text-gray-400 mb-1">pH值</div>
|
|
|
+ <div class="text-3xl font-medium text-red-500">8.5</div>
|
|
|
+ </div>
|
|
|
+ <div class="data-update">
|
|
|
+ <div class="text-sm text-gray-400 mb-1">溶解氧</div>
|
|
|
+ <div class="text-3xl font-medium">6.2<span class="text-xl ml-1">mg/L</span></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div class="text-sm text-red-500">pH值超标</div>
|
|
|
+ <button class="btn btn-outline text-sm px-4 py-2">处理告警</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 气象监测器卡片 -->
|
|
|
+ <div class="device-card">
|
|
|
+ <button class="history-btn" onclick="showDeviceHistory(this.closest('.device-card'))" title="查看历史数据">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M12 20v-6M6 20V10M18 20V4"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="card-header">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <h4 class="text-lg font-medium">气象监测器 #1</h4>
|
|
|
+ <span class="status-badge online">在线</span>
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-400 mt-1">东区气象站</div>
|
|
|
+ </div>
|
|
|
+ <div class="grid grid-cols-2 gap-8 mb-6">
|
|
|
+ <div class="data-update">
|
|
|
+ <div class="text-sm text-gray-400 mb-1">温度</div>
|
|
|
+ <div class="text-3xl font-medium">26.5<span class="text-xl ml-1">℃</span></div>
|
|
|
+ </div>
|
|
|
+ <div class="data-update">
|
|
|
+ <div class="text-sm text-gray-400 mb-1">湿度</div>
|
|
|
+ <div class="text-3xl font-medium">68<span class="text-xl ml-1">%</span></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div class="text-sm text-gray-400">1分钟前更新</div>
|
|
|
+ <button class="btn btn-outline text-sm px-4 py-2">查看详情</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 离线设备卡片 -->
|
|
|
+ <div class="device-card offline">
|
|
|
+ <button class="history-btn" onclick="showDeviceHistory(this.closest('.device-card'))" title="查看历史数据">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M12 20v-6M6 20V10M18 20V4"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="card-header">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <h4 class="text-lg font-medium">土壤监测器 #4</h4>
|
|
|
+ <span class="status-badge offline">离线</span>
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-400 mt-1">西区2号地块</div>
|
|
|
+ </div>
|
|
|
+ <div class="grid grid-cols-2 gap-8 mb-6">
|
|
|
+ <div class="data-update">
|
|
|
+ <div class="text-sm text-gray-400 mb-1">土壤湿度</div>
|
|
|
+ <div class="text-3xl font-medium text-gray-500">--<span class="text-xl ml-1">%</span></div>
|
|
|
+ </div>
|
|
|
+ <div class="data-update">
|
|
|
+ <div class="text-sm text-gray-400 mb-1">土壤温度</div>
|
|
|
+ <div class="text-3xl font-medium text-gray-500">--<span class="text-xl ml-1">℃</span></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div class="text-sm text-gray-500">设备离线 > 24h</div>
|
|
|
+ <button class="btn btn-outline text-sm px-4 py-2">查看详情</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 第二行设备 -->
|
|
|
+ <!-- 水质监测器卡片 -->
|
|
|
+ <div class="device-card">
|
|
|
+ <button class="history-btn" onclick="showDeviceHistory(this.closest('.device-card'))" title="查看历史数据">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M12 20v-6M6 20V10M18 20V4"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="card-header">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <h4 class="text-lg font-medium">水质监测器 #3</h4>
|
|
|
+ <span class="status-badge online">在线</span>
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-400 mt-1">东区2号地块</div>
|
|
|
+ </div>
|
|
|
+ <div class="grid grid-cols-2 gap-8 mb-6">
|
|
|
+ <div class="data-update">
|
|
|
+ <div class="text-sm text-gray-400 mb-1">pH值</div>
|
|
|
+ <div class="text-3xl font-medium">7.2</div>
|
|
|
+ </div>
|
|
|
+ <div class="data-update">
|
|
|
+ <div class="text-sm text-gray-400 mb-1">溶解氧</div>
|
|
|
+ <div class="text-3xl font-medium">5.8<span class="text-xl ml-1">mg/L</span></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div class="text-sm text-gray-400">5分钟前更新</div>
|
|
|
+ <div class="flex gap-2">
|
|
|
+ <button class="btn btn-outline text-sm px-4 py-2">查看详情</button>
|
|
|
+ <button class="btn btn-outline text-sm px-4 py-2" onclick="showDeviceHistory(this.closest('.device-card'))">
|
|
|
+ <i class="iconfont icon-chart mr-1"></i>历史数据
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 气象监测器卡片(告警状态) -->
|
|
|
+ <div class="device-card alert">
|
|
|
+ <button class="history-btn" onclick="showDeviceHistory(this.closest('.device-card'))" title="查看历史数据">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M12 20v-6M6 20V10M18 20V4"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="card-header">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <h4 class="text-lg font-medium">气象监测器 #2</h4>
|
|
|
+ <span class="status-badge warning">告警</span>
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-400 mt-1">西区气象站</div>
|
|
|
+ </div>
|
|
|
+ <div class="grid grid-cols-2 gap-8 mb-6">
|
|
|
+ <div class="data-update">
|
|
|
+ <div class="text-sm text-gray-400 mb-1">温度</div>
|
|
|
+ <div class="text-3xl font-medium text-red-500">35.8<span class="text-xl ml-1">℃</span></div>
|
|
|
+ </div>
|
|
|
+ <div class="data-update">
|
|
|
+ <div class="text-sm text-gray-400 mb-1">湿度</div>
|
|
|
+ <div class="text-3xl font-medium">45<span class="text-xl ml-1">%</span></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div class="text-sm text-red-500">温度过高</div>
|
|
|
+ <button class="btn btn-outline text-sm px-4 py-2">处理告警</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 土壤监测器卡片 #5 -->
|
|
|
+ <div class="device-card">
|
|
|
+ <button class="history-btn" onclick="showDeviceHistory(this.closest('.device-card'))" title="查看历史数据">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M12 20v-6M6 20V10M18 20V4"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="card-header">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <h4 class="text-lg font-medium">土壤监测器 #5</h4>
|
|
|
+ <span class="status-badge online">在线</span>
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-400 mt-1">南区1号地块</div>
|
|
|
+ </div>
|
|
|
+ <div class="grid grid-cols-2 gap-8 mb-6">
|
|
|
+ <div class="data-update">
|
|
|
+ <div class="text-sm text-gray-400 mb-1">土壤湿度</div>
|
|
|
+ <div class="text-3xl font-medium">28.5<span class="text-xl ml-1">%</span></div>
|
|
|
+ </div>
|
|
|
+ <div class="data-update">
|
|
|
+ <div class="text-sm text-gray-400 mb-1">土壤温度</div>
|
|
|
+ <div class="text-3xl font-medium">22.8<span class="text-xl ml-1">℃</span></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div class="text-sm text-gray-400">3分钟前更新</div>
|
|
|
+ <button class="btn btn-outline text-sm px-4 py-2">查看详情</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 水质监测器卡片 #4 -->
|
|
|
+ <div class="device-card">
|
|
|
+ <button class="history-btn" onclick="showDeviceHistory(this.closest('.device-card'))" title="查看历史数据">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M12 20v-6M6 20V10M18 20V4"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="card-header">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <h4 class="text-lg font-medium">水质监测器 #4</h4>
|
|
|
+ <span class="status-badge online">在线</span>
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-400 mt-1">南区水质站</div>
|
|
|
+ </div>
|
|
|
+ <div class="grid grid-cols-2 gap-8 mb-6">
|
|
|
+ <div class="data-update">
|
|
|
+ <div class="text-sm text-gray-400 mb-1">pH值</div>
|
|
|
+ <div class="text-3xl font-medium">7.1</div>
|
|
|
+ </div>
|
|
|
+ <div class="data-update">
|
|
|
+ <div class="text-sm text-gray-400 mb-1">溶解氧</div>
|
|
|
+ <div class="text-3xl font-medium">6.5<span class="text-xl ml-1">mg/L</span></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div class="text-sm text-gray-400">1分钟前更新</div>
|
|
|
+ <button class="btn btn-outline text-sm px-4 py-2">查看详情</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 单个摄像头全屏模式 -->
|
|
|
+ <div id="singleCameraFullscreen">
|
|
|
+ <div class="video-area">
|
|
|
+ <div class="flex justify-between items-center p-4">
|
|
|
+ <h3 class="text-lg font-medium">东区1号摄像头</h3>
|
|
|
+ <button class="video-nav-btn" id="exitSingleFullscreen">
|
|
|
+ <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
+ <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="flex-grow bg-slate-800 relative">
|
|
|
+ <!-- 视频画面区域 -->
|
|
|
+ <div class="absolute inset-0 flex items-center justify-center">
|
|
|
+ <svg class="w-24 h-24 text-slate-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M23 7l-7 5 7 5V7z"/>
|
|
|
+ <rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="control-panel">
|
|
|
+ <div class="space-y-6">
|
|
|
+ <!-- 云台控制 -->
|
|
|
+ <div>
|
|
|
+ <h4 class="text-sm font-medium mb-3">云台控制</h4>
|
|
|
+ <div class="ptz-controls">
|
|
|
+ <button class="ptz-btn" title="左上">↖</button>
|
|
|
+ <button class="ptz-btn" title="向上">↑</button>
|
|
|
+ <button class="ptz-btn" title="右上">↗</button>
|
|
|
+ <button class="ptz-btn" title="向左">←</button>
|
|
|
+ <button class="ptz-btn" title="停止">●</button>
|
|
|
+ <button class="ptz-btn" title="向右">→</button>
|
|
|
+ <button class="ptz-btn" title="左下">↙</button>
|
|
|
+ <button class="ptz-btn" title="向下">↓</button>
|
|
|
+ <button class="ptz-btn" title="右下">↘</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 变倍控制 -->
|
|
|
+ <div>
|
|
|
+ <h4 class="text-sm font-medium mb-3">变倍控制</h4>
|
|
|
+ <div class="grid grid-cols-2 gap-3">
|
|
|
+ <button class="ptz-btn" title="放大">
|
|
|
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v6m3-3H7"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <button class="ptz-btn" title="缩小">
|
|
|
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM7 10h6"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 预置位 -->
|
|
|
+ <div>
|
|
|
+ <h4 class="text-sm font-medium mb-3">预置位</h4>
|
|
|
+ <div class="preset-grid">
|
|
|
+ <button class="preset-btn">位置1</button>
|
|
|
+ <button class="preset-btn">位置2</button>
|
|
|
+ <button class="preset-btn">位置3</button>
|
|
|
+ <button class="preset-btn">位置4</button>
|
|
|
+ <button class="preset-btn">位置5</button>
|
|
|
+ <button class="preset-btn">位置6</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 回放控制 -->
|
|
|
+ <div>
|
|
|
+ <h4 class="text-sm font-medium mb-3">回放控制</h4>
|
|
|
+ <div class="grid grid-cols-2 gap-3">
|
|
|
+ <button class="ptz-btn" title="开始回放">
|
|
|
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"/>
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <button class="ptz-btn" title="暂停回放">
|
|
|
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 9v6m4-6v6m7-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 其他控制 -->
|
|
|
+ <div>
|
|
|
+ <h4 class="text-sm font-medium mb-3">其他控制</h4>
|
|
|
+ <div class="grid grid-cols-2 gap-3">
|
|
|
+ <button class="ptz-btn" title="截图">
|
|
|
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/>
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <button class="ptz-btn" title="录制">
|
|
|
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
+ <circle cx="12" cy="12" r="10" stroke-width="2"/>
|
|
|
+ <circle cx="12" cy="12" r="3" fill="currentColor"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 历史数据图表模态框 -->
|
|
|
+ <div id="historyChartModal" class="modal-backdrop">
|
|
|
+ <div class="modal-content">
|
|
|
+ <!-- 模态框头部 -->
|
|
|
+ <div class="modal-header">
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
+ <h3 id="modalTitle" class="text-lg font-medium"></h3>
|
|
|
+ <span id="modalStatus" class="status-badge"></span>
|
|
|
+ </div>
|
|
|
+ <button id="closeModal" class="modal-close">
|
|
|
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 图表控制区域 -->
|
|
|
+ <div class="border-b border-slate-700 px-6 py-4">
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <div class="flex gap-2">
|
|
|
+ <button class="time-range-btn active" data-range="24h">近24小时</button>
|
|
|
+ <button class="time-range-btn" data-range="7d">近7天</button>
|
|
|
+ <button class="time-range-btn" data-range="30d">近30天</button>
|
|
|
+ </div>
|
|
|
+ <div class="flex gap-2" id="indicatorBtns">
|
|
|
+ <button class="indicator-btn active" data-indicator="all">所有指标</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 图表区域 -->
|
|
|
+ <div class="modal-body">
|
|
|
+ <div id="historyChart" class="w-full" style="height: 400px;"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
<script>
|
|
|
- // 监听iframe加载完成
|
|
|
- document.getElementById('content-frame').onload = function() {
|
|
|
- // 确保iframe高度铺满
|
|
|
- this.style.height = window.innerHeight + 'px';
|
|
|
+ // 页面状态管理
|
|
|
+ const state = {
|
|
|
+ currentPage: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ totalDevices: 45,
|
|
|
+ selectedFarm: 'all',
|
|
|
+ selectedArea: 'all',
|
|
|
+ timeRange: 'realtime',
|
|
|
+ deviceType: 'all',
|
|
|
+ sortOrder: 'default',
|
|
|
+ autoRefreshInterval: 30,
|
|
|
+ currentVideoIndex: 0
|
|
|
};
|
|
|
-
|
|
|
- // 页面大小变化时调整iframe大小
|
|
|
- window.addEventListener('resize', function() {
|
|
|
- document.getElementById('content-frame').style.height = window.innerHeight + 'px';
|
|
|
+
|
|
|
+ // 初始化页面
|
|
|
+ document.addEventListener('DOMContentLoaded', function() {
|
|
|
+ initializeSelectors();
|
|
|
+ initializeVideoControls();
|
|
|
+ initializeDeviceFilters();
|
|
|
+ setupAutoRefresh();
|
|
|
+ initializeDataUpdates();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 初始化选择器
|
|
|
+ function initializeSelectors() {
|
|
|
+ const farmSelector = document.getElementById('farmSelector');
|
|
|
+ const areaSelector = document.getElementById('areaSelector');
|
|
|
+ const timeRange = document.getElementById('timeRange');
|
|
|
+
|
|
|
+ // 农场选择联动
|
|
|
+ farmSelector.addEventListener('change', function() {
|
|
|
+ state.selectedFarm = this.value;
|
|
|
+ updateAreaOptions(this.value);
|
|
|
+ refreshData();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 地块选择
|
|
|
+ areaSelector.addEventListener('change', function() {
|
|
|
+ state.selectedArea = this.value;
|
|
|
+ refreshData();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 时间范围
|
|
|
+ timeRange.addEventListener('change', function() {
|
|
|
+ state.timeRange = this.value;
|
|
|
+ refreshData();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化视频控制
|
|
|
+ function initializeVideoControls() {
|
|
|
+ const prevBtn = document.getElementById('prevPage');
|
|
|
+ const nextBtn = document.getElementById('nextPage');
|
|
|
+ const fullscreenBtn = document.getElementById('gridFullscreen');
|
|
|
+
|
|
|
+ prevBtn.addEventListener('click', () => switchVideo('prev'));
|
|
|
+ nextBtn.addEventListener('click', () => switchVideo('next'));
|
|
|
+
|
|
|
+ // 视频全屏
|
|
|
+ fullscreenBtn.addEventListener('click', function() {
|
|
|
+ const mainVideo = document.querySelector('.main-video');
|
|
|
+ if (mainVideo.requestFullscreen) {
|
|
|
+ mainVideo.requestFullscreen();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 缩略图点击切换主视频
|
|
|
+ document.querySelectorAll('.video-grid .video-card:not(.main-video)').forEach((card, index) => {
|
|
|
+ card.addEventListener('click', () => {
|
|
|
+ state.currentVideoIndex = index;
|
|
|
+ updateMainVideo(index);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 切换视频
|
|
|
+ function switchVideo(direction) {
|
|
|
+ const videos = document.querySelectorAll('.video-grid .video-card:not(.main-video)');
|
|
|
+ if (direction === 'next') {
|
|
|
+ state.currentVideoIndex = (state.currentVideoIndex + 1) % videos.length;
|
|
|
+ } else {
|
|
|
+ state.currentVideoIndex = state.currentVideoIndex === 0 ? videos.length - 1 : state.currentVideoIndex - 1;
|
|
|
+ }
|
|
|
+ updateMainVideo(state.currentVideoIndex);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新主视频显示
|
|
|
+ function updateMainVideo(index) {
|
|
|
+ const mainVideo = document.querySelector('.main-video');
|
|
|
+ const selectedVideo = document.querySelectorAll('.video-grid .video-card:not(.main-video)')[index];
|
|
|
+
|
|
|
+ // 更新视频源和信息
|
|
|
+ mainVideo.querySelector('img').src = selectedVideo.querySelector('img').src;
|
|
|
+ mainVideo.querySelector('.text-white').textContent = `摄像头 #${index + 1}`;
|
|
|
+
|
|
|
+ // 高亮当前选中的缩略图
|
|
|
+ document.querySelectorAll('.video-grid .video-card:not(.main-video)').forEach((card, i) => {
|
|
|
+ card.classList.toggle('ring-2', i === index);
|
|
|
+ card.classList.toggle('ring-primary', i === index);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化设备筛选
|
|
|
+ function initializeDeviceFilters() {
|
|
|
+ const typeFilter = document.getElementById('deviceTypeFilter');
|
|
|
+ const sortOrder = document.getElementById('sortOrder');
|
|
|
+
|
|
|
+ typeFilter.addEventListener('change', function() {
|
|
|
+ state.deviceType = this.value;
|
|
|
+ refreshDeviceList();
|
|
|
+ });
|
|
|
+
|
|
|
+ sortOrder.addEventListener('change', function() {
|
|
|
+ state.sortOrder = this.value;
|
|
|
+ refreshDeviceList();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置自动刷新
|
|
|
+ function setupAutoRefresh() {
|
|
|
+ const autoRefreshSelect = document.getElementById('autoRefresh');
|
|
|
+ let refreshInterval;
|
|
|
+
|
|
|
+ function updateRefreshInterval() {
|
|
|
+ if (refreshInterval) {
|
|
|
+ clearInterval(refreshInterval);
|
|
|
+ }
|
|
|
+
|
|
|
+ const seconds = parseInt(autoRefreshSelect.value);
|
|
|
+ state.autoRefreshInterval = seconds;
|
|
|
+ refreshInterval = setInterval(refreshData, seconds * 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ autoRefreshSelect.addEventListener('change', updateRefreshInterval);
|
|
|
+ updateRefreshInterval(); // 初始化定时器
|
|
|
+
|
|
|
+ // 手动刷新按钮
|
|
|
+ const refreshBtn = document.getElementById('refreshBtn');
|
|
|
+ const refreshIcon = refreshBtn.querySelector('.iconfont');
|
|
|
+
|
|
|
+ refreshBtn.addEventListener('click', function() {
|
|
|
+ refreshIcon.classList.add('refresh-spin');
|
|
|
+ refreshData();
|
|
|
+ setTimeout(() => {
|
|
|
+ refreshIcon.classList.remove('refresh-spin');
|
|
|
+ }, 1000);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化数据更新
|
|
|
+ function initializeDataUpdates() {
|
|
|
+ // 更新时间显示
|
|
|
+ function updateLastRefreshTime() {
|
|
|
+ const lastUpdate = document.getElementById('lastUpdate');
|
|
|
+ const now = new Date();
|
|
|
+ const diff = Math.floor((now - window.lastRefreshTime) / 1000);
|
|
|
+
|
|
|
+ if (diff < 60) {
|
|
|
+ lastUpdate.textContent = diff + '秒前';
|
|
|
+ } else if (diff < 3600) {
|
|
|
+ lastUpdate.textContent = Math.floor(diff / 60) + '分钟前';
|
|
|
+ } else {
|
|
|
+ lastUpdate.textContent = Math.floor(diff / 3600) + '小时前';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 定时更新时间显示
|
|
|
+ window.lastRefreshTime = new Date();
|
|
|
+ setInterval(updateLastRefreshTime, 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 刷新数据
|
|
|
+ async function refreshData() {
|
|
|
+ // 更新最后刷新时间
|
|
|
+ window.lastRefreshTime = new Date();
|
|
|
+
|
|
|
+ // 模拟数据更新效果
|
|
|
+ const dataElements = document.querySelectorAll('.data-update');
|
|
|
+ dataElements.forEach(el => {
|
|
|
+ el.classList.add('flash');
|
|
|
+ setTimeout(() => el.classList.remove('flash'), 500);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 这里应该调用实际的API获取最新数据
|
|
|
+ await Promise.all([
|
|
|
+ refreshDeviceList(),
|
|
|
+ refreshDeviceStats(),
|
|
|
+ refreshVideoFeeds()
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 刷新设备列表
|
|
|
+ async function refreshDeviceList() {
|
|
|
+ // 模拟API调用
|
|
|
+ console.log('Refreshing device list with filters:', {
|
|
|
+ farm: state.selectedFarm,
|
|
|
+ area: state.selectedArea,
|
|
|
+ type: state.deviceType,
|
|
|
+ sort: state.sortOrder,
|
|
|
+ page: state.currentPage
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 刷新设备统计
|
|
|
+ async function refreshDeviceStats() {
|
|
|
+ // 模拟API调用
|
|
|
+ console.log('Refreshing device statistics');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 刷新视频源
|
|
|
+ async function refreshVideoFeeds() {
|
|
|
+ // 模拟API调用
|
|
|
+ console.log('Refreshing video feeds');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新地块选项
|
|
|
+ function updateAreaOptions(farmId) {
|
|
|
+ const areaSelector = document.getElementById('areaSelector');
|
|
|
+ // 模拟API数据
|
|
|
+ const areaData = {
|
|
|
+ 'farm1': [
|
|
|
+ {id: 'area1', name: '东区1号地块'},
|
|
|
+ {id: 'area2', name: '东区2号地块'}
|
|
|
+ ],
|
|
|
+ 'farm2': [
|
|
|
+ {id: 'area3', name: '西区1号地块'},
|
|
|
+ {id: 'area4', name: '西区2号地块'}
|
|
|
+ ]
|
|
|
+ };
|
|
|
+
|
|
|
+ // 清空现有选项
|
|
|
+ areaSelector.innerHTML = '<option value="all">全部地块</option>';
|
|
|
+
|
|
|
+ // 添加新选项
|
|
|
+ if (farmId !== 'all' && areaData[farmId]) {
|
|
|
+ areaData[farmId].forEach(area => {
|
|
|
+ const option = document.createElement('option');
|
|
|
+ option.value = area.id;
|
|
|
+ option.textContent = area.name;
|
|
|
+ areaSelector.appendChild(option);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 视频布局切换功能
|
|
|
+ document.addEventListener('DOMContentLoaded', function() {
|
|
|
+ const container = document.querySelector('.video-container');
|
|
|
+ const videoCards = document.querySelectorAll('.video-card');
|
|
|
+ const prevBtn = document.querySelector('.video-nav-btn[title="上一页"]');
|
|
|
+ const nextBtn = document.querySelector('.video-nav-btn[title="下一页"]');
|
|
|
+ const fullscreenBtn = document.querySelector('.video-nav-btn[title="全屏显示"]');
|
|
|
+
|
|
|
+ let currentPage = 1;
|
|
|
+ const cardsPerPage = 6;
|
|
|
+ const totalPages = Math.ceil(videoCards.length / cardsPerPage);
|
|
|
+
|
|
|
+ // 更新页码显示
|
|
|
+ function updatePagination() {
|
|
|
+ const start = (currentPage - 1) * cardsPerPage + 1;
|
|
|
+ const end = Math.min(currentPage * cardsPerPage, videoCards.length);
|
|
|
+ document.querySelector('.text-sm.text-gray-400').textContent =
|
|
|
+ `${start}-${end} / ${videoCards.length}`;
|
|
|
+
|
|
|
+ // 更新按钮状态
|
|
|
+ prevBtn.disabled = currentPage === 1;
|
|
|
+ nextBtn.disabled = currentPage === totalPages;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示指定页的视频
|
|
|
+ function showPage(page) {
|
|
|
+ videoCards.forEach((card, index) => {
|
|
|
+ const shouldShow = index >= (page - 1) * cardsPerPage &&
|
|
|
+ index < page * cardsPerPage;
|
|
|
+ card.style.display = shouldShow ? '' : 'none';
|
|
|
+ });
|
|
|
+ updatePagination();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 翻页事件
|
|
|
+ prevBtn.addEventListener('click', () => {
|
|
|
+ if (currentPage > 1) {
|
|
|
+ currentPage--;
|
|
|
+ showPage(currentPage);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ nextBtn.addEventListener('click', () => {
|
|
|
+ if (currentPage < totalPages) {
|
|
|
+ currentPage++;
|
|
|
+ showPage(currentPage);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 全屏显示
|
|
|
+ fullscreenBtn.addEventListener('click', () => {
|
|
|
+ if (container.requestFullscreen) {
|
|
|
+ container.requestFullscreen();
|
|
|
+ } else if (container.webkitRequestFullscreen) {
|
|
|
+ container.webkitRequestFullscreen();
|
|
|
+ } else if (container.msRequestFullscreen) {
|
|
|
+ container.msRequestFullscreen();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 初始化显示
|
|
|
+ showPage(1);
|
|
|
});
|
|
|
+
|
|
|
+ document.addEventListener('DOMContentLoaded', function() {
|
|
|
+ const videoGrid = document.getElementById('videoGrid');
|
|
|
+ const gridFullscreenBtn = document.getElementById('gridFullscreen');
|
|
|
+ const singleFullscreenModal = document.getElementById('singleCameraFullscreen');
|
|
|
+ const exitSingleFullscreenBtn = document.getElementById('exitSingleFullscreen');
|
|
|
+ const cameraFullscreenBtns = document.querySelectorAll('.camera-fullscreen-btn');
|
|
|
+
|
|
|
+ // 显示指定页的视频
|
|
|
+ function showPage(page) {
|
|
|
+ const videoCards = document.querySelectorAll('.video-card');
|
|
|
+ const cardsPerPage = 3; // 每页显示3个
|
|
|
+ videoCards.forEach((card, index) => {
|
|
|
+ const shouldShow = index >= (page - 1) * cardsPerPage &&
|
|
|
+ index < page * cardsPerPage;
|
|
|
+ card.style.display = shouldShow ? 'block' : 'none';
|
|
|
+ });
|
|
|
+ updatePagination(page, Math.ceil(videoCards.length / cardsPerPage));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新分页显示
|
|
|
+ function updatePagination(currentPage, totalPages) {
|
|
|
+ const cardsPerPage = 3;
|
|
|
+ const start = (currentPage - 1) * cardsPerPage + 1;
|
|
|
+ const end = Math.min(currentPage * cardsPerPage, 12);
|
|
|
+ document.querySelector('.text-sm.text-gray-400').textContent =
|
|
|
+ `${start}-${end} / 12`;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 网格全屏模式
|
|
|
+ gridFullscreenBtn.addEventListener('click', () => {
|
|
|
+ if (document.fullscreenElement) {
|
|
|
+ document.exitFullscreen();
|
|
|
+ } else {
|
|
|
+ videoGrid.requestFullscreen();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 进入全屏时添加样式
|
|
|
+ document.addEventListener('fullscreenchange', () => {
|
|
|
+ if (document.fullscreenElement === videoGrid) {
|
|
|
+ videoGrid.classList.add('fullscreen-mode');
|
|
|
+ // 全屏模式下显示6个摄像头
|
|
|
+ const videoCards = document.querySelectorAll('.video-card');
|
|
|
+ videoCards.forEach((card, index) => {
|
|
|
+ card.style.display = index < 6 ? 'block' : 'none';
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ videoGrid.classList.remove('fullscreen-mode');
|
|
|
+ // 退出全屏时恢复原始显示
|
|
|
+ showPage(1);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 单个摄像头全屏
|
|
|
+ cameraFullscreenBtns.forEach(btn => {
|
|
|
+ btn.addEventListener('click', (e) => {
|
|
|
+ e.stopPropagation();
|
|
|
+ const card = btn.closest('.video-card');
|
|
|
+ const title = card.querySelector('.text-white').textContent;
|
|
|
+ singleFullscreenModal.querySelector('h3').textContent = title;
|
|
|
+ singleFullscreenModal.classList.add('active');
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 退出单个摄像头全屏
|
|
|
+ exitSingleFullscreenBtn.addEventListener('click', () => {
|
|
|
+ singleFullscreenModal.classList.remove('active');
|
|
|
+ });
|
|
|
+
|
|
|
+ // ESC 键退出单个摄像头全屏
|
|
|
+ document.addEventListener('keydown', (e) => {
|
|
|
+ if (e.key === 'Escape' && singleFullscreenModal.classList.contains('active')) {
|
|
|
+ singleFullscreenModal.classList.remove('active');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 初始化显示第一页
|
|
|
+ showPage(1);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 初始化图表相关功能
|
|
|
+ document.addEventListener('DOMContentLoaded', function() {
|
|
|
+ initializeChartModal();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 初始化图表模态框
|
|
|
+ function initializeChartModal() {
|
|
|
+ const modal = document.getElementById('historyChartModal');
|
|
|
+ const closeBtn = document.getElementById('closeModal');
|
|
|
+ const timeRangeBtns = document.querySelectorAll('.time-range-btn');
|
|
|
+ let currentChart = null;
|
|
|
+ let currentDeviceType = '';
|
|
|
+ let currentDeviceId = '';
|
|
|
+
|
|
|
+ // 关闭模态框
|
|
|
+ function closeModal() {
|
|
|
+ modal.classList.remove('active');
|
|
|
+ setTimeout(() => {
|
|
|
+ if (currentChart) {
|
|
|
+ currentChart.dispose();
|
|
|
+ currentChart = null;
|
|
|
+ }
|
|
|
+ }, 300); // 等待过渡动画完成
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化图表
|
|
|
+ function initializeChart() {
|
|
|
+ const chartDom = document.getElementById('historyChart');
|
|
|
+ if (currentChart) {
|
|
|
+ currentChart.dispose();
|
|
|
+ }
|
|
|
+ currentChart = echarts.init(chartDom);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新指标按钮
|
|
|
+ function updateIndicatorButtons(deviceType) {
|
|
|
+ const container = document.getElementById('indicatorBtns');
|
|
|
+ const indicators = {
|
|
|
+ '土壤监测器': ['所有指标', '土壤湿度', '土壤温度', 'EC值'],
|
|
|
+ '水质监测器': ['所有指标', 'pH值', '溶解氧', '电导率', '浊度'],
|
|
|
+ '气象监测器': ['所有指标', '温度', '湿度', '光照', '风速', '降雨量']
|
|
|
+ }[deviceType] || ['所有指标'];
|
|
|
+
|
|
|
+ // 清空现有按钮
|
|
|
+ container.innerHTML = '';
|
|
|
+
|
|
|
+ // 添加新按钮
|
|
|
+ indicators.forEach((indicator, index) => {
|
|
|
+ const btn = document.createElement('button');
|
|
|
+ btn.className = `indicator-btn${index === 0 ? ' active' : ''}`;
|
|
|
+ btn.textContent = indicator;
|
|
|
+ btn.dataset.indicator = indicator;
|
|
|
+ btn.onclick = () => {
|
|
|
+ document.querySelectorAll('.indicator-btn').forEach(b => b.classList.remove('active'));
|
|
|
+ btn.classList.add('active');
|
|
|
+ updateChartData();
|
|
|
+ };
|
|
|
+ container.appendChild(btn);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新图表数据
|
|
|
+ function updateChartData() {
|
|
|
+ const activeTimeRange = document.querySelector('.time-range-btn.active').dataset.range;
|
|
|
+ const activeIndicator = document.querySelector('.indicator-btn.active').dataset.indicator;
|
|
|
+ const data = generateMockData(activeTimeRange, currentDeviceType);
|
|
|
+
|
|
|
+ const option = {
|
|
|
+ backgroundColor: 'transparent',
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ backgroundColor: 'rgba(15, 23, 42, 0.9)',
|
|
|
+ borderColor: '#334155',
|
|
|
+ textStyle: { color: '#f1f5f9' }
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: getIndicatorNames(currentDeviceType, activeIndicator),
|
|
|
+ textStyle: { color: '#94a3b8' },
|
|
|
+ top: 0
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ left: '3%',
|
|
|
+ right: '4%',
|
|
|
+ bottom: '3%',
|
|
|
+ containLabel: true,
|
|
|
+ top: 40
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ boundaryGap: false,
|
|
|
+ data: data.times,
|
|
|
+ axisLine: { lineStyle: { color: '#334155' } },
|
|
|
+ axisLabel: { color: '#94a3b8' }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ axisLine: { lineStyle: { color: '#334155' } },
|
|
|
+ axisLabel: { color: '#94a3b8' },
|
|
|
+ splitLine: { lineStyle: { color: '#1e293b' } }
|
|
|
+ },
|
|
|
+ series: generateSeries(currentDeviceType, activeIndicator, data)
|
|
|
+ };
|
|
|
+
|
|
|
+ currentChart.setOption(option);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取指标名称
|
|
|
+ function getIndicatorNames(deviceType, activeIndicator) {
|
|
|
+ const indicators = {
|
|
|
+ '土壤监测器': ['土壤湿度', '土壤温度', 'EC值'],
|
|
|
+ '水质监测器': ['pH值', '溶解氧', '电导率', '浊度'],
|
|
|
+ '气象监测器': ['温度', '湿度', '光照', '风速', '降雨量']
|
|
|
+ }[deviceType] || ['指标1', '指标2'];
|
|
|
+
|
|
|
+ return activeIndicator === '所有指标' ? indicators : [activeIndicator];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成图表系列
|
|
|
+ function generateSeries(deviceType, activeIndicator, data) {
|
|
|
+ const indicators = {
|
|
|
+ '土壤监测器': {
|
|
|
+ names: ['土壤湿度', '土壤温度', 'EC值'],
|
|
|
+ units: ['%', '℃', 'ms/cm'],
|
|
|
+ colors: ['#0ea5e9', '#10b981', '#f59e0b']
|
|
|
+ },
|
|
|
+ '水质监测器': {
|
|
|
+ names: ['pH值', '溶解氧', '电导率', '浊度'],
|
|
|
+ units: ['', 'mg/L', 'ms/cm', 'NTU'],
|
|
|
+ colors: ['#0ea5e9', '#10b981', '#f59e0b', '#8b5cf6']
|
|
|
+ },
|
|
|
+ '气象监测器': {
|
|
|
+ names: ['温度', '湿度', '光照', '风速', '降雨量'],
|
|
|
+ units: ['℃', '%', 'lux', 'm/s', 'mm'],
|
|
|
+ colors: ['#0ea5e9', '#10b981', '#f59e0b', '#8b5cf6', '#ec4899']
|
|
|
+ }
|
|
|
+ }[deviceType] || { names: [], units: [], colors: [] };
|
|
|
+
|
|
|
+ if (activeIndicator === '所有指标') {
|
|
|
+ return indicators.names.map((name, index) => ({
|
|
|
+ name: name,
|
|
|
+ type: 'line',
|
|
|
+ data: data[`values${index + 1}`],
|
|
|
+ smooth: true,
|
|
|
+ showSymbol: false,
|
|
|
+ lineStyle: { width: 2 },
|
|
|
+ areaStyle: { opacity: 0.1 },
|
|
|
+ itemStyle: { color: indicators.colors[index] }
|
|
|
+ }));
|
|
|
+ } else {
|
|
|
+ const index = indicators.names.indexOf(activeIndicator);
|
|
|
+ return [{
|
|
|
+ name: activeIndicator,
|
|
|
+ type: 'line',
|
|
|
+ data: data[`values${index + 1}`],
|
|
|
+ smooth: true,
|
|
|
+ showSymbol: false,
|
|
|
+ lineStyle: { width: 2 },
|
|
|
+ areaStyle: { opacity: 0.1 },
|
|
|
+ itemStyle: { color: indicators.colors[index] }
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成模拟数据
|
|
|
+ function generateMockData(timeRange, deviceType) {
|
|
|
+ const times = [];
|
|
|
+ const values = {};
|
|
|
+ const points = timeRange === '24h' ? 24 : timeRange === '7d' ? 7 : 30;
|
|
|
+ const now = new Date();
|
|
|
+
|
|
|
+ const ranges = {
|
|
|
+ '土壤监测器': [
|
|
|
+ [20, 40], // 土壤湿度
|
|
|
+ [15, 30], // 土壤温度
|
|
|
+ [0.5, 2] // EC值
|
|
|
+ ],
|
|
|
+ '水质监测器': [
|
|
|
+ [6.5, 8.5], // pH值
|
|
|
+ [4, 8], // 溶解氧
|
|
|
+ [0.5, 2], // 电导率
|
|
|
+ [0, 10] // 浊度
|
|
|
+ ],
|
|
|
+ '气象监测器': [
|
|
|
+ [15, 35], // 温度
|
|
|
+ [40, 80], // 湿度
|
|
|
+ [0, 100000], // 光照
|
|
|
+ [0, 10], // 风速
|
|
|
+ [0, 50] // 降雨量
|
|
|
+ ]
|
|
|
+ }[deviceType] || [[0, 100]];
|
|
|
+
|
|
|
+ for (let i = points - 1; i >= 0; i--) {
|
|
|
+ const time = new Date(now - i * (timeRange === '24h' ? 3600000 : 86400000));
|
|
|
+ times.push(timeRange === '24h' ?
|
|
|
+ time.getHours() + ':00' :
|
|
|
+ (time.getMonth() + 1) + '/' + time.getDate());
|
|
|
+
|
|
|
+ ranges.forEach((range, index) => {
|
|
|
+ if (!values[`values${index + 1}`]) {
|
|
|
+ values[`values${index + 1}`] = [];
|
|
|
+ }
|
|
|
+ values[`values${index + 1}`].push(
|
|
|
+ (Math.random() * (range[1] - range[0]) + range[0]).toFixed(1)
|
|
|
+ );
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return { times, ...values };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示设备历史数据
|
|
|
+ window.showDeviceHistory = function(card) {
|
|
|
+ const deviceName = card.querySelector('h4').textContent;
|
|
|
+ const deviceType = deviceName.split(' ')[0];
|
|
|
+ const status = card.querySelector('.status-badge').cloneNode(true);
|
|
|
+ currentDeviceType = deviceType;
|
|
|
+ currentDeviceId = card.dataset.deviceId;
|
|
|
+
|
|
|
+ // 更新模态框标题和状态
|
|
|
+ document.getElementById('modalTitle').textContent = `${deviceName} - 历史数据`;
|
|
|
+ const modalStatus = document.getElementById('modalStatus');
|
|
|
+ modalStatus.className = status.className;
|
|
|
+ modalStatus.textContent = status.textContent;
|
|
|
+
|
|
|
+ // 更新指标按钮
|
|
|
+ updateIndicatorButtons(deviceType);
|
|
|
+
|
|
|
+ // 显示模态框
|
|
|
+ modal.classList.add('active');
|
|
|
+
|
|
|
+ // 等待模态框显示后再初始化图表
|
|
|
+ setTimeout(() => {
|
|
|
+ initializeChart();
|
|
|
+ updateChartData();
|
|
|
+ }, 100);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 事件监听
|
|
|
+ closeBtn.addEventListener('click', closeModal);
|
|
|
+ modal.addEventListener('click', (e) => {
|
|
|
+ if (e.target === modal) {
|
|
|
+ closeModal();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ timeRangeBtns.forEach(btn => {
|
|
|
+ btn.addEventListener('click', () => {
|
|
|
+ timeRangeBtns.forEach(b => b.classList.remove('active'));
|
|
|
+ btn.classList.add('active');
|
|
|
+ if (currentChart) {
|
|
|
+ updateChartData();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 监听窗口大小变化
|
|
|
+ window.addEventListener('resize', () => {
|
|
|
+ if (currentChart) {
|
|
|
+ currentChart.resize();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // ESC键关闭模态框
|
|
|
+ document.addEventListener('keydown', (e) => {
|
|
|
+ if (e.key === 'Escape' && modal.classList.contains('active')) {
|
|
|
+ closeModal();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
</script>
|
|
|
</body>
|
|
|
</html>
|