| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>中国安全生产科学研究院实验室安全智慧化管控中心</title>
- <link rel="stylesheet" href="libs/reset.css" />
- <script src="libs/echarts.min.js"></script>
- <style>
- /* ======================== CSS Variables ======================== */
- :root {
- --screen-w: 1920;
- --screen-h: 1080;
- --bg-0: #020c1b;
- --bg-1: #061630;
- --bg-2: #0a2550;
- --panel-bg: rgba(6, 22, 56, 0.82);
- --panel-border: rgba(72, 180, 255, 0.28);
- --panel-glow: rgba(72, 180, 255, 0.06);
- --text-main: #d8f4ff;
- --text-sub: #7eacc8;
- --accent: #48d7ff;
- --accent2: #3a7bff;
- --good: #36d399;
- --warn: #ffb020;
- --danger: #ff4d4f;
- --grade-I: #ff4d4f;
- --grade-II: #ff8c00;
- --grade-III: #ffcc00;
- --grade-IV: #3a7bff;
- }
- * { box-sizing: border-box; margin: 0; padding: 0; }
- html, body {
- width: 100%; height: 100%; overflow: hidden;
- background: radial-gradient(ellipse 1400px 700px at 10% 0%, rgba(50,140,255,0.15), transparent 65%),
- radial-gradient(ellipse 1000px 500px at 90% 100%, rgba(0,200,255,0.1), transparent 60%),
- linear-gradient(150deg, var(--bg-0), var(--bg-1) 45%, var(--bg-2));
- color: var(--text-main);
- font-family: "DIN Alternate","Alibaba PuHuiTi","PingFang SC","Microsoft YaHei",sans-serif;
- }
- /* ======================== Viewport Scaling ======================== */
- .viewport { width: 100%; height: 100%; position: relative; }
- .screen {
- width: 1920px; height: 1080px;
- position: absolute; left: 50%; top: 50%;
- transform-origin: center center;
- overflow: hidden;
- }
- /* ======================== Page Toggle ======================== */
- .page { display: none; width: 100%; height: calc(100% - 72px); }
- .page.active { display: flex; }
- /* ======================== Top Nav ======================== */
- .top-nav {
- width: 100%; height: 72px; display: flex; align-items: center; justify-content: space-between;
- padding: 0 30px;
- background: linear-gradient(180deg, rgba(10,30,70,0.95) 0%, rgba(6,20,50,0.75) 100%);
- border-bottom: 1px solid var(--panel-border);
- position: relative; z-index: 100;
- }
- .top-nav::after {
- content: ''; position: absolute; bottom: 0; left: 10%; right: 10%; height: 1px;
- background: linear-gradient(90deg, transparent, var(--accent), transparent);
- }
- .nav-title {
- font-size: 26px; font-weight: 700; letter-spacing: 6px;
- background: linear-gradient(135deg, #fff 0%, var(--accent) 100%);
- -webkit-background-clip: text; -webkit-text-fill-color: transparent;
- text-shadow: 0 0 30px rgba(72,215,255,0.3);
- }
- .nav-tabs { display: flex; gap: 4px; }
- .nav-tab {
- padding: 8px 28px; border-radius: 4px; cursor: pointer;
- font-size: 15px; letter-spacing: 2px; transition: all 0.3s;
- border: 1px solid transparent; color: var(--text-sub);
- background: transparent;
- }
- .nav-tab:hover { color: var(--accent); border-color: var(--panel-border); }
- .nav-tab.active {
- background: linear-gradient(135deg, rgba(72,215,255,0.18), rgba(58,123,255,0.12));
- border-color: var(--accent); color: #fff;
- box-shadow: 0 0 16px rgba(72,215,255,0.2);
- }
- .nav-right { display: flex; align-items: center; gap: 20px; font-size: 14px; color: var(--text-sub); }
- .nav-right .clock { font-size: 22px; font-weight: 600; color: var(--accent); letter-spacing: 2px; }
- .nav-right .weather { display: flex; align-items: center; gap: 6px; }
- /* ======================== Panel Styles ======================== */
- .panel {
- background: var(--panel-bg);
- border: 1px solid var(--panel-border);
- border-radius: 6px; padding: 14px 16px;
- position: relative; overflow: hidden;
- backdrop-filter: blur(8px);
- }
- .panel::before {
- content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px;
- background: linear-gradient(90deg, transparent, var(--accent), transparent);
- opacity: 0.6;
- }
- .panel-title {
- font-size: 15px; font-weight: 600; letter-spacing: 2px; margin-bottom: 12px;
- padding-left: 12px; position: relative; color: #fff;
- }
- .panel-title::before {
- content: ''; position: absolute; left: 0; top: 2px; width: 3px; height: 14px;
- background: var(--accent); border-radius: 2px;
- box-shadow: 0 0 8px var(--accent);
- }
- /* ======================== Lab Status Page Layout ======================== */
- #page-lab { display: none; gap: 14px; padding: 14px; }
- #page-lab.active { display: flex; }
- .col-left, .col-right { width: 440px; display: flex; flex-direction: column; gap: 14px; flex-shrink: 0; }
- .col-center { flex: 1; display: flex; flex-direction: column; gap: 14px; min-width: 0; }
- /* ======================== Status Cards ======================== */
- .status-cards { display: flex; gap: 12px; margin-top: 0; }
- .status-card {
- flex: 1; text-align: center; padding: 6px 6px; border-radius: 4px;
- background: rgba(72,215,255,0.06); border: 1px solid rgba(72,215,255,0.12);
- }
- .status-card .label { font-size: 12px; color: var(--text-sub); margin-bottom: 2px; }
- .status-card .value { font-size: 22px; font-weight: 700; }
- .status-card.using .value { color: var(--accent); }
- .status-card.error .value { color: var(--danger); }
- .status-card.idle .value { color: var(--good); }
- /* ======================== Flip Counter ======================== */
- .flip-counter-row { display: flex; gap: 24px; justify-content: center; margin-bottom: 10px; }
- .flip-group { text-align: center; }
- .flip-group .flip-label { font-size: 12px; color: var(--text-sub); margin-bottom: 6px; }
- .flip-digits { display: flex; gap: 4px; justify-content: center; }
- .flip-digit {
- width: 32px; height: 42px; line-height: 42px; text-align: center;
- font-size: 26px; font-weight: 700; color: var(--accent);
- background: linear-gradient(180deg, rgba(72,215,255,0.12) 0%, rgba(6,22,56,0.9) 100%);
- border: 1px solid rgba(72,215,255,0.25); border-radius: 4px;
- }
- /* ======================== Sensor Scroll List ======================== */
- .sensor-scroll-wrap {
- flex: 1; overflow: hidden; position: relative; min-height: 0;
- }
- .sensor-scroll-inner {
- animation: scrollUp 40s linear infinite;
- }
- @keyframes scrollUp {
- 0% { transform: translateY(0); }
- 100% { transform: translateY(-50%); }
- }
- .sensor-item {
- padding: 10px 12px; margin-bottom: 6px; border-radius: 4px;
- background: rgba(72,215,255,0.04); border: 1px solid rgba(72,215,255,0.08);
- transition: all 0.3s;
- }
- .sensor-item:hover { border-color: var(--accent); background: rgba(72,215,255,0.08); }
- .sensor-item .lab-name { font-size: 13px; font-weight: 600; margin-bottom: 6px; color: #fff; }
- .sensor-item .lab-unit { font-size: 11px; color: var(--text-sub); margin-left: 6px; }
- .sensor-values { display: flex; gap: 10px; flex-wrap: wrap; }
- .sensor-val {
- font-size: 12px; padding: 2px 8px; border-radius: 3px;
- background: rgba(72,215,255,0.06);
- }
- .sensor-val.alarm {
- background: rgba(255,77,79,0.15); color: var(--danger); font-weight: 600;
- animation: alarmPulse 1s ease-in-out infinite alternate;
- }
- @keyframes alarmPulse {
- 0% { box-shadow: 0 0 4px rgba(255,77,79,0.3); }
- 100% { box-shadow: 0 0 12px rgba(255,77,79,0.6); }
- }
- .alarm-icon { display: inline-block; margin-right: 2px; }
- /* ======================== Warning Scroll ======================== */
- .warning-header { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
- .warning-count {
- font-size: 28px; font-weight: 700; color: var(--warn);
- text-shadow: 0 0 10px rgba(255,176,32,0.4);
- }
- .warning-count-label { font-size: 12px; color: var(--text-sub); }
- .warning-scroll-wrap { flex: 1; overflow: hidden; min-height: 0; }
- .warning-scroll-inner { animation: scrollUp 30s linear infinite; }
- .warning-item {
- padding: 8px 12px; margin-bottom: 6px; border-radius: 4px;
- background: rgba(255,176,32,0.04); border-left: 3px solid var(--warn);
- }
- .warning-item .w-lab { font-size: 13px; font-weight: 600; color: #fff; }
- .warning-item .w-sensor { font-size: 12px; color: var(--warn); margin: 2px 0; }
- .warning-item .w-time { font-size: 11px; color: var(--text-sub); }
- .warning-item .w-meta {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-top: 4px;
- gap: 8px;
- }
- .warning-item .w-owner { font-size: 11px; color: var(--text-sub); }
- .warn-state {
- font-size: 11px;
- padding: 2px 8px;
- border-radius: 10px;
- border: 1px solid transparent;
- white-space: nowrap;
- }
- .warn-state.pending {
- color: #ffd5d6;
- border-color: rgba(255, 77, 79, 0.45);
- background: rgba(255, 77, 79, 0.18);
- }
- .warn-state.processing {
- color: #ffe7c0;
- border-color: rgba(255, 176, 32, 0.5);
- background: rgba(255, 176, 32, 0.16);
- }
- .warn-state.resolved {
- color: #cbf7e3;
- border-color: rgba(54, 211, 153, 0.45);
- background: rgba(54, 211, 153, 0.16);
- }
- /* ======================== Device Stats Top ======================== */
- .device-top { display: flex; gap: 12px; margin-bottom: 10px; }
- .device-stat-card {
- flex: 1; text-align: center; padding: 8px; border-radius: 4px;
- background: rgba(72,215,255,0.06); border: 1px solid rgba(72,215,255,0.1);
- }
- .device-stat-card .ds-label { font-size: 12px; color: var(--text-sub); }
- .device-stat-card .ds-value { font-size: 22px; font-weight: 700; color: var(--accent); }
- .device-stat-card.offline .ds-value { color: var(--text-sub); }
- .device-bottom { display: flex; gap: 10px; align-items: center; flex: 1; min-height: 0; }
- .device-gauge-wrap { width: 50%; height: 100%; min-height: 140px; }
- .device-type-list { flex: 1; display: flex; flex-direction: column; gap: 10px; justify-content: center; }
- .device-type-item {
- display: flex; justify-content: space-between; align-items: center;
- padding: 10px 14px; border-radius: 4px;
- background: rgba(72,215,255,0.04); border: 1px solid rgba(72,215,255,0.08);
- }
- .device-type-item .dt-name { font-size: 13px; color: var(--text-sub); }
- .device-type-item .dt-value { font-size: 18px; font-weight: 700; color: var(--accent); }
- /* ======================== Equipment Stats ======================== */
- .equip-chart-wrap { flex: 1; min-height: 0; }
- .equip-mid {
- display: flex; gap: 10px; justify-content: center; margin: 10px 0;
- }
- .equip-mid-item { text-align: center; flex: 1; padding: 8px 4px; border-radius: 4px;
- background: rgba(72,215,255,0.04); border: 1px solid rgba(72,215,255,0.08); }
- .equip-mid-item .em-value { font-size: 24px; font-weight: 700; color: var(--accent); }
- .equip-mid-item .em-label { font-size: 12px; color: var(--text-sub); margin-top: 2px; }
- .equip-bottom {
- display: flex; gap: 10px;
- }
- .equip-status-card {
- flex: 1; text-align: center; padding: 12px 6px; border-radius: 4px;
- background: rgba(72,215,255,0.04); border: 1px solid rgba(72,215,255,0.08);
- }
- .equip-status-card .es-value { font-size: 22px; font-weight: 700; }
- .equip-status-card .es-label { font-size: 12px; color: var(--text-sub); margin-top: 2px; }
- /* ======================== Video Page ======================== */
- #page-video { display: none; gap: 14px; padding: 14px; }
- #page-video.active { display: flex; }
- .video-left {
- width: 300px; display: flex; flex-direction: column; gap: 10px; flex-shrink: 0;
- }
- .video-right { flex: 1; display: flex; flex-direction: column; min-width: 0; position: relative; }
- .video-search {
- width: 100%; padding: 8px 12px; border-radius: 4px;
- background: rgba(72,215,255,0.06); border: 1px solid var(--panel-border);
- color: var(--text-main); font-size: 13px; outline: none;
- }
- .video-search::placeholder { color: var(--text-sub); }
- .video-filter {
- width: 100%; padding: 8px 12px; border-radius: 4px;
- background: rgba(72,215,255,0.06); border: 1px solid var(--panel-border);
- color: var(--text-main); font-size: 13px; outline: none;
- appearance: none; cursor: pointer;
- }
- .tree-container {
- flex: 1; overflow-y: auto; padding: 6px;
- }
- .tree-container::-webkit-scrollbar { width: 4px; }
- .tree-container::-webkit-scrollbar-thumb { background: var(--panel-border); border-radius: 2px; }
- .tree-node {
- padding: 4px 0; cursor: pointer; user-select: none;
- }
- .tree-node-content {
- display: flex; align-items: center; gap: 6px; padding: 5px 8px; border-radius: 4px;
- font-size: 13px; transition: all 0.2s;
- }
- .tree-node-content:hover { background: rgba(72,215,255,0.08); }
- .tree-node-content.selected { background: rgba(72,215,255,0.15); color: var(--accent); }
- .tree-arrow { display: inline-block; width: 14px; font-size: 10px; color: var(--text-sub); transition: transform 0.2s; }
- .tree-arrow.expanded { transform: rotate(90deg); }
- .tree-children { padding-left: 18px; display: none; }
- .tree-children.open { display: block; }
- .video-breadcrumb {
- font-size: 14px; color: var(--text-sub); margin-bottom: 10px; padding: 4px 0;
- border-bottom: 1px solid rgba(72,215,255,0.1);
- position: relative; z-index: 1;
- }
- .video-grid {
- flex: 1; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr);
- gap: 8px; min-height: 0;
- position: relative; z-index: 1;
- }
- .video-cell {
- position: relative; overflow: hidden;
- display: flex; align-items: center; justify-content: center;
- }
- /* Sci-fi outer frame */
- .cam-frame {
- position: relative; width: 100%; height: 100%; border-radius: 6px; padding: 4px;
- background: linear-gradient(145deg, rgba(72,215,255,0.35), rgba(58,123,255,0.1)), rgba(4,14,35,0.95);
- box-shadow: inset 0 0 18px rgba(72,215,255,0.2), 0 0 20px rgba(58,123,255,0.15);
- }
- .cam-frame::before {
- content: ''; position: absolute; inset: 0; border-radius: 6px; pointer-events: none;
- border: 1px solid rgba(100,210,255,0.55);
- clip-path: polygon(
- 0 0, 18px 0, 18px 2px, calc(100% - 18px) 2px, calc(100% - 18px) 0, 100% 0,
- 100% 18px, calc(100% - 2px) 18px, calc(100% - 2px) calc(100% - 18px), 100% calc(100% - 18px), 100% 100%,
- calc(100% - 18px) 100%, calc(100% - 18px) calc(100% - 2px), 18px calc(100% - 2px), 18px 100%, 0 100%,
- 0 calc(100% - 18px), 2px calc(100% - 18px), 2px 18px, 0 18px
- );
- }
- .cam-frame::after {
- content: ''; position: absolute; inset: 2px; border-radius: 5px; pointer-events: none;
- border: 1px solid rgba(120,220,255,0.18);
- }
- /* Corner glow accents */
- .cam-corner {
- position: absolute; width: 10px; height: 10px; z-index: 2;
- }
- .cam-corner::before, .cam-corner::after {
- content: ''; position: absolute; background: var(--accent);
- box-shadow: 0 0 6px var(--accent), 0 0 12px rgba(72,215,255,0.4);
- }
- .cam-corner.c-tl { top: 2px; left: 2px; }
- .cam-corner.c-tl::before { width: 14px; height: 2px; top: 0; left: 0; }
- .cam-corner.c-tl::after { width: 2px; height: 14px; top: 0; left: 0; }
- .cam-corner.c-tr { top: 2px; right: 2px; }
- .cam-corner.c-tr::before { width: 14px; height: 2px; top: 0; right: 0; }
- .cam-corner.c-tr::after { width: 2px; height: 14px; top: 0; right: 0; }
- .cam-corner.c-bl { bottom: 2px; left: 2px; }
- .cam-corner.c-bl::before { width: 14px; height: 2px; bottom: 0; left: 0; }
- .cam-corner.c-bl::after { width: 2px; height: 14px; bottom: 0; left: 0; }
- .cam-corner.c-br { bottom: 2px; right: 2px; }
- .cam-corner.c-br::before { width: 14px; height: 2px; bottom: 0; right: 0; }
- .cam-corner.c-br::after { width: 2px; height: 14px; bottom: 0; right: 0; }
- /* Inner camera view */
- .cam-inner {
- width: 100%; height: 100%; border-radius: 4px; overflow: hidden; position: relative;
- background: radial-gradient(ellipse 120px 80px at 20% 25%, rgba(72,180,255,0.2), transparent 70%),
- linear-gradient(150deg, rgba(8,30,65,0.95), rgba(4,15,35,0.92));
- }
- .cam-inner::after {
- content: ''; position: absolute; inset: 0; pointer-events: none;
- background: repeating-linear-gradient(
- to bottom, rgba(200,246,255,0.03) 0, rgba(200,246,255,0.03) 2px, transparent 2px, transparent 5px
- );
- }
- .cam-inner .cam-label {
- position: absolute; top: 6px; left: 8px; font-size: 11px;
- background: rgba(0,0,0,0.55); padding: 2px 8px; border-radius: 2px; color: var(--text-sub); z-index: 1;
- }
- .cam-inner .cam-placeholder {
- position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
- font-size: 12px; color: var(--text-sub); opacity: 0.4;
- }
- .cam-inner .cam-dot {
- display: inline-block; width: 6px; height: 6px; border-radius: 50%; margin-right: 4px;
- background: #18ff6f; box-shadow: 0 0 6px #18ff6f;
- }
- /* AI camera special style */
- .video-cell.ai-cam .cam-frame {
- background: linear-gradient(145deg, rgba(255,176,32,0.3), rgba(255,120,0,0.1)), rgba(4,14,35,0.95);
- box-shadow: inset 0 0 18px rgba(255,176,32,0.2), 0 0 20px rgba(255,140,0,0.15);
- }
- .video-cell.ai-cam .cam-frame::before {
- border-color: rgba(255,190,80,0.55);
- }
- .video-cell.ai-cam .cam-corner::before, .video-cell.ai-cam .cam-corner::after {
- background: var(--warn);
- box-shadow: 0 0 6px var(--warn), 0 0 12px rgba(255,176,32,0.4);
- }
- .video-cell.ai-cam .cam-inner .cam-label { background: rgba(255,176,32,0.3); color: var(--warn); }
- .ai-badge {
- position: absolute; top: 8px; right: 10px; font-size: 10px; z-index: 2;
- background: linear-gradient(135deg, var(--warn), #ff6b00); color: #fff;
- padding: 2px 10px; border-radius: 10px; font-weight: 600;
- box-shadow: 0 0 10px rgba(255,176,32,0.4);
- }
- .video-pagination {
- display: flex; justify-content: center; align-items: center; gap: 12px; margin-top: 10px;
- position: relative; z-index: 1;
- }
- .page-btn {
- padding: 6px 20px; border-radius: 4px; cursor: pointer;
- background: rgba(72,215,255,0.08); border: 1px solid var(--panel-border);
- color: var(--text-main); font-size: 13px; transition: all 0.3s;
- }
- .page-btn:hover { background: rgba(72,215,255,0.15); border-color: var(--accent); }
- .page-info { font-size: 13px; color: var(--text-sub); }
- /* ======================== Alert Modal ======================== */
- .alert-overlay {
- position: fixed; top: 0; left: 0; width: 100%; height: 100%;
- background: rgba(255,0,0,0.08); z-index: 9999;
- display: none; align-items: center; justify-content: center;
- animation: alertFlash 1.5s ease-in-out infinite;
- }
- .alert-overlay.show { display: flex; }
- /* Screen edge red glow */
- .alert-overlay::before {
- content: ''; position: absolute; inset: 0; pointer-events: none; z-index: -1;
- box-shadow:
- inset 0 0 80px 30px rgba(255,0,0,0.35),
- inset 0 0 200px 60px rgba(255,0,0,0.15);
- animation: edgeRedGlow 1s ease-in-out infinite;
- }
- @keyframes edgeRedGlow {
- 0%, 100% {
- box-shadow:
- inset 0 0 60px 20px rgba(255,0,0,0.25),
- inset 0 0 150px 40px rgba(255,0,0,0.1);
- }
- 50% {
- box-shadow:
- inset 0 0 120px 50px rgba(255,0,0,0.5),
- inset 0 0 300px 80px rgba(255,0,0,0.25);
- }
- }
- /* Four edge red light bars */
- .alert-overlay::after {
- content: ''; position: absolute; inset: 0; pointer-events: none; z-index: -1;
- border: 3px solid transparent;
- animation: edgeBorderFlash 0.8s ease-in-out infinite;
- }
- @keyframes edgeBorderFlash {
- 0%, 100% { border-color: rgba(255,50,50,0.15); }
- 50% { border-color: rgba(255,50,50,0.6); }
- }
- @keyframes alertFlash {
- 0%, 100% { background: rgba(255,0,0,0.04); }
- 50% { background: rgba(255,0,0,0.12); }
- }
- .alert-modal {
- width: 900px; padding: 0; border-radius: 8px; overflow: hidden;
- border: 2px solid var(--danger);
- box-shadow: 0 0 60px rgba(255,77,79,0.4), 0 0 120px rgba(255,77,79,0.2);
- animation: alertModalPulse 2s ease-in-out infinite;
- }
- @keyframes alertModalPulse {
- 0%, 100% { box-shadow: 0 0 40px rgba(255,77,79,0.3), 0 0 80px rgba(255,77,79,0.15); }
- 50% { box-shadow: 0 0 80px rgba(255,77,79,0.5), 0 0 160px rgba(255,77,79,0.25); }
- }
- .alert-header {
- background: linear-gradient(135deg, #cc0000, #ff4d4f); padding: 16px 24px;
- display: flex; align-items: center; gap: 12px;
- }
- .alert-header .alert-icon { font-size: 28px; animation: spin 2s linear infinite; }
- @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
- .alert-header .alert-title { font-size: 20px; font-weight: 700; letter-spacing: 4px; color: #fff; }
- .alert-body {
- background: linear-gradient(180deg, #1a0808, #0d0404); padding: 24px;
- display: flex; gap: 20px;
- }
- .alert-info {
- flex: 1; min-width: 0;
- }
- .alert-video {
- width: 320px; flex-shrink: 0; display: flex; flex-direction: column; gap: 8px;
- }
- .alert-video-title {
- font-size: 13px; color: var(--danger); font-weight: 600; letter-spacing: 1px;
- display: flex; align-items: center; gap: 6px;
- }
- .alert-video-title::before {
- content: ''; width: 6px; height: 6px; border-radius: 50%;
- background: var(--danger); box-shadow: 0 0 8px var(--danger);
- animation: dotBlink 1.5s ease-in-out infinite;
- }
- .alert-video-feed {
- flex: 1; min-height: 180px; border-radius: 6px; position: relative; overflow: hidden;
- background: radial-gradient(ellipse 100px 60px at 20% 25%, rgba(255,77,79,0.15), transparent 70%),
- linear-gradient(150deg, rgba(15,5,5,0.95), rgba(8,2,2,0.92));
- border: 1px solid rgba(255,77,79,0.3);
- box-shadow: inset 0 0 20px rgba(255,77,79,0.1), 0 0 12px rgba(255,77,79,0.08);
- }
- .alert-video-feed::after {
- content: ''; position: absolute; inset: 0; pointer-events: none;
- background: repeating-linear-gradient(
- to bottom, rgba(255,200,200,0.03) 0, rgba(255,200,200,0.03) 2px, transparent 2px, transparent 5px
- );
- }
- .alert-video-feed .cam-label {
- position: absolute; top: 8px; left: 10px; font-size: 11px;
- background: rgba(255,0,0,0.4); padding: 2px 10px; border-radius: 2px;
- color: #fff; z-index: 1;
- display: flex; align-items: center; gap: 4px;
- }
- .alert-video-feed .cam-label .rec-dot {
- width: 6px; height: 6px; border-radius: 50%;
- background: #ff4d4f; box-shadow: 0 0 6px #ff4d4f;
- animation: dotBlink 1s ease-in-out infinite;
- }
- .alert-video-feed .cam-placeholder {
- position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
- font-size: 13px; color: rgba(255,255,255,0.3);
- }
- .alert-info-row { display: flex; margin-bottom: 10px; font-size: 14px; }
- .alert-info-row .a-label { width: 105px; color: var(--text-sub); flex-shrink: 0; }
- .alert-info-row .a-value { color: #fff; font-weight: 600; }
- .alert-info-row .a-value.danger { color: var(--danger); }
- .alert-footer {
- padding: 16px 24px; text-align: center;
- background: #0d0404; border-top: 1px solid rgba(255,77,79,0.2);
- }
- .alert-close-btn {
- padding: 8px 40px; border-radius: 4px; cursor: pointer;
- background: linear-gradient(135deg, #cc0000, #ff4d4f); border: none;
- color: #fff; font-size: 14px; font-weight: 600; letter-spacing: 2px;
- transition: all 0.3s;
- }
- .alert-close-btn:hover { box-shadow: 0 0 20px rgba(255,77,79,0.5); }
- /* ======================== Decorations ======================== */
- .corner-deco {
- position: absolute; width: 14px; height: 14px;
- border-color: var(--accent); border-style: solid;
- opacity: 0.7; z-index: 2;
- animation: cornerBreathe 3s ease-in-out infinite;
- }
- .corner-deco.tl { top: -1px; left: -1px; border-width: 2px 0 0 2px; }
- .corner-deco.tr { top: -1px; right: -1px; border-width: 2px 2px 0 0; animation-delay: 0.5s; }
- .corner-deco.bl { bottom: -1px; left: -1px; border-width: 0 0 2px 2px; animation-delay: 1s; }
- .corner-deco.br { bottom: -1px; right: -1px; border-width: 0 2px 2px 0; animation-delay: 1.5s; }
- @keyframes cornerBreathe {
- 0%, 100% { opacity: 0.4; box-shadow: 0 0 4px rgba(72,215,255,0.2); }
- 50% { opacity: 1; box-shadow: 0 0 12px rgba(72,215,255,0.6), 0 0 24px rgba(72,215,255,0.2); }
- }
- /* ======================== Background Effects ======================== */
- /* Tech grid overlay */
- .screen::before {
- content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 0;
- background-image:
- linear-gradient(rgba(72,180,255,0.04) 1px, transparent 1px),
- linear-gradient(90deg, rgba(72,180,255,0.04) 1px, transparent 1px);
- background-size: 60px 60px;
- mask-image: radial-gradient(ellipse 70% 60% at 50% 50%, rgba(0,0,0,0.5) 0%, transparent 100%);
- }
- /* Ambient floating particles */
- .screen::after {
- content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 1;
- background:
- radial-gradient(1.5px 1.5px at 15% 25%, rgba(72,215,255,0.5), transparent),
- radial-gradient(1px 1px at 85% 15%, rgba(72,215,255,0.4), transparent),
- radial-gradient(1.5px 1.5px at 45% 80%, rgba(58,123,255,0.4), transparent),
- radial-gradient(1px 1px at 70% 55%, rgba(72,215,255,0.3), transparent),
- radial-gradient(1.5px 1.5px at 25% 60%, rgba(100,200,255,0.35), transparent),
- radial-gradient(1px 1px at 90% 75%, rgba(72,215,255,0.3), transparent);
- animation: particleDrift 20s ease-in-out infinite alternate;
- }
- @keyframes particleDrift {
- 0% { transform: translate(0, 0); opacity: 0.6; }
- 50% { transform: translate(-8px, 6px); opacity: 1; }
- 100% { transform: translate(5px, -4px); opacity: 0.7; }
- }
- /* ======================== Panel Animations ======================== */
- /* Panel border breathing glow */
- .panel {
- border: 1px solid var(--panel-border);
- animation: panelGlow 4s ease-in-out infinite;
- transition: box-shadow 0.4s ease;
- }
- @keyframes panelGlow {
- 0%, 100% { box-shadow: inset 0 0 15px rgba(72,180,255,0.03), 0 0 8px rgba(72,180,255,0.05); }
- 50% { box-shadow: inset 0 0 20px rgba(72,180,255,0.08), 0 0 16px rgba(72,180,255,0.1); }
- }
- .panel:hover {
- box-shadow: inset 0 0 25px rgba(72,180,255,0.12), 0 0 24px rgba(72,180,255,0.15) !important;
- }
- /* Panel top scan line */
- .panel::after {
- content: ''; position: absolute; top: 0; left: -100%; width: 60%; height: 1px;
- background: linear-gradient(90deg, transparent, rgba(72,215,255,0.6), rgba(72,215,255,0.8), rgba(72,215,255,0.6), transparent);
- animation: panelScan 6s linear infinite;
- pointer-events: none; z-index: 3;
- }
- @keyframes panelScan {
- 0% { left: -60%; }
- 100% { left: 160%; }
- }
- /* Stagger scan animation per panel */
- .col-left .panel:nth-child(2)::after { animation-delay: 1s; }
- .col-left .panel:nth-child(3)::after { animation-delay: 2s; }
- .col-center .panel:nth-child(2)::after { animation-delay: 3s; }
- .col-right .panel:nth-child(1)::after { animation-delay: 1.5s; }
- .col-right .panel:nth-child(2)::after { animation-delay: 3.5s; }
- /* ======================== Top Nav Animations ======================== */
- /* Nav bottom edge flowing light */
- .top-nav::before {
- content: ''; position: absolute; bottom: -1px; left: 0; width: 120px; height: 2px;
- background: linear-gradient(90deg, transparent, var(--accent), rgba(72,215,255,0.8), transparent);
- animation: navFlow 4s linear infinite;
- z-index: 101;
- }
- @keyframes navFlow {
- 0% { left: -120px; }
- 100% { left: calc(100% + 120px); }
- }
- /* Title text glow pulse */
- .nav-title {
- animation: titleGlow 3s ease-in-out infinite;
- }
- @keyframes titleGlow {
- 0%, 100% { filter: drop-shadow(0 0 6px rgba(72,215,255,0.2)); }
- 50% { filter: drop-shadow(0 0 16px rgba(72,215,255,0.5)); }
- }
- /* Clock pulse */
- .nav-right .clock {
- animation: clockPulse 2s ease-in-out infinite;
- }
- @keyframes clockPulse {
- 0%, 100% { text-shadow: 0 0 6px rgba(72,215,255,0.3); }
- 50% { text-shadow: 0 0 14px rgba(72,215,255,0.6), 0 0 28px rgba(72,215,255,0.2); }
- }
- /* Active tab glow */
- .nav-tab.active {
- animation: tabGlow 2.5s ease-in-out infinite;
- }
- @keyframes tabGlow {
- 0%, 100% { box-shadow: 0 0 10px rgba(72,215,255,0.15); }
- 50% { box-shadow: 0 0 22px rgba(72,215,255,0.35), inset 0 0 10px rgba(72,215,255,0.08); }
- }
- /* ======================== Data Card Animations ======================== */
- /* Status cards breathing */
- .status-card {
- transition: all 0.3s ease;
- animation: cardBreathe 4s ease-in-out infinite;
- }
- .status-card:nth-child(2) { animation-delay: 1.3s; }
- .status-card:nth-child(3) { animation-delay: 2.6s; }
- @keyframes cardBreathe {
- 0%, 100% { border-color: rgba(72,215,255,0.12); box-shadow: 0 0 0 transparent; }
- 50% { border-color: rgba(72,215,255,0.3); box-shadow: 0 0 10px rgba(72,215,255,0.08); }
- }
- .status-card:hover {
- transform: translateY(-2px);
- border-color: var(--accent) !important;
- box-shadow: 0 4px 16px rgba(72,215,255,0.15) !important;
- }
- /* Flip digit glow */
- .flip-digit {
- animation: digitGlow 2s ease-in-out infinite alternate;
- transition: all 0.3s ease;
- }
- @keyframes digitGlow {
- 0% { box-shadow: 0 0 4px rgba(72,215,255,0.1); text-shadow: 0 0 4px rgba(72,215,255,0.3); }
- 100% { box-shadow: 0 0 10px rgba(72,215,255,0.25); text-shadow: 0 0 12px rgba(72,215,255,0.5); }
- }
- /* Device type item hover */
- .device-type-item {
- transition: all 0.3s ease;
- }
- .device-type-item:hover {
- border-color: rgba(72,215,255,0.35);
- background: rgba(72,215,255,0.08);
- box-shadow: 0 0 12px rgba(72,215,255,0.1);
- transform: translateX(3px);
- }
- /* Equipment status cards */
- .equip-status-card, .equip-mid-item, .device-stat-card {
- transition: all 0.3s ease;
- }
- .equip-status-card:hover, .equip-mid-item:hover, .device-stat-card:hover {
- border-color: rgba(72,215,255,0.3);
- box-shadow: 0 0 12px rgba(72,215,255,0.1);
- transform: translateY(-2px);
- }
- /* ======================== Sensor & Warning Animations ======================== */
- /* Sensor item enter animation */
- .sensor-item {
- animation: sensorFadeIn 0.5s ease-out;
- }
- @keyframes sensorFadeIn {
- from { opacity: 0; transform: translateY(8px); }
- to { opacity: 1; transform: translateY(0); }
- }
- /* Warning item left border pulse */
- .warning-item {
- transition: all 0.3s ease;
- animation: warnBorderPulse 3s ease-in-out infinite;
- }
- @keyframes warnBorderPulse {
- 0%, 100% { border-left-color: rgba(255,176,32,0.6); }
- 50% { border-left-color: var(--warn); box-shadow: -2px 0 8px rgba(255,176,32,0.15); }
- }
- .warning-item:hover {
- background: rgba(255,176,32,0.08);
- transform: translateX(3px);
- }
- /* Warning count glow */
- .warning-count {
- animation: warnCountGlow 2s ease-in-out infinite;
- }
- @keyframes warnCountGlow {
- 0%, 100% { text-shadow: 0 0 8px rgba(255,176,32,0.3); }
- 50% { text-shadow: 0 0 20px rgba(255,176,32,0.6), 0 0 40px rgba(255,176,32,0.2); }
- }
- /* ======================== Camera Frame Animations ======================== */
- /* Corner glow breathing */
- .cam-corner::before, .cam-corner::after {
- animation: camCornerGlow 2.5s ease-in-out infinite;
- }
- .cam-corner.c-tr::before, .cam-corner.c-tr::after { animation-delay: 0.6s; }
- .cam-corner.c-bl::before, .cam-corner.c-bl::after { animation-delay: 1.2s; }
- .cam-corner.c-br::before, .cam-corner.c-br::after { animation-delay: 1.8s; }
- @keyframes camCornerGlow {
- 0%, 100% { opacity: 0.5; box-shadow: 0 0 4px var(--accent); }
- 50% { opacity: 1; box-shadow: 0 0 10px var(--accent), 0 0 20px rgba(72,215,255,0.3); }
- }
- /* Camera frame border breathing */
- .cam-frame {
- background:
- linear-gradient(rgba(4,14,35,0.96), rgba(4,14,35,0.96)) padding-box,
- linear-gradient(135deg,
- rgba(72,215,255,0.2) 0%,
- rgba(72,215,255,0.6) 18%,
- rgba(130,235,255,0.9) 34%,
- rgba(72,215,255,0.45) 52%,
- rgba(58,123,255,0.28) 72%,
- rgba(72,215,255,0.18) 100%
- ) border-box;
- background-size: 100% 100%, 240% 240%;
- animation: camFrameBreathe 4s ease-in-out infinite, camFrameBorderFlow 3.9s linear infinite;
- }
- @keyframes camFrameBreathe {
- 0%, 100% { box-shadow: inset 0 0 14px rgba(72,215,255,0.15), 0 0 14px rgba(58,123,255,0.1); }
- 50% { box-shadow: inset 0 0 22px rgba(72,215,255,0.3), 0 0 24px rgba(58,123,255,0.2); }
- }
- @keyframes camFrameBorderFlow {
- 0% { background-position: 0 0, 0% 50%; }
- 100% { background-position: 0 0, 220% 50%; }
- }
- /* Camera online dot blink */
- .cam-inner .cam-dot {
- animation: dotBlink 2s ease-in-out infinite;
- }
- @keyframes dotBlink {
- 0%, 100% { opacity: 1; box-shadow: 0 0 4px #18ff6f; }
- 50% { opacity: 0.4; box-shadow: 0 0 8px #18ff6f, 0 0 16px rgba(24,255,111,0.3); }
- }
- /* AI badge pulse */
- .ai-badge {
- animation: aiBadgePulse 1.5s ease-in-out infinite;
- }
- @keyframes aiBadgePulse {
- 0%, 100% { box-shadow: 0 0 8px rgba(255,176,32,0.3); }
- 50% { box-shadow: 0 0 16px rgba(255,176,32,0.6), 0 0 32px rgba(255,176,32,0.2); transform: scale(1.05); }
- }
- /* AI camera frame special breathing */
- .video-cell.ai-cam .cam-frame {
- background:
- linear-gradient(rgba(4,14,35,0.96), rgba(4,14,35,0.96)) padding-box,
- linear-gradient(135deg,
- rgba(255,176,32,0.22) 0%,
- rgba(255,176,32,0.62) 20%,
- rgba(255,210,120,0.9) 36%,
- rgba(255,176,32,0.45) 58%,
- rgba(255,120,0,0.25) 78%,
- rgba(255,176,32,0.22) 100%
- ) border-box;
- background-size: 100% 100%, 240% 240%;
- animation: aiFrameBreathe 3s ease-in-out infinite, aiFrameBorderFlow 2.9s linear infinite;
- }
- @keyframes aiFrameBreathe {
- 0%, 100% { box-shadow: inset 0 0 14px rgba(255,176,32,0.15), 0 0 14px rgba(255,140,0,0.1); }
- 50% { box-shadow: inset 0 0 24px rgba(255,176,32,0.3), 0 0 28px rgba(255,140,0,0.2); }
- }
- @keyframes aiFrameBorderFlow {
- 0% { background-position: 0 0, 0% 50%; }
- 100% { background-position: 0 0, 240% 50%; }
- }
- /* ======================== Page Button Animations ======================== */
- .page-btn {
- transition: all 0.3s ease;
- position: relative; overflow: hidden;
- }
- .page-btn::after {
- content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
- background: linear-gradient(90deg, transparent, rgba(72,215,255,0.15), transparent);
- transition: left 0.5s ease;
- }
- .page-btn:hover::after { left: 100%; }
- .page-btn:hover {
- transform: translateY(-1px);
- box-shadow: 0 4px 12px rgba(72,215,255,0.15);
- }
- /* ======================== Panel Title Glow ======================== */
- .panel-title::before {
- animation: titleBarGlow 2.5s ease-in-out infinite;
- }
- @keyframes titleBarGlow {
- 0%, 100% { box-shadow: 0 0 6px var(--accent); opacity: 0.7; }
- 50% { box-shadow: 0 0 14px var(--accent), 0 0 24px rgba(72,215,255,0.3); opacity: 1; }
- }
- /* ======================== Alarm Icon Enhanced ======================== */
- .alarm-icon {
- animation: alarmIconShake 0.5s ease-in-out infinite alternate;
- }
- @keyframes alarmIconShake {
- 0% { transform: translateX(-1px) rotate(-5deg); }
- 100% { transform: translateX(1px) rotate(5deg); }
- }
- /* ================================================================ */
- /* ============= VIDEO MONITORING PAGE ANIMATIONS ================= */
- /* ================================================================ */
- /* --- Video page entrance fade-in --- */
- #page-video.active {
- animation: videoPageFadeIn 0.6s ease-out;
- }
- @keyframes videoPageFadeIn {
- from { opacity: 0; transform: translateY(8px); }
- to { opacity: 1; transform: translateY(0); }
- }
- /* --- Video-left sidebar panel effects --- */
- .video-left {
- position: relative;
- }
- /* Sidebar vertical scan line */
- .video-left::after {
- content: ''; position: absolute; left: 0; top: -100%; width: 1px; height: 60%;
- background: linear-gradient(180deg, transparent, rgba(72,215,255,0.7), rgba(72,215,255,0.9), rgba(72,215,255,0.7), transparent);
- animation: sidebarScan 5s linear infinite;
- pointer-events: none; z-index: 3;
- }
- @keyframes sidebarScan {
- 0% { top: -60%; }
- 100% { top: 160%; }
- }
- /* Sidebar left edge glow */
- .video-left::before {
- content: ''; position: absolute; left: 0; top: 10%; bottom: 10%; width: 2px;
- background: linear-gradient(180deg, transparent, var(--accent), transparent);
- opacity: 0.4;
- animation: sidebarEdgeGlow 3s ease-in-out infinite;
- z-index: 3; pointer-events: none;
- }
- @keyframes sidebarEdgeGlow {
- 0%, 100% { opacity: 0.25; box-shadow: 0 0 4px var(--accent); }
- 50% { opacity: 0.7; box-shadow: 0 0 12px var(--accent), 0 0 24px rgba(72,215,255,0.2); }
- }
- /* --- Search input effects --- */
- .video-search {
- transition: all 0.3s ease;
- position: relative;
- }
- .video-search:focus {
- border-color: var(--accent);
- box-shadow: 0 0 12px rgba(72,215,255,0.2), inset 0 0 8px rgba(72,215,255,0.06);
- background: rgba(72,215,255,0.1);
- }
- /* Search input idle glow */
- .video-search {
- animation: searchIdleGlow 4s ease-in-out infinite;
- }
- @keyframes searchIdleGlow {
- 0%, 100% { box-shadow: 0 0 0 transparent; }
- 50% { box-shadow: 0 0 6px rgba(72,215,255,0.1); }
- }
- /* --- Filter dropdown effects --- */
- .video-filter {
- transition: all 0.3s ease;
- animation: filterIdleGlow 4s ease-in-out infinite 1s;
- }
- @keyframes filterIdleGlow {
- 0%, 100% { box-shadow: 0 0 0 transparent; }
- 50% { box-shadow: 0 0 6px rgba(72,215,255,0.1); }
- }
- .video-filter:focus {
- border-color: var(--accent);
- box-shadow: 0 0 12px rgba(72,215,255,0.2), inset 0 0 8px rgba(72,215,255,0.06);
- background: rgba(72,215,255,0.1);
- }
- /* --- Tree node animations --- */
- .tree-node-content {
- transition: all 0.3s ease;
- position: relative;
- overflow: hidden;
- }
- /* Tree node hover light sweep */
- .tree-node-content::after {
- content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
- background: linear-gradient(90deg, transparent, rgba(72,215,255,0.08), transparent);
- transition: left 0.6s ease;
- pointer-events: none;
- }
- .tree-node-content:hover::after {
- left: 100%;
- }
- .tree-node-content:hover {
- box-shadow: 0 0 8px rgba(72,215,255,0.08);
- text-shadow: 0 0 6px rgba(72,215,255,0.2);
- }
- /* Tree selected node glow pulse */
- .tree-node-content.selected {
- animation: treeSelectedGlow 2.5s ease-in-out infinite;
- border-left: 2px solid var(--accent);
- }
- @keyframes treeSelectedGlow {
- 0%, 100% { box-shadow: 0 0 6px rgba(72,215,255,0.1); background: rgba(72,215,255,0.12); }
- 50% { box-shadow: 0 0 14px rgba(72,215,255,0.2); background: rgba(72,215,255,0.2); }
- }
- /* Tree children expand animation */
- .tree-children.open {
- animation: treeExpand 0.35s ease-out;
- }
- @keyframes treeExpand {
- from { opacity: 0; max-height: 0; transform: translateY(-6px); }
- to { opacity: 1; max-height: 600px; transform: translateY(0); }
- }
- /* Tree arrow rotation glow */
- .tree-arrow {
- transition: all 0.3s ease;
- }
- .tree-arrow.expanded {
- color: var(--accent);
- text-shadow: 0 0 6px rgba(72,215,255,0.4);
- }
- /* --- Video breadcrumb flowing light --- */
- .video-breadcrumb {
- position: relative; overflow: hidden;
- transition: all 0.3s ease;
- }
- .video-breadcrumb::before {
- content: ''; position: absolute; bottom: 0; left: -80px; width: 80px; height: 1px;
- background: linear-gradient(90deg, transparent, var(--accent), rgba(72,215,255,0.8), transparent);
- animation: breadcrumbFlow 5s linear infinite;
- pointer-events: none;
- }
- @keyframes breadcrumbFlow {
- 0% { left: -80px; }
- 100% { left: calc(100% + 80px); }
- }
- /* Breadcrumb text subtle glow */
- .video-breadcrumb {
- animation: breadcrumbTextGlow 3s ease-in-out infinite;
- }
- @keyframes breadcrumbTextGlow {
- 0%, 100% { text-shadow: none; color: var(--text-sub); }
- 50% { text-shadow: 0 0 8px rgba(72,215,255,0.2); color: #a0d4e8; }
- }
- /* --- Video grid container effects --- */
- .video-grid {
- position: relative;
- border: 1px solid rgba(72,215,255,0.08);
- border-radius: 6px;
- padding: 6px;
- animation: videoGridBreathe 5s ease-in-out infinite;
- }
- @keyframes videoGridBreathe {
- 0%, 100% { border-color: rgba(72,215,255,0.06); box-shadow: 0 0 0 transparent; }
- 50% { border-color: rgba(72,215,255,0.18); box-shadow: 0 0 16px rgba(72,215,255,0.06), inset 0 0 20px rgba(72,215,255,0.03); }
- }
- /* Grid background tech pattern */
- .video-grid::before {
- content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 0; border-radius: 6px;
- background: repeating-linear-gradient(
- 0deg, rgba(72,215,255,0.015) 0, rgba(72,215,255,0.015) 1px, transparent 1px, transparent 60px
- ), repeating-linear-gradient(
- 90deg, rgba(72,215,255,0.015) 0, rgba(72,215,255,0.015) 1px, transparent 1px, transparent 60px
- );
- }
- /* Grid horizontal scan line */
- .video-grid::after {
- content: ''; position: absolute; left: 0; top: -100%; width: 100%; height: 2px;
- background: linear-gradient(90deg, transparent 5%, rgba(72,215,255,0.15) 20%, rgba(72,215,255,0.35) 50%, rgba(72,215,255,0.15) 80%, transparent 95%);
- animation: gridScanLine 8s linear infinite;
- pointer-events: none; z-index: 5;
- }
- @keyframes gridScanLine {
- 0% { top: -2px; }
- 100% { top: 100%; }
- }
- /* --- Camera cell staggered entrance --- */
- .video-cell {
- animation: camCellEntrance 0.5s ease-out both;
- }
- .video-cell:nth-child(1) { animation-delay: 0.05s; }
- .video-cell:nth-child(2) { animation-delay: 0.1s; }
- .video-cell:nth-child(3) { animation-delay: 0.15s; }
- .video-cell:nth-child(4) { animation-delay: 0.2s; }
- .video-cell:nth-child(5) { animation-delay: 0.25s; }
- .video-cell:nth-child(6) { animation-delay: 0.3s; }
- .video-cell:nth-child(7) { animation-delay: 0.35s; }
- .video-cell:nth-child(8) { animation-delay: 0.4s; }
- .video-cell:nth-child(9) { animation-delay: 0.45s; }
- @keyframes camCellEntrance {
- from { opacity: 0; transform: scale(0.92); }
- to { opacity: 1; transform: scale(1); }
- }
- /* --- Camera inner moving scanline --- */
- .cam-inner::before {
- content: ''; position: absolute; left: 0; width: 100%; height: 1px;
- background: linear-gradient(90deg, transparent 10%, rgba(72,215,255,0.12) 30%, rgba(72,215,255,0.22) 50%, rgba(72,215,255,0.12) 70%, transparent 90%);
- animation: camInnerScan 6s linear infinite;
- pointer-events: none; z-index: 2;
- }
- @keyframes camInnerScan {
- 0% { top: 0; }
- 100% { top: 100%; }
- }
- /* AI cam inner has orange scanline */
- .video-cell.ai-cam .cam-inner::before {
- background: linear-gradient(90deg, transparent 10%, rgba(255,176,32,0.12) 30%, rgba(255,176,32,0.25) 50%, rgba(255,176,32,0.12) 70%, transparent 90%);
- }
- /* --- Camera frame hover effect --- */
- .cam-frame {
- transition: box-shadow 0.3s ease, transform 0.3s ease;
- }
- .video-cell:hover .cam-frame {
- box-shadow: inset 0 0 28px rgba(72,215,255,0.35), 0 0 32px rgba(58,123,255,0.25) !important;
- transform: scale(1.01);
- }
- .video-cell.ai-cam:hover .cam-frame {
- box-shadow: inset 0 0 28px rgba(255,176,32,0.35), 0 0 32px rgba(255,140,0,0.25) !important;
- }
- /* --- Camera label typing glow --- */
- .cam-label {
- animation: camLabelGlow 3s ease-in-out infinite;
- }
- @keyframes camLabelGlow {
- 0%, 100% { box-shadow: none; }
- 50% { box-shadow: 0 0 8px rgba(0,0,0,0.3), 0 0 4px rgba(72,215,255,0.15); }
- }
- /* --- Video pagination effects --- */
- .video-pagination {
- position: relative;
- }
- .video-pagination::before {
- content: ''; position: absolute; top: 0; left: 20%; right: 20%; height: 1px;
- background: linear-gradient(90deg, transparent, rgba(72,215,255,0.2), transparent);
- animation: paginationLineGlow 3s ease-in-out infinite;
- }
- @keyframes paginationLineGlow {
- 0%, 100% { opacity: 0.3; }
- 50% { opacity: 1; }
- }
- .page-info {
- animation: pageInfoGlow 3s ease-in-out infinite;
- }
- @keyframes pageInfoGlow {
- 0%, 100% { text-shadow: none; }
- 50% { text-shadow: 0 0 8px rgba(72,215,255,0.3); color: #a0d4e8; }
- }
- /* Page button idle subtle pulse */
- .page-btn {
- animation: pageBtnPulse 4s ease-in-out infinite;
- }
- .page-btn:nth-child(3) { animation-delay: 2s; }
- @keyframes pageBtnPulse {
- 0%, 100% { border-color: var(--panel-border); }
- 50% { border-color: rgba(72,215,255,0.4); box-shadow: 0 0 8px rgba(72,215,255,0.08); }
- }
- /* --- Video-right background ambient particles --- */
- .video-right {
- position: relative;
- }
- .video-right::before {
- content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 0;
- background-image:
- radial-gradient(1px 1px at 10% 20%, rgba(72,215,255,0.3) 50%, transparent 100%),
- radial-gradient(1px 1px at 30% 65%, rgba(72,215,255,0.25) 50%, transparent 100%),
- radial-gradient(1.5px 1.5px at 55% 15%, rgba(72,215,255,0.2) 50%, transparent 100%),
- radial-gradient(1px 1px at 75% 80%, rgba(72,215,255,0.3) 50%, transparent 100%),
- radial-gradient(1px 1px at 90% 40%, rgba(72,215,255,0.25) 50%, transparent 100%),
- radial-gradient(1.5px 1.5px at 45% 90%, rgba(72,215,255,0.2) 50%, transparent 100%);
- animation: videoParticlesDrift 12s ease-in-out infinite alternate;
- }
- @keyframes videoParticlesDrift {
- 0% { opacity: 0.3; transform: translateY(0); }
- 50% { opacity: 0.7; }
- 100% { opacity: 0.3; transform: translateY(-10px); }
- }
- /* --- Tree container scroll bar glow --- */
- .tree-container::-webkit-scrollbar-thumb {
- box-shadow: 0 0 4px rgba(72,215,255,0.2);
- }
- .tree-container::-webkit-scrollbar-thumb:hover {
- background: var(--accent);
- box-shadow: 0 0 8px rgba(72,215,255,0.4);
- }
- /* ======================== Chemical Inventory Stats ======================== */
- .chem-panel-body {
- flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center;
- }
- .chem-stats-grid {
- width: 100%; display: grid; grid-template-columns: 1fr 1fr; gap: 10px; padding: 4px 0;
- }
- .chem-stat-item {
- display: flex; align-items: center; gap: 10px; padding: 10px 12px;
- background: rgba(72,215,255,0.04); border: 1px solid rgba(72,215,255,0.08);
- border-radius: 4px; transition: all 0.3s;
- }
- .chem-stat-item:hover {
- border-color: rgba(72,215,255,0.3); box-shadow: 0 0 10px rgba(72,215,255,0.08);
- transform: translateY(-1px);
- }
- .chem-ring-wrap {
- width: 48px; height: 48px; flex-shrink: 0; position: relative;
- }
- .chem-ring-wrap svg { width: 100%; height: 100%; }
- .chem-ring-bg { fill: none; stroke: rgba(72,215,255,0.1); stroke-width: 3; }
- .chem-ring-fg { fill: none; stroke-width: 3; stroke-linecap: round; transition: stroke-dashoffset 1s ease; }
- .chem-ring-icon { font-size: 16px; text-anchor: middle; dominant-baseline: central; }
- .chem-ring-pct { font-size: 10px; font-weight: 700; text-anchor: middle; dominant-baseline: central; }
- .chem-stat-info { min-width: 0; flex: 1; }
- .chem-stat-value { font-size: 18px; font-weight: 700; line-height: 1.2; }
- .chem-stat-label { font-size: 11px; color: var(--text-sub); margin-top: 2px; white-space: nowrap; }
- .chem-stat-pct { font-size: 11px; margin-top: 2px; }
- /* Warning item chemical violation style */
- .warning-item.chem-violation {
- border-left-color: #a78bfa;
- background: rgba(167,139,250,0.04);
- }
- .warning-item.chem-violation .w-sensor {
- color: #a78bfa;
- }
- @keyframes chemViolationPulse {
- 0%, 100% { border-left-color: rgba(167,139,250,0.6); }
- 50% { border-left-color: #a78bfa; box-shadow: -2px 0 8px rgba(167,139,250,0.15); }
- }
- .warning-item.chem-violation {
- animation: chemViolationPulse 3s ease-in-out infinite;
- }
- /* ======================== Evacuation Modal ======================== */
- .evac-overlay {
- position: fixed; top: 0; left: 0; width: 100%; height: 100%;
- background: rgba(0,0,0,0.85); z-index: 10000;
- display: none; align-items: center; justify-content: center;
- }
- .evac-overlay.show { display: flex; }
- .evac-modal {
- width: 1100px; height: 680px; border-radius: 8px; overflow: hidden;
- border: 1px solid rgba(72,180,255,0.3);
- background: linear-gradient(180deg, #0a1e3d, #061228);
- box-shadow: 0 0 60px rgba(72,180,255,0.15);
- display: flex; flex-direction: column;
- }
- .evac-header {
- display: flex; align-items: center; justify-content: space-between;
- padding: 14px 24px; border-bottom: 1px solid rgba(72,180,255,0.2);
- }
- .evac-header-left { display: flex; align-items: center; gap: 10px; }
- .evac-header-left .evac-icon { font-size: 22px; }
- .evac-header-left .evac-title { font-size: 20px; font-weight: 700; letter-spacing: 4px; color: #fff; }
- .evac-close {
- width: 30px; height: 30px; border-radius: 50%; cursor: pointer;
- background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.15);
- color: #fff; font-size: 18px; display: flex; align-items: center; justify-content: center;
- transition: all 0.3s;
- }
- .evac-close:hover { background: rgba(255,77,79,0.3); border-color: var(--danger); }
- .evac-body {
- flex: 1; display: flex; min-height: 0; padding: 12px 16px; gap: 14px;
- }
- .evac-main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
- .evac-legend {
- display: flex; align-items: center; gap: 20px; justify-content: flex-end;
- font-size: 12px; color: var(--text-sub); margin-bottom: 6px;
- }
- .evac-legend-item { display: flex; align-items: center; gap: 6px; }
- .evac-legend-line { width: 22px; height: 3px; border-radius: 2px; }
- .evac-floorplan {
- flex: 1; border: 1px solid rgba(72,180,255,0.15); border-radius: 6px;
- position: relative; overflow: hidden; background: rgba(6,22,56,0.6);
- }
- .evac-floorplan svg { width: 100%; height: 100%; }
- .evac-alert-card {
- position: absolute; left: 14px; bottom: 14px;
- background: rgba(20,5,5,0.92); border: 1px solid rgba(255,77,79,0.4);
- border-radius: 6px; padding: 10px 14px; max-width: 240px; z-index: 2;
- }
- .evac-alert-card .eac-header {
- display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; gap: 8px;
- }
- .evac-alert-card .eac-time { color: var(--text-sub); font-size: 11px; display: flex; align-items: center; gap: 4px; }
- .evac-alert-card .eac-time .eac-icon { color: var(--danger); }
- .evac-alert-card .eac-close-btn { cursor: pointer; color: var(--text-sub); font-size: 14px; background: none; border: none; }
- .evac-alert-card .eac-badge {
- display: inline-block; font-size: 10px; padding: 2px 8px; border-radius: 3px;
- background: rgba(255,77,79,0.2); color: var(--danger); border: 1px solid rgba(255,77,79,0.3);
- margin-bottom: 4px;
- }
- .evac-alert-card .eac-desc { color: #fff; font-size: 12px; }
- .evac-sidebar {
- width: 190px; display: flex; flex-direction: column; gap: 8px; flex-shrink: 0;
- }
- .evac-sidebar-block {
- background: rgba(72,215,255,0.04); border: 1px solid rgba(72,215,255,0.08);
- border-radius: 4px; padding: 10px; flex: 1; display: flex; flex-direction: column;
- }
- .evac-sidebar-title { font-size: 11px; color: var(--text-sub); margin-bottom: 4px; }
- .evac-sidebar-label { font-size: 13px; color: #fff; font-weight: 600; margin-bottom: 6px; }
- .evac-video-placeholder {
- flex: 1; min-height: 60px; border-radius: 4px;
- background: rgba(6,22,56,0.8); border: 1px solid rgba(72,180,255,0.1);
- display: flex; align-items: center; justify-content: center;
- font-size: 11px; color: var(--text-sub); opacity: 0.5;
- }
- .evac-footer {
- border-top: 1px solid rgba(72,180,255,0.15); padding: 12px 20px;
- display: flex; gap: 14px; align-items: flex-end;
- }
- .evac-metrics { display: flex; flex-direction: column; gap: 5px; min-width: 180px; }
- .evac-metric-row { display: flex; align-items: center; gap: 8px; font-size: 12px; }
- .evac-metric-label { color: var(--text-sub); }
- .evac-metric-value { color: var(--danger); font-weight: 600; }
- .evac-broadcast { flex: 1; display: flex; flex-direction: column; gap: 6px; }
- .evac-broadcast-header { display: flex; align-items: center; justify-content: space-between; }
- .evac-broadcast-title { font-size: 13px; color: #fff; display: flex; align-items: center; gap: 6px; }
- .evac-broadcast-hint { font-size: 11px; color: var(--text-sub); }
- .evac-speakers { display: flex; gap: 6px; }
- .evac-speaker-btn {
- flex: 1; padding: 7px 10px; border-radius: 4px; text-align: center;
- font-size: 11px; cursor: pointer; transition: all 0.3s;
- background: linear-gradient(135deg, rgba(72,215,255,0.15), rgba(58,123,255,0.1));
- border: 1px solid rgba(72,215,255,0.3); color: var(--accent);
- }
- .evac-speaker-btn:hover { background: linear-gradient(135deg, rgba(72,215,255,0.25), rgba(58,123,255,0.2)); }
- .evac-broadcast-input { display: flex; gap: 6px; }
- .evac-broadcast-input input {
- flex: 1; padding: 7px 12px; border-radius: 4px;
- background: rgba(72,215,255,0.06); border: 1px solid rgba(72,180,255,0.2);
- color: var(--text-main); font-size: 12px; outline: none;
- }
- .evac-broadcast-input input::placeholder { color: var(--text-sub); }
- .evac-broadcast-input button {
- padding: 7px 16px; border-radius: 4px; cursor: pointer;
- background: rgba(72,215,255,0.1); border: 1px solid rgba(72,215,255,0.3);
- color: var(--accent); font-size: 12px; transition: all 0.3s;
- }
- .evac-broadcast-input button:hover { background: rgba(72,215,255,0.2); }
- .evac-actions { display: flex; flex-direction: column; gap: 8px; min-width: 110px; }
- .evac-btn-later {
- padding: 10px 20px; border-radius: 4px; cursor: pointer;
- background: rgba(72,215,255,0.06); border: 1px solid rgba(72,215,255,0.2);
- color: var(--text-main); font-size: 13px; text-align: center; transition: all 0.3s;
- }
- .evac-btn-later:hover { background: rgba(72,215,255,0.12); }
- .evac-btn-exec {
- padding: 10px 20px; border-radius: 4px; cursor: pointer;
- background: linear-gradient(135deg, rgba(255,0,0,0.15), rgba(200,0,0,0.1));
- border: 1px solid rgba(255,77,79,0.5); color: var(--danger);
- font-size: 13px; font-weight: 600; text-align: center; transition: all 0.3s;
- }
- .evac-btn-exec:hover { background: linear-gradient(135deg, rgba(255,0,0,0.25), rgba(200,0,0,0.2)); box-shadow: 0 0 16px rgba(255,77,79,0.3); }
- .evac-route-arrow { opacity: 0; transition: opacity 0.5s; }
- .evac-route-arrow.visible { animation: evacArrowFlow 1.5s ease-in-out infinite; }
- @keyframes evacArrowFlow {
- 0% { opacity: 0.4; }
- 50% { opacity: 1; }
- 100% { opacity: 0.4; }
- }
- .evac-room-group { cursor: pointer; }
- .evac-room-group:hover rect { stroke: var(--accent); stroke-width: 2; fill: rgba(72,215,255,0.12); }
- .evac-room-group.selected rect { stroke: var(--accent); stroke-width: 2; fill: rgba(72,215,255,0.15); }
- .evac-room-group.alarm rect { stroke: rgba(255,77,79,0.7) !important; stroke-width: 2 !important; fill: rgba(255,77,79,0.1) !important; animation: evacRoomAlarm 1s ease-in-out infinite; }
- @keyframes evacRoomAlarm {
- 0%, 100% { stroke: rgba(255,77,79,0.5); fill: rgba(255,77,79,0.06); }
- 50% { stroke: rgba(255,77,79,0.9); fill: rgba(255,77,79,0.15); }
- }
- </style>
- </head>
- <body>
- <div class="viewport">
- <div class="screen" id="mainScreen">
- <!-- ===================== TOP NAV ===================== -->
- <div class="top-nav">
- <div class="nav-title">中国安全生产科学研究院实验室安全智慧化管控中心</div>
- <div class="nav-tabs">
- <div class="nav-tab active" onclick="switchPage('lab')">实验室情况</div>
- <div class="nav-tab" onclick="switchPage('video')">视频监控</div>
- </div>
- <div class="nav-right">
- <span class="weather">☁ 北京 · 晴 18°C</span>
- <span id="weekday"></span>
- <span class="clock" id="clock"></span>
- </div>
- </div>
- <!-- ===================== PAGE: 实验室情况 ===================== -->
- <div class="page active" id="page-lab">
- <!-- LEFT COLUMN -->
- <div class="col-left">
- <!-- 实验室基本情况统计 -->
- <div class="panel" style="height: 260px;">
- <div class="corner-deco tl"></div><div class="corner-deco tr"></div>
- <div class="corner-deco bl"></div><div class="corner-deco br"></div>
- <div class="panel-title">实验室基本情况统计</div>
- <div id="chartLabOverview" style="width:100%;height:150px;"></div>
- <div class="status-cards">
- <div class="status-card using">
- <div class="label">使用</div><div class="value">20<span style="font-size:12px;color:var(--text-sub)"> 间</span></div>
- </div>
- <div class="status-card error">
- <div class="label">异常</div><div class="value">3<span style="font-size:12px;color:var(--text-sub)"> 间</span></div>
- </div>
- <div class="status-card idle">
- <div class="label">空闲</div><div class="value">35<span style="font-size:12px;color:var(--text-sub)"> 间</span></div>
- </div>
- </div>
- </div>
- <!-- 实验室安全分级统计 -->
- <div class="panel" style="flex:1;display:flex;flex-direction:column;">
- <div class="corner-deco tl"></div><div class="corner-deco tr"></div>
- <div class="corner-deco bl"></div><div class="corner-deco br"></div>
- <div class="panel-title">实验室安全分级统计</div>
- <div id="chartGradeBar" style="width:100%;flex:1;min-height:0;"></div>
- </div>
- <!-- 实验室进入人数统计及走势 -->
- <div class="panel" style="height: 260px;">
- <div class="corner-deco tl"></div><div class="corner-deco tr"></div>
- <div class="corner-deco bl"></div><div class="corner-deco br"></div>
- <div class="panel-title">实验室进入人数统计及走势</div>
- <div class="flip-counter-row">
- <div class="flip-group">
- <div class="flip-label">今日总进入人数</div>
- <div class="flip-digits" id="flipTotal"></div>
- </div>
- <div class="flip-group">
- <div class="flip-label">当前正在实验人数</div>
- <div class="flip-digits" id="flipCurrent"></div>
- </div>
- </div>
- <div id="chartPeopleLine" style="width:100%;height:130px;"></div>
- </div>
- </div>
- <!-- CENTER COLUMN -->
- <div class="col-center">
- <!-- 实验室视频监控 (2x2) -->
- <div class="panel" style="flex:1;display:flex;flex-direction:column;">
- <div class="corner-deco tl"></div><div class="corner-deco tr"></div>
- <div class="corner-deco bl"></div><div class="corner-deco br"></div>
- <div class="panel-title">实验室视频监控</div>
- <div style="flex:1;display:grid;grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;gap:8px;min-height:0;">
- <div class="video-cell">
- <div class="cam-frame">
- <div class="cam-corner c-tl"></div><div class="cam-corner c-tr"></div>
- <div class="cam-corner c-bl"></div><div class="cam-corner c-br"></div>
- <div class="cam-inner">
- <span class="cam-label"><span class="cam-dot"></span>A101 走廊</span>
- <span class="cam-placeholder">📹 实时监控画面</span>
- </div>
- </div>
- </div>
- <div class="video-cell">
- <div class="cam-frame">
- <div class="cam-corner c-tl"></div><div class="cam-corner c-tr"></div>
- <div class="cam-corner c-bl"></div><div class="cam-corner c-br"></div>
- <div class="cam-inner">
- <span class="cam-label"><span class="cam-dot"></span>B201 走廊</span>
- <span class="cam-placeholder">📹 实时监控画面</span>
- </div>
- </div>
- </div>
- <div class="video-cell ai-cam">
- <div class="cam-frame">
- <div class="cam-corner c-tl"></div><div class="cam-corner c-tr"></div>
- <div class="cam-corner c-bl"></div><div class="cam-corner c-br"></div>
- <span class="ai-badge">AI 智能</span>
- <div class="cam-inner">
- <span class="cam-label"><span class="cam-dot"></span>🎯 化学实验室A101</span>
- <span class="cam-placeholder">📹 实时监控画面</span>
- </div>
- </div>
- </div>
- <div class="video-cell">
- <div class="cam-frame">
- <div class="cam-corner c-tl"></div><div class="cam-corner c-tr"></div>
- <div class="cam-corner c-bl"></div><div class="cam-corner c-br"></div>
- <div class="cam-inner">
- <span class="cam-label"><span class="cam-dot"></span>C102 走廊</span>
- <span class="cam-placeholder">📹 实时监控画面</span>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- Bottom row: 实验环境安全智能感知 + 实验室实时风险预警 -->
- <div style="height:320px;display:flex;gap:14px;flex-shrink:0;">
- <!-- 实验环境安全智能感知 -->
- <div class="panel" style="flex:1;display:flex;flex-direction:column;min-width:0;">
- <div class="corner-deco tl"></div><div class="corner-deco tr"></div>
- <div class="corner-deco bl"></div><div class="corner-deco br"></div>
- <div class="panel-title">实验环境安全智能感知</div>
- <div class="sensor-scroll-wrap">
- <div class="sensor-scroll-inner" id="sensorList"></div>
- </div>
- </div>
- <!-- 实验室实时风险预警 -->
- <div class="panel" style="flex:1;display:flex;flex-direction:column;min-width:0;">
- <div class="corner-deco tl"></div><div class="corner-deco tr"></div>
- <div class="corner-deco bl"></div><div class="corner-deco br"></div>
- <div class="panel-title">实验室实时风险预警</div>
- <div class="warning-header">
- <div class="warning-count" id="warningCount">12</div>
- <div class="warning-count-label">本月预警响应总数</div>
- </div>
- <div class="warning-scroll-wrap">
- <div class="warning-scroll-inner" id="warningList"></div>
- </div>
- </div>
- </div>
- </div>
- <!-- RIGHT COLUMN -->
- <div class="col-right">
- <!-- 智能环境感知应用设备统计 -->
- <div class="panel" style="flex:4;display:flex;flex-direction:column;">
- <div class="corner-deco tl"></div><div class="corner-deco tr"></div>
- <div class="corner-deco bl"></div><div class="corner-deco br"></div>
- <div class="panel-title">智能环境感知应用设备统计</div>
- <div class="device-top">
- <div class="device-stat-card">
- <div class="ds-label">在线设备</div><div class="ds-value">186</div>
- </div>
- <div class="device-stat-card offline">
- <div class="ds-label">离线设备</div><div class="ds-value">14</div>
- </div>
- </div>
- <div class="device-bottom">
- <div class="device-gauge-wrap" id="chartGauge"></div>
- <div class="device-type-list">
- <div class="device-type-item"><span class="dt-name">电子信息铭牌</span><span class="dt-value">58</span></div>
- <div class="device-type-item"><span class="dt-name">化学品智能终端</span><span class="dt-value">32</span></div>
- <div class="device-type-item"><span class="dt-name">传感器</span><span class="dt-value">76</span></div>
- <div class="device-type-item"><span class="dt-name">智能设备</span><span class="dt-value">34</span></div>
- </div>
- </div>
- </div>
- <!-- 实验室设备分类及使用统计 -->
- <div class="panel" style="flex:4;display:flex;flex-direction:column;">
- <div class="corner-deco tl"></div><div class="corner-deco tr"></div>
- <div class="corner-deco bl"></div><div class="corner-deco br"></div>
- <div class="panel-title">实验室设备分类及使用统计</div>
- <div class="equip-chart-wrap">
- <div id="chartEquipPie" style="width:100%;height:100%;"></div>
- </div>
- <div class="equip-mid">
- <div class="equip-mid-item"><div class="em-value">586</div><div class="em-label">设备总数</div></div>
- <div class="equip-mid-item"><div class="em-value">12,480<span style="font-size:11px;color:var(--text-sub)">h</span></div><div class="em-label">使用总时长</div></div>
- <div class="equip-mid-item"><div class="em-value">78.6<span style="font-size:11px;color:var(--text-sub)">%</span></div><div class="em-label">设备使用率</div></div>
- </div>
- <div class="equip-bottom">
- <div class="equip-status-card"><div class="es-value" style="color:var(--accent)">128</div><div class="es-label">使用</div></div>
- <div class="equip-status-card"><div class="es-value" style="color:var(--good)">312</div><div class="es-label">空闲</div></div>
- <div class="equip-status-card"><div class="es-value" style="color:var(--warn)">108</div><div class="es-label">正常</div></div>
- <div class="equip-status-card"><div class="es-value" style="color:var(--danger)">38</div><div class="es-label">维修</div></div>
- </div>
- </div>
- <!-- 化学品库存动态统计 -->
- <div class="panel" style="flex:3;display:flex;flex-direction:column;">
- <div class="corner-deco tl"></div><div class="corner-deco tr"></div>
- <div class="corner-deco bl"></div><div class="corner-deco br"></div>
- <div class="panel-title">化学品库存动态统计</div>
- <div class="chem-panel-body">
- <div class="chem-stats-grid">
- <div class="chem-stat-item">
- <div class="chem-ring-wrap">
- <svg viewBox="0 0 44 44">
- <g transform="rotate(-90 22 22)">
- <circle class="chem-ring-bg" cx="22" cy="22" r="18"/>
- <circle class="chem-ring-fg" cx="22" cy="22" r="18" stroke="#48d7ff"
- stroke-dasharray="113.1" stroke-dashoffset="0"/>
- </g>
- <text x="22" y="23" class="chem-ring-icon" fill="#48d7ff">🧪</text>
- </svg>
- </div>
- <div class="chem-stat-info">
- <div class="chem-stat-value" style="color:#48d7ff">2,450<span style="font-size:10px;color:var(--text-sub)"> L</span></div>
- <div class="chem-stat-label">存量化学品总量</div>
- </div>
- </div>
- <div class="chem-stat-item">
- <div class="chem-ring-wrap">
- <svg viewBox="0 0 44 44">
- <g transform="rotate(-90 22 22)">
- <circle class="chem-ring-bg" cx="22" cy="22" r="18"/>
- <circle class="chem-ring-fg" cx="22" cy="22" r="18" stroke="#ff4d4f"
- stroke-dasharray="113.1" stroke-dashoffset="81.4"/>
- </g>
- <text x="22" y="22" class="chem-ring-pct" fill="#ff4d4f">27.8%</text>
- </svg>
- </div>
- <div class="chem-stat-info">
- <div class="chem-stat-value" style="color:#ff4d4f">680<span style="font-size:10px;color:var(--text-sub)"> L</span></div>
- <div class="chem-stat-label">管控类化学品</div>
- </div>
- </div>
- <div class="chem-stat-item">
- <div class="chem-ring-wrap">
- <svg viewBox="0 0 44 44">
- <g transform="rotate(-90 22 22)">
- <circle class="chem-ring-bg" cx="22" cy="22" r="18"/>
- <circle class="chem-ring-fg" cx="22" cy="22" r="18" stroke="#36d399"
- stroke-dasharray="113.1" stroke-dashoffset="31.4"/>
- </g>
- <text x="22" y="22" class="chem-ring-pct" fill="#36d399">72.2%</text>
- </svg>
- </div>
- <div class="chem-stat-info">
- <div class="chem-stat-value" style="color:#36d399">1,770<span style="font-size:10px;color:var(--text-sub)"> L</span></div>
- <div class="chem-stat-label">非管控类化学品</div>
- </div>
- </div>
- <div class="chem-stat-item">
- <div class="chem-stat-info" style="text-align:center;flex:1;">
- <div class="chem-stat-value" style="color:#ffb020;font-size:22px;">156</div>
- <div class="chem-stat-label">存量化学品总类目</div>
- <div class="chem-stat-pct" style="color:var(--text-sub)">管控68类 / 非管控88类</div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- ===================== PAGE: 视频监控 ===================== -->
- <div class="page" id="page-video">
- <!-- LEFT: Tree -->
- <div class="video-left panel">
- <div class="corner-deco tl"></div><div class="corner-deco tr"></div>
- <div class="corner-deco bl"></div><div class="corner-deco br"></div>
- <div class="panel-title">建筑结构导航</div>
- <input class="video-search" type="text" placeholder="🔍 搜索楼栋 / 楼层..." />
- <select class="video-filter">
- <option value="">全部二级单位</option>
- <option>安全技术研究所</option>
- <option>职业安全研究所</option>
- <option>化学品安全研究所</option>
- <option>矿山安全研究所</option>
- </select>
- <div class="tree-container" id="treeContainer"></div>
- </div>
- <!-- RIGHT: Video Grid -->
- <div class="video-right">
- <div class="video-breadcrumb">安科院院区 → 科研楼A → 3层</div>
- <div class="video-grid" id="videoGrid"></div>
- <div class="video-pagination">
- <button class="page-btn" onclick="videoPageChange(-1)">◀ 上一页</button>
- <span class="page-info" id="videoPageInfo">第 1 / 3 页</span>
- <button class="page-btn" onclick="videoPageChange(1)">下一页 ▶</button>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- ===================== ALERT MODAL ===================== -->
- <div class="alert-overlay" id="alertOverlay">
- <div class="alert-modal">
- <div class="alert-header">
- <span class="alert-icon">⚠</span>
- <span class="alert-title">安 全 预 警</span>
- </div>
- <div class="alert-body">
- <div class="alert-info">
- <div class="alert-info-row"><span class="a-label">实 验 室:</span><span class="a-value" id="alertLab">--</span></div>
- <div class="alert-info-row"><span class="a-label">楼栋楼层:</span><span class="a-value" id="alertBuilding">--</span></div>
- <div class="alert-info-row"><span class="a-label">所属单位:</span><span class="a-value" id="alertUnit">--</span></div>
- <div class="alert-info-row"><span class="a-label">异常参数:</span><span class="a-value danger" id="alertParam">--</span></div>
- <div class="alert-info-row"><span class="a-label">当前数值:</span><span class="a-value danger" id="alertValue">--</span></div>
- <div class="alert-info-row"><span class="a-label">实验室负责人:</span><span class="a-value" id="alertPerson">--</span></div>
- <div class="alert-info-row"><span class="a-label">联系电话:</span><span class="a-value" id="alertPhone">--</span></div>
- <div class="alert-info-row"><span class="a-label">预警时间:</span><span class="a-value" id="alertTime">--</span></div>
- </div>
- <div class="alert-video">
- <div class="alert-video-title">实时监控画面</div>
- <div class="alert-video-feed">
- <span class="cam-label"><span class="rec-dot"></span>REC <span id="alertCamName">--</span></span>
- <span class="cam-placeholder">📹 实时监控画面</span>
- </div>
- </div>
- </div>
- <div class="alert-footer">
- <button class="alert-close-btn" onclick="closeAlert()">确 认</button>
- <button class="alert-close-btn" style="background:linear-gradient(135deg,#1a3a6a,#0d2555);border:1px solid rgba(72,180,255,0.5);margin-left:12px;" onclick="closeAlert();openEvacuation()">应急疏散</button>
- </div>
- </div>
- </div>
- <!-- ===================== EVACUATION MODAL ===================== -->
- <div class="evac-overlay" id="evacOverlay">
- <div class="evac-modal">
- <div class="evac-header">
- <div class="evac-header-left">
- <span class="evac-icon">📋</span>
- <span class="evac-title">应急疏散</span>
- </div>
- <div class="evac-close" onclick="closeEvacuation()">✕</div>
- </div>
- <div class="evac-body">
- <div class="evac-main">
- <div class="evac-legend">
- <div class="evac-legend-item">
- <span class="evac-legend-line" style="background:linear-gradient(90deg,#3a7bff,#48d7ff);"></span>
- <span>应急疏散路线图</span>
- </div>
- <div class="evac-legend-item">
- <span>房间号→实验室名称</span>
- </div>
- </div>
- <div class="evac-floorplan">
- <svg viewBox="0 0 680 360" id="evacFloorSvg">
- <!-- Top row rooms -->
- <g class="evac-room-group" data-room="A101" onclick="selectEvacRoom(this)">
- <rect x="40" y="30" width="130" height="80" rx="3" fill="rgba(72,180,255,0.06)" stroke="rgba(72,180,255,0.3)" stroke-width="1"/>
- <text x="105" y="75" text-anchor="middle" fill="#7eacc8" font-size="13" pointer-events="none">A101</text>
- </g>
- <g class="evac-room-group" data-room="A102" onclick="selectEvacRoom(this)">
- <rect x="180" y="30" width="130" height="80" rx="3" fill="rgba(72,180,255,0.06)" stroke="rgba(72,180,255,0.3)" stroke-width="1"/>
- <text x="245" y="75" text-anchor="middle" fill="#7eacc8" font-size="13" pointer-events="none">A102</text>
- </g>
- <g class="evac-room-group" data-room="A103" onclick="selectEvacRoom(this)">
- <rect x="320" y="30" width="130" height="80" rx="3" fill="rgba(72,180,255,0.06)" stroke="rgba(72,180,255,0.3)" stroke-width="1"/>
- <text x="385" y="75" text-anchor="middle" fill="#7eacc8" font-size="13" pointer-events="none">A103</text>
- </g>
- <g class="evac-room-group" data-room="A104" onclick="selectEvacRoom(this)">
- <rect x="460" y="30" width="130" height="80" rx="3" fill="rgba(72,180,255,0.06)" stroke="rgba(72,180,255,0.3)" stroke-width="1"/>
- <text x="525" y="75" text-anchor="middle" fill="#7eacc8" font-size="13" pointer-events="none">A104</text>
- </g>
- <!-- Corridor -->
- <text x="340" y="155" text-anchor="middle" fill="rgba(126,172,200,0.5)" font-size="14" letter-spacing="8">走 道</text>
- <circle cx="300" cy="155" r="10" fill="none" stroke="rgba(72,180,255,0.15)" stroke-width="1"/>
- <circle cx="380" cy="155" r="10" fill="none" stroke="rgba(72,180,255,0.15)" stroke-width="1"/>
- <!-- Right exit -->
- <rect x="630" y="45" width="30" height="80" rx="2" fill="none" stroke="rgba(255,77,79,0.4)" stroke-width="1" stroke-dasharray="4,2"/>
- <text x="645" y="80" text-anchor="middle" fill="var(--danger)" font-size="10">紧急</text>
- <text x="645" y="95" text-anchor="middle" fill="var(--danger)" font-size="10">出口</text>
- <!-- Left exit -->
- <rect x="2" y="220" width="30" height="80" rx="2" fill="none" stroke="rgba(255,77,79,0.4)" stroke-width="1" stroke-dasharray="4,2"/>
- <text x="17" y="255" text-anchor="middle" fill="var(--danger)" font-size="10">紧急</text>
- <text x="17" y="270" text-anchor="middle" fill="var(--danger)" font-size="10">出口</text>
- <!-- Bottom row rooms -->
- <g class="evac-room-group" data-room="A105" onclick="selectEvacRoom(this)">
- <rect x="40" y="200" width="110" height="110" rx="3" fill="rgba(72,180,255,0.06)" stroke="rgba(72,180,255,0.3)" stroke-width="1"/>
- <text x="95" y="260" text-anchor="middle" fill="#7eacc8" font-size="13" pointer-events="none">A105</text>
- </g>
- <g class="evac-room-group" data-room="A106" onclick="selectEvacRoom(this)">
- <rect x="160" y="200" width="100" height="110" rx="3" fill="rgba(72,180,255,0.06)" stroke="rgba(72,180,255,0.3)" stroke-width="1"/>
- <text x="210" y="260" text-anchor="middle" fill="#7eacc8" font-size="13" pointer-events="none">A106</text>
- </g>
- <g class="evac-room-group" data-room="A107" onclick="selectEvacRoom(this)">
- <rect x="270" y="200" width="100" height="110" rx="3" fill="rgba(72,180,255,0.06)" stroke="rgba(72,180,255,0.3)" stroke-width="1"/>
- <text x="320" y="260" text-anchor="middle" fill="#7eacc8" font-size="13" pointer-events="none">A107</text>
- </g>
- <g class="evac-room-group" data-room="A108" onclick="selectEvacRoom(this)">
- <rect x="380" y="200" width="100" height="110" rx="3" fill="rgba(72,180,255,0.06)" stroke="rgba(72,180,255,0.3)" stroke-width="1"/>
- <text x="430" y="260" text-anchor="middle" fill="#7eacc8" font-size="13" pointer-events="none">A108</text>
- </g>
- <g class="evac-room-group" data-room="A109" onclick="selectEvacRoom(this)">
- <rect x="490" y="200" width="130" height="110" rx="3" fill="rgba(72,180,255,0.06)" stroke="rgba(72,180,255,0.3)" stroke-width="1"/>
- <text x="555" y="260" text-anchor="middle" fill="#7eacc8" font-size="13" pointer-events="none">A109</text>
- </g>
- <!-- Evacuation route (drawn dynamically) -->
- <g id="evacRoutes" class="evac-route-arrow"></g>
- <!-- SVG arrow marker -->
- <defs>
- <marker id="evacArrowHead" markerWidth="10" markerHeight="8" refX="9" refY="4" orient="auto">
- <polygon points="0,0 10,4 0,8" fill="#3a7bff"/>
- </marker>
- </defs>
- </svg>
- <!-- Alert info card overlay -->
- <div class="evac-alert-card" id="evacAlertCard">
- <div class="eac-header">
- <div class="eac-time"><span class="eac-icon">⚠</span> <span id="evacAlertTime">2026-03-24 14:32:18</span></div>
- <button class="eac-close-btn" onclick="document.getElementById('evacAlertCard').style.display='none'">✕</button>
- </div>
- <div class="eac-badge">触发风险</div>
- <div class="eac-desc">发生风险:TVOC浓度超标</div>
- </div>
- </div>
- </div>
- <div class="evac-sidebar">
- <div class="evac-sidebar-block">
- <div class="evac-sidebar-title">实时视频监控</div>
- <div class="evac-sidebar-label" id="evacSidebarLabel1">楼道 1 层</div>
- <div class="evac-video-placeholder" id="evacSidebarVideo1">📹 实时视频监控</div>
- </div>
- <div class="evac-sidebar-block">
- <div class="evac-sidebar-title">实时视频监控</div>
- <div class="evac-sidebar-label" id="evacSidebarLabel2">楼道 2 层</div>
- <div class="evac-video-placeholder" id="evacSidebarVideo2">📹 实时视频监控</div>
- </div>
- </div>
- </div>
- <div class="evac-footer">
- <div class="evac-metrics">
- <div class="evac-metric-row">
- <span class="evac-metric-label">告警指标</span>
- <span class="evac-metric-value">TVOC 浓度超标</span>
- <span style="color:var(--text-sub);font-size:12px">2.85 / 0.6 mg/m³</span>
- </div>
- <div class="evac-metric-row">
- <span class="evac-metric-label">当前值 / 安全阈值</span>
- <span class="evac-metric-value">TVOC 浓度超标</span>
- <span style="color:var(--text-sub);font-size:12px">2.85 / 0.6 mg/m³</span>
- </div>
- </div>
- <div class="evac-broadcast">
- <div class="evac-broadcast-header">
- <div class="evac-broadcast-title">🔊 语音广播</div>
- <div class="evac-broadcast-hint">选择播放设备</div>
- </div>
- <div class="evac-speakers">
- <div class="evac-speaker-btn">NKL1FB1122 喇叭</div>
- <div class="evac-speaker-btn">NKL1FB1122 喇叭</div>
- <div class="evac-speaker-btn">NKL1FB1122 喇叭</div>
- </div>
- <div class="evac-broadcast-input">
- <input type="text" placeholder="请输入喊话内容" />
- <button>发送</button>
- </div>
- </div>
- <div class="evac-actions">
- <div class="evac-btn-later" onclick="closeEvacuation()">稍后处理</div>
- <div class="evac-btn-exec" onclick="executeEvacuation()">执行疏散</div>
- </div>
- </div>
- </div>
- </div>
- <script>
- /* ================================================================
- JavaScript - 中国安全生产科学研究院实验室安全智慧化管控中心
- ================================================================ */
- // ===================== Viewport Scaling =====================
- function fitScreen() {
- const scr = document.getElementById('mainScreen');
- const sw = 1920, sh = 1080;
- const vw = window.innerWidth, vh = window.innerHeight;
- const scale = Math.min(vw / sw, vh / sh);
- scr.style.transform = `translate(-50%, -50%) scale(${scale})`;
- }
- window.addEventListener('resize', fitScreen);
- fitScreen();
- // ===================== Auto Fullscreen =====================
- document.addEventListener('click', function onFirstClick() {
- if (document.documentElement.requestFullscreen) {
- document.documentElement.requestFullscreen().catch(() => {});
- }
- document.removeEventListener('click', onFirstClick);
- }, { once: true });
- // ===================== Clock =====================
- function updateClock() {
- const now = new Date();
- const hh = String(now.getHours()).padStart(2,'0');
- const mm = String(now.getMinutes()).padStart(2,'0');
- const ss = String(now.getSeconds()).padStart(2,'0');
- document.getElementById('clock').textContent = `${hh}:${mm}:${ss}`;
- const days = ['日','一','二','三','四','五','六'];
- document.getElementById('weekday').textContent = `星期${days[now.getDay()]}`;
- }
- setInterval(updateClock, 1000);
- updateClock();
- // ===================== Page Switch =====================
- function switchPage(page) {
- document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
- document.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active'));
- if (page === 'lab') {
- document.getElementById('page-lab').classList.add('active');
- document.querySelectorAll('.nav-tab')[0].classList.add('active');
- setTimeout(() => { initLabCharts(); }, 100);
- } else {
- document.getElementById('page-video').classList.add('active');
- document.querySelectorAll('.nav-tab')[1].classList.add('active');
- }
- }
- // ===================== Flip Counter =====================
- function renderFlip(id, num, len) {
- const str = String(num).padStart(len, '0');
- const container = document.getElementById(id);
- container.innerHTML = str.split('').map(d => `<div class="flip-digit">${d}</div>`).join('');
- }
- renderFlip('flipTotal', 1286, 5);
- renderFlip('flipCurrent', 47, 4);
- // ===================== Sensor Scroll Data =====================
- const labNames = [
- { name: '化学实验室A101(A101)', unit: '化学品安全研究所' },
- { name: '材料力学实验室B203(B203)', unit: '安全技术研究所' },
- { name: '气体分析实验室A305(A305)', unit: '职业安全研究所' },
- { name: '高温高压实验室C102(C102)', unit: '矿山安全研究所' },
- { name: '生物安全实验室A202(A202)', unit: '职业安全研究所' },
- { name: '粉尘检测实验室B101(B101)', unit: '矿山安全研究所' },
- { name: '电气安全实验室C201(C201)', unit: '安全技术研究所' },
- { name: '环境模拟实验室A408(A408)', unit: '化学品安全研究所' },
- { name: '爆炸安全实验室B302(B302)', unit: '安全技术研究所' },
- { name: '应急救援实验室C305(C305)', unit: '职业安全研究所' },
- { name: '有机化学实验室A103(A103)', unit: '化学品安全研究所' },
- { name: '无损检测实验室B205(B205)', unit: '安全技术研究所' },
- ];
- const sensorTypes = ['温度','湿度','TVOC','CO₂','O₂'];
- function randVal(type) {
- switch(type) {
- case '温度': return (18 + Math.random() * 15).toFixed(1) + '°C';
- case '湿度': return (30 + Math.random() * 50).toFixed(0) + '%';
- case 'TVOC': return (Math.random() * 2).toFixed(2) + 'mg/m³';
- case 'CO₂': return (300 + Math.random() * 800).toFixed(0) + 'ppm';
- case 'O₂': return (18 + Math.random() * 4).toFixed(1) + '%';
- default: return '--';
- }
- }
- function isAlarm(type, val) {
- const n = parseFloat(val);
- if (type === '温度' && n > 30) return true;
- if (type === 'TVOC' && n > 1.5) return true;
- if (type === 'CO₂' && n > 900) return true;
- if (type === 'O₂' && n < 19.5) return true;
- return false;
- }
- function buildSensorList() {
- const el = document.getElementById('sensorList');
- let html = '';
- const items = [...labNames, ...labNames]; // duplicate for seamless scroll
- items.forEach(lab => {
- html += `<div class="sensor-item"><div class="lab-name">${lab.name}<span class="lab-unit">${lab.unit}</span></div><div class="sensor-values">`;
- sensorTypes.forEach(s => {
- const v = randVal(s);
- const alarm = isAlarm(s, v);
- html += `<span class="sensor-val${alarm ? ' alarm' : ''}">${alarm ? '<span class="alarm-icon">⚠</span>' : ''}${s}: ${v}</span>`;
- });
- html += '</div></div>';
- });
- el.innerHTML = html;
- }
- buildSensorList();
- // ===================== Warning Scroll Data =====================
- function buildWarningList() {
- const el = document.getElementById('warningList');
- const warnings = [
- { type: 'sensor', lab: '化学实验室A101(A101)', unit: '化学品安全研究所', sensor: 'TVOC', value: '1.82mg/m³', time: '2026-03-24 14:32:18' },
- { type: 'chem', lab: '气体分析实验室A305(A305)', unit: '职业安全研究所', person: '王伟', time: '2026-03-24 14:28:45' },
- { type: 'sensor', lab: '高温高压实验室C102(C102)', unit: '矿山安全研究所', sensor: '温度', value: '35.6°C', time: '2026-03-24 14:28:05' },
- { type: 'chem', lab: '有机化学实验室A103(A103)', unit: '化学品安全研究所', person: '李明', time: '2026-03-24 14:15:33' },
- { type: 'sensor', lab: '气体分析实验室A305(A305)', unit: '职业安全研究所', sensor: 'CO₂', value: '1050ppm', time: '2026-03-24 13:55:42' },
- { type: 'sensor', lab: '粉尘检测实验室B101(B101)', unit: '矿山安全研究所', sensor: 'O₂', value: '18.2%', time: '2026-03-24 13:20:10' },
- { type: 'chem', lab: '环境模拟实验室A408(A408)', unit: '化学品安全研究所', person: '赵磊', time: '2026-03-24 13:10:22' },
- { type: 'sensor', lab: '爆炸安全实验室B302(B302)', unit: '安全技术研究所', sensor: 'TVOC', value: '1.95mg/m³', time: '2026-03-24 12:45:33' },
- { type: 'chem', lab: '材料力学实验室B203(B203)', unit: '安全技术研究所', person: '陈静', time: '2026-03-24 12:30:18' },
- { type: 'sensor', lab: '环境模拟实验室A408(A408)', unit: '化学品安全研究所', sensor: '温度', value: '33.8°C', time: '2026-03-24 11:18:27' },
- { type: 'sensor', lab: '生物安全实验室A202(A202)', unit: '职业安全研究所', sensor: 'CO₂', value: '980ppm', time: '2026-03-24 10:42:51' },
- { type: 'chem', lab: '化学实验室A101(A101)', unit: '化学品安全研究所', person: '张强', time: '2026-03-24 10:15:07' },
- { type: 'sensor', lab: '有机化学实验室A103(A103)', unit: '化学品安全研究所', sensor: 'TVOC', value: '1.68mg/m³', time: '2026-03-24 09:36:14' },
- ];
- const all = [...warnings, ...warnings]; // duplicate for seamless scroll
- let html = '';
- all.forEach(w => {
- if (w.type === 'chem') {
- html += `<div class="warning-item chem-violation">
- <div class="w-lab">${w.lab} - ${w.unit}</div>
- <div class="w-sensor">化学品违规带离: ${w.person} 未正常使用违规带离</div>
- <div class="w-time">${w.time}</div>
- </div>`;
- } else {
- html += `<div class="warning-item">
- <div class="w-lab">${w.lab} - ${w.unit}</div>
- <div class="w-sensor">异常: ${w.sensor} ${w.value}</div>
- <div class="w-time">${w.time}</div>
- </div>`;
- }
- });
- el.innerHTML = html;
- document.getElementById('warningCount').textContent = warnings.length;
- }
- buildWarningList();
- // ===================== Video Page: Tree =====================
- const treeData = {
- name: '安科院院区', children: [
- { name: '科研楼A', children: [
- { name: '1层', children: [{name:'A101'},{name:'A102'},{name:'A103'}] },
- { name: '2层', children: [{name:'A201'},{name:'A202'},{name:'A203'}] },
- { name: '3层', children: [{name:'A301'},{name:'A302'},{name:'A303'},{name:'A304'},{name:'A305'}] },
- { name: '4层', children: [{name:'A401'},{name:'A402'},{name:'A403'},{name:'A408'}] },
- ]},
- { name: '科研楼B', children: [
- { name: '1层', children: [{name:'B101'},{name:'B102'}] },
- { name: '2层', children: [{name:'B201'},{name:'B202'},{name:'B203'},{name:'B205'}] },
- { name: '3层', children: [{name:'B301'},{name:'B302'}] },
- ]},
- { name: '科研楼C', children: [
- { name: '1层', children: [{name:'C101'},{name:'C102'}] },
- { name: '2层', children: [{name:'C201'},{name:'C202'}] },
- { name: '3层', children: [{name:'C301'},{name:'C305'}] },
- ]},
- ]
- };
- function buildTree(node, depth) {
- const hasChild = node.children && node.children.length > 0;
- let html = `<div class="tree-node">
- <div class="tree-node-content" style="padding-left:${depth * 12}px" onclick="toggleTree(this)">
- <span class="tree-arrow">${hasChild ? '▶' : ''}</span>
- <span>${node.name}</span>
- </div>`;
- if (hasChild) {
- html += `<div class="tree-children">`;
- node.children.forEach(c => { html += buildTree(c, depth + 1); });
- html += `</div>`;
- }
- html += `</div>`;
- return html;
- }
- document.getElementById('treeContainer').innerHTML = buildTree(treeData, 0);
- function toggleTree(el) {
- const children = el.parentElement.querySelector('.tree-children');
- const arrow = el.querySelector('.tree-arrow');
- if (children) {
- children.classList.toggle('open');
- arrow.classList.toggle('expanded');
- }
- // select highlight
- document.querySelectorAll('.tree-node-content').forEach(n => n.classList.remove('selected'));
- el.classList.add('selected');
- }
- // ===================== Video Grid =====================
- let currentVideoPage = 1;
- const totalVideoPages = 3;
- function renderVideoGrid() {
- const grid = document.getElementById('videoGrid');
- let html = '';
- for (let i = 0; i < 9; i++) {
- const roomNum = (currentVideoPage - 1) * 9 + i + 1;
- const isAI = i === 0;
- html += `<div class="video-cell${isAI ? ' ai-cam' : ''}">
- <div class="cam-frame">
- <div class="cam-corner c-tl"></div><div class="cam-corner c-tr"></div>
- <div class="cam-corner c-bl"></div><div class="cam-corner c-br"></div>
- ${isAI ? '<span class="ai-badge">AI 智能</span>' : ''}
- <div class="cam-inner">
- <span class="cam-label"><span class="cam-dot"></span>${isAI ? '🎯 ' : ''}A3${String(roomNum).padStart(2,'0')} 摄像头</span>
- <span class="cam-placeholder">📹 实时监控画面</span>
- </div>
- </div>
- </div>`;
- }
- grid.innerHTML = html;
- document.getElementById('videoPageInfo').textContent = `第 ${currentVideoPage} / ${totalVideoPages} 页`;
- }
- renderVideoGrid();
- function videoPageChange(dir) {
- currentVideoPage = Math.max(1, Math.min(totalVideoPages, currentVideoPage + dir));
- renderVideoGrid();
- }
- // ===================== Alert Modal =====================
- function showAlert(lab, building, unit, param, value, person, phone) {
- document.getElementById('alertLab').textContent = lab;
- document.getElementById('alertBuilding').textContent = building || '--';
- document.getElementById('alertUnit').textContent = unit;
- document.getElementById('alertParam').textContent = param;
- document.getElementById('alertValue').textContent = value;
- document.getElementById('alertPerson').textContent = person || '--';
- document.getElementById('alertPhone').textContent = phone || '--';
- document.getElementById('alertCamName').textContent = lab;
- const now = new Date();
- document.getElementById('alertTime').textContent =
- `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}-${String(now.getDate()).padStart(2,'0')} ${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}:${String(now.getSeconds()).padStart(2,'0')}`;
- document.getElementById('alertOverlay').classList.add('show');
- }
- function closeAlert() {
- document.getElementById('alertOverlay').classList.remove('show');
- }
- // Demo: trigger alert after 15s
- setTimeout(() => {
- showAlert('化学实验室A101(A101)', '科研楼A-1层', '化学品安全研究所', 'TVOC 超标', '1.82 mg/m³', '张明', '138-0012-3456');
- }, 15000);
- // ===================== ECharts Initialization =====================
- let chartLabOverview, chartGradeBar, chartPeopleLine, chartGauge, chartEquipPie;
- function initLabCharts() {
- // ---- 1. Lab Overview Ring ----
- if (chartLabOverview) chartLabOverview.dispose();
- chartLabOverview = echarts.init(document.getElementById('chartLabOverview'));
- chartLabOverview.setOption({
- tooltip: { trigger: 'item', backgroundColor: 'rgba(6,22,56,0.9)', borderColor: 'rgba(72,180,255,0.3)', textStyle: { color: '#d8f4ff' } },
- legend: { orient: 'vertical', right: 10, top: 'center', textStyle: { color: '#7eacc8', fontSize: 11 }, itemWidth: 10, itemHeight: 10 },
- series: [{
- type: 'pie', radius: ['45%','70%'], center: ['35%','50%'],
- label: {
- show: true, position: 'center',
- formatter: '58\n实验室总数', fontSize: 14, color: '#fff', lineHeight: 20
- },
- data: [
- { value: 8, name: 'I级(红)', itemStyle: { color: '#ff4d4f' } },
- { value: 12, name: 'II级(橙)', itemStyle: { color: '#ff8c00' } },
- { value: 18, name: 'III级(黄)', itemStyle: { color: '#ffcc00' } },
- { value: 20, name: 'IV级(蓝)', itemStyle: { color: '#3a7bff' } },
- ],
- emphasis: { scaleSize: 6 }
- }]
- });
- // ---- 2. Grade Stacked Bar ----
- if (chartGradeBar) chartGradeBar.dispose();
- chartGradeBar = echarts.init(document.getElementById('chartGradeBar'));
- const units = ['安全技术\n研究所','职业安全\n研究所','化学品安全\n研究所','矿山安全\n研究所','应急科学\n研究中心','信息技术\n研究所','检测检验\n中心','标准化\n研究所'];
- const totals = [12, 10, 14, 8, 6, 4, 3, 1];
- const gradeData = {
- 'I级': [2,1,3,2,1,0,0,0],
- 'II级': [3,2,4,2,1,1,1,0],
- 'III级': [4,4,4,2,2,2,1,1],
- 'IV级': [3,3,3,2,2,1,1,0]
- };
- const colors = { 'I级': '#ff4d4f', 'II级': '#ff8c00', 'III级': '#ffcc00', 'IV级': '#3a7bff' };
- let barOption = {
- tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, backgroundColor: 'rgba(6,22,56,0.9)', borderColor: 'rgba(72,180,255,0.3)', textStyle: { color: '#d8f4ff' } },
- legend: { data: Object.keys(gradeData), top: 0, textStyle: { color: '#7eacc8', fontSize: 11 }, itemWidth: 10, itemHeight: 10 },
- grid: { left: 40, right: 10, top: 35, bottom: 50 },
- xAxis: {
- type: 'category', data: units,
- axisLabel: {
- color: '#7eacc8', fontSize: 10, interval: 0,
- formatter: function(v, idx) { return v + '\n(' + totals[idx] + ')'; }
- },
- axisLine: { lineStyle: { color: 'rgba(72,180,255,0.2)' } },
- axisTick: { show: false }
- },
- yAxis: {
- type: 'value',
- axisLabel: { color: '#7eacc8', fontSize: 10 },
- axisLine: { lineStyle: { color: 'rgba(72,180,255,0.2)' } },
- splitLine: { lineStyle: { color: 'rgba(72,180,255,0.08)' } }
- },
- dataZoom: [{
- type: 'slider', show: false, xAxisIndex: 0, startValue: 0, endValue: 5
- }],
- series: Object.keys(gradeData).map(k => ({
- name: k, type: 'bar', stack: 'total', barWidth: 20,
- data: gradeData[k],
- itemStyle: { color: colors[k], borderRadius: [0,0,0,0] }
- }))
- };
- chartGradeBar.setOption(barOption);
- // Auto scroll X axis
- let barScrollIdx = 0;
- setInterval(() => {
- barScrollIdx++;
- if (barScrollIdx > units.length - 5) barScrollIdx = 0;
- chartGradeBar.dispatchAction({ type: 'dataZoom', startValue: barScrollIdx, endValue: barScrollIdx + 5 });
- }, 5000);
- // ---- 3. People Line Chart ----
- if (chartPeopleLine) chartPeopleLine.dispose();
- chartPeopleLine = echarts.init(document.getElementById('chartPeopleLine'));
- const timeSlots = ['0:00','3:00','6:00','9:00','12:00','15:00','18:00','21:00','24:00'];
- chartPeopleLine.setOption({
- tooltip: { trigger: 'axis', backgroundColor: 'rgba(6,22,56,0.9)', borderColor: 'rgba(72,180,255,0.3)', textStyle: { color: '#d8f4ff' } },
- legend: { data: ['进入人数','实验人数'], top: 0, right: 0, textStyle: { color: '#7eacc8', fontSize: 10 }, itemWidth: 14, itemHeight: 2 },
- grid: { left: 35, right: 10, top: 24, bottom: 20 },
- xAxis: {
- type: 'category', data: timeSlots, boundaryGap: false,
- axisLabel: { color: '#7eacc8', fontSize: 10 },
- axisLine: { lineStyle: { color: 'rgba(72,180,255,0.2)' } }
- },
- yAxis: {
- type: 'value',
- axisLabel: { color: '#7eacc8', fontSize: 10 },
- splitLine: { lineStyle: { color: 'rgba(72,180,255,0.08)' } }
- },
- series: [
- {
- name: '进入人数', type: 'line', smooth: true,
- data: [0, 2, 5, 86, 120, 145, 98, 42, 0],
- lineStyle: { color: '#48d7ff', width: 2 },
- areaStyle: { color: new echarts.graphic.LinearGradient(0,0,0,1,[{offset:0,color:'rgba(72,215,255,0.25)'},{offset:1,color:'rgba(72,215,255,0)'}]) },
- itemStyle: { color: '#48d7ff' }, symbolSize: 4
- },
- {
- name: '实验人数', type: 'line', smooth: true,
- data: [0, 1, 3, 52, 68, 78, 55, 20, 0],
- lineStyle: { color: '#3a7bff', width: 2 },
- areaStyle: { color: new echarts.graphic.LinearGradient(0,0,0,1,[{offset:0,color:'rgba(58,123,255,0.2)'},{offset:1,color:'rgba(58,123,255,0)'}]) },
- itemStyle: { color: '#3a7bff' }, symbolSize: 4
- }
- ]
- });
- // ---- 4. Device Gauge ----
- if (chartGauge) chartGauge.dispose();
- chartGauge = echarts.init(document.getElementById('chartGauge'));
- chartGauge.setOption({
- series: [{
- type: 'gauge', startAngle: 210, endAngle: -30,
- radius: '90%', center: ['50%','58%'],
- min: 0, max: 100,
- axisLine: {
- lineStyle: {
- width: 14,
- color: [[0.7,'rgba(72,215,255,0.2)'],[0.9,'rgba(72,215,255,0.4)'],[1,'#48d7ff']]
- }
- },
- axisTick: { show: false },
- splitLine: { show: false },
- axisLabel: { show: false },
- pointer: {
- length: '60%', width: 4,
- itemStyle: { color: '#48d7ff' }
- },
- title: { show: true, offsetCenter: [0, '72%'], fontSize: 12, color: '#7eacc8' },
- detail: {
- valueAnimation: true, fontSize: 26, color: '#48d7ff', fontWeight: 700,
- offsetCenter: [0, '40%'],
- formatter: '{value}%'
- },
- data: [{ value: 93, name: '设备在线率' }]
- }]
- });
- // ---- 5. Equipment Pie ----
- if (chartEquipPie) chartEquipPie.dispose();
- chartEquipPie = echarts.init(document.getElementById('chartEquipPie'));
- chartEquipPie.setOption({
- tooltip: { trigger: 'item', backgroundColor: 'rgba(6,22,56,0.9)', borderColor: 'rgba(72,180,255,0.3)', textStyle: { color: '#d8f4ff' } },
- legend: { orient: 'vertical', right: 4, top: 'center', textStyle: { color: '#7eacc8', fontSize: 10 }, itemWidth: 8, itemHeight: 8 },
- series: [{
- type: 'pie', radius: ['30%', '60%'], center: ['35%','50%'],
- label: { show: false },
- data: [
- { value: 120, name: '分析仪器', itemStyle: { color: '#48d7ff' } },
- { value: 95, name: '安全防护', itemStyle: { color: '#3a7bff' } },
- { value: 86, name: '化学试剂设备', itemStyle: { color: '#36d399' } },
- { value: 78, name: '电气设备', itemStyle: { color: '#ffb020' } },
- { value: 65, name: '力学测试', itemStyle: { color: '#ff8c00' } },
- { value: 52, name: '环境监测', itemStyle: { color: '#ff4d4f' } },
- { value: 90, name: '通用设备', itemStyle: { color: '#a78bfa' } },
- ],
- emphasis: { scaleSize: 4 }
- }]
- });
- }
- // Init charts on load
- setTimeout(initLabCharts, 200);
- // Handle resize
- window.addEventListener('resize', () => {
- [chartLabOverview, chartGradeBar, chartPeopleLine, chartGauge, chartEquipPie].forEach(c => c && c.resize());
- });
- // ===================== Evacuation Modal =====================
- // Room name mapping
- const evacRoomNames = {
- A101: '化学实验室A101', A102: '材料分析实验室A102',
- A103: '有机化学实验室A103', A104: '气相色谱实验室A104',
- A105: '粉尘检测实验室A105', A106: '光谱分析实验室A106',
- A107: '质谱分析实验室A107', A108: '样品前处理室A108',
- A109: '大型仪器室A109'
- };
- // Room center coordinates for route drawing
- const evacRoomPos = {
- // Top row: room center X, corridor Y entry point
- A101: { cx: 105, cy: 70, row: 'top', exitX: 105, corridorY: 130 },
- A102: { cx: 245, cy: 70, row: 'top', exitX: 245, corridorY: 130 },
- A103: { cx: 385, cy: 70, row: 'top', exitX: 385, corridorY: 130 },
- A104: { cx: 525, cy: 70, row: 'top', exitX: 525, corridorY: 130 },
- // Bottom row: room center X, corridor Y entry point
- A105: { cx: 95, cy: 255, row: 'bottom', exitX: 95, corridorY: 185 },
- A106: { cx: 210, cy: 255, row: 'bottom', exitX: 210, corridorY: 185 },
- A107: { cx: 320, cy: 255, row: 'bottom', exitX: 320, corridorY: 185 },
- A108: { cx: 430, cy: 255, row: 'bottom', exitX: 430, corridorY: 185 },
- A109: { cx: 555, cy: 255, row: 'bottom', exitX: 555, corridorY: 185 },
- };
- // Current alarm room (default from the alert)
- let evacAlarmRoom = 'A105';
- function openEvacuation() {
- document.getElementById('evacOverlay').classList.add('show');
- // Reset state
- document.getElementById('evacRoutes').innerHTML = '';
- document.getElementById('evacRoutes').classList.remove('visible');
- document.getElementById('evacAlertCard').style.display = 'block';
- // Clear room selections & alarm highlights
- document.querySelectorAll('.evac-room-group').forEach(g => {
- g.classList.remove('selected', 'alarm');
- });
- // Reset sidebar
- document.getElementById('evacSidebarLabel1').textContent = '楼道 1 层';
- document.getElementById('evacSidebarVideo1').textContent = '📹 实时视频监控';
- document.getElementById('evacSidebarLabel2').textContent = '楼道 2 层';
- document.getElementById('evacSidebarVideo2').textContent = '📹 实时视频监控';
- // Update time
- const now = new Date();
- const ts = `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}-${String(now.getDate()).padStart(2,'0')} ${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}:${String(now.getSeconds()).padStart(2,'0')}`;
- document.getElementById('evacAlertTime').textContent = ts;
- }
- function closeEvacuation() {
- document.getElementById('evacOverlay').classList.remove('show');
- }
- function selectEvacRoom(el) {
- // Clear previous selection (keep alarm class)
- document.querySelectorAll('.evac-room-group.selected').forEach(g => g.classList.remove('selected'));
- el.classList.add('selected');
- const room = el.getAttribute('data-room');
- const roomName = evacRoomNames[room] || room;
- // Update sidebar: top block shows selected room camera, bottom stays corridor
- document.getElementById('evacSidebarLabel1').textContent = roomName;
- document.getElementById('evacSidebarVideo1').textContent = '📹 ' + room + ' 实时监控画面';
- document.getElementById('evacSidebarLabel2').textContent = '楼道走廊';
- document.getElementById('evacSidebarVideo2').textContent = '📹 走廊实时监控画面';
- }
- function executeEvacuation() {
- const routesG = document.getElementById('evacRoutes');
- routesG.innerHTML = ''; // Clear previous
- // Mark alarm room
- document.querySelectorAll('.evac-room-group').forEach(g => g.classList.remove('alarm'));
- const alarmEl = document.querySelector(`.evac-room-group[data-room="${evacAlarmRoom}"]`);
- if (alarmEl) alarmEl.classList.add('alarm');
- const pos = evacRoomPos[evacAlarmRoom];
- if (!pos) return;
- // Build polyline path: room -> corridor -> along corridor -> exit
- let pathPoints = [];
- if (pos.row === 'top') {
- // Top row: go down to corridor, then right to exit
- pathPoints = [
- [pos.exitX, pos.cy + 40], // bottom edge of room
- [pos.exitX, pos.corridorY], // enter corridor
- [620, pos.corridorY], // along corridor to right
- [620, 85], // up to exit
- [640, 85], // into exit
- ];
- } else {
- // Bottom row: go up to corridor, then left to exit
- pathPoints = [
- [pos.exitX, pos.cy - 55], // top edge of room
- [pos.exitX, pos.corridorY], // enter corridor
- [35, pos.corridorY], // along corridor to left
- [35, 260], // down to exit
- [17, 260], // into exit
- ];
- }
- // Draw animated polyline
- const pointsStr = pathPoints.map(p => p.join(',')).join(' ');
- const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
- polyline.setAttribute('points', pointsStr);
- polyline.setAttribute('fill', 'none');
- polyline.setAttribute('stroke', '#3a7bff');
- polyline.setAttribute('stroke-width', '3');
- polyline.setAttribute('stroke-dasharray', '8,4');
- polyline.setAttribute('marker-end', 'url(#evacArrowHead)');
- routesG.appendChild(polyline);
- // Add glow effect polyline
- const glowLine = polyline.cloneNode();
- glowLine.setAttribute('stroke', 'rgba(58,123,255,0.3)');
- glowLine.setAttribute('stroke-width', '8');
- glowLine.removeAttribute('stroke-dasharray');
- glowLine.removeAttribute('marker-end');
- routesG.insertBefore(glowLine, polyline);
- // Show with animation
- routesG.classList.add('visible');
- }
- </script>
- </body>
- </html>
|