| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363 |
- <!DOCTYPE html>
- <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>
- <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: #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: var(--bg-dark);
- color: var(--text-primary);
- margin: 0;
- padding: 0;
- 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;
- }
-
- .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;
- 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>
- <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>
- // 页面状态管理
- const state = {
- currentPage: 1,
- pageSize: 10,
- totalDevices: 45,
- selectedFarm: 'all',
- selectedArea: 'all',
- timeRange: 'realtime',
- deviceType: 'all',
- sortOrder: 'default',
- autoRefreshInterval: 30,
- currentVideoIndex: 0
- };
- // 初始化页面
- 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>
|