From c4735c0d3ff8320346cff779bad4a83a42f575db Mon Sep 17 00:00:00 2001 From: Hao Wang Date: Sun, 7 Dec 2025 02:07:14 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9A=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E5=8C=96=E9=A1=B9=E7=9B=AE=E7=BB=93=E6=9E=84=EF=BC=8C=E5=88=86?= =?UTF-8?q?=E7=A6=BB=E9=85=8D=E7=BD=AE=E3=80=81=E8=B7=AF=E7=94=B1=E3=80=81?= =?UTF-8?q?=E6=91=84=E5=83=8F=E5=A4=B4=E7=AE=A1=E7=90=86=E5=99=A8=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=81=A5=E5=BA=B7=E6=A3=80=E6=9F=A5=EF=BC=8C?= =?UTF-8?q?=E6=9B=B4=E6=96=B0Dockerfile=E5=92=8C=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 15 ++ Dockerfile | 6 +- app/__init__.py | 43 ++++ app/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1901 bytes .../camera_manager.cpython-312.pyc | Bin 0 -> 4887 bytes app/__pycache__/config.cpython-312.pyc | Bin 0 -> 1865 bytes app/__pycache__/health.cpython-312.pyc | Bin 0 -> 2992 bytes app/camera_manager.py | 103 +++++++++ app/config.py | 69 ++++++ app/health.py | 63 ++++++ app/routes/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 170 bytes app/routes/__pycache__/main.cpython-312.pyc | Bin 0 -> 3198 bytes app/routes/main.py | 53 +++++ app/templates/index.html | 53 +++++ multi_camera.log | 2 + requirements.txt | 4 +- run.py | 21 ++ start.sh | 14 +- static/css/style.css | 214 ++++++++++++++++++ static/js/app.js | 105 +++++++++ 21 files changed, 761 insertions(+), 5 deletions(-) create mode 100644 .env.example create mode 100644 app/__init__.py create mode 100644 app/__pycache__/__init__.cpython-312.pyc create mode 100644 app/__pycache__/camera_manager.cpython-312.pyc create mode 100644 app/__pycache__/config.cpython-312.pyc create mode 100644 app/__pycache__/health.cpython-312.pyc create mode 100644 app/camera_manager.py create mode 100644 app/config.py create mode 100644 app/health.py create mode 100644 app/routes/__init__.py create mode 100644 app/routes/__pycache__/__init__.cpython-312.pyc create mode 100644 app/routes/__pycache__/main.cpython-312.pyc create mode 100644 app/routes/main.py create mode 100644 app/templates/index.html create mode 100644 multi_camera.log create mode 100644 run.py create mode 100644 static/css/style.css create mode 100644 static/js/app.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..24470ad --- /dev/null +++ b/.env.example @@ -0,0 +1,15 @@ +# 环境变量配置示例 +# 复制此文件为 .env 并填写实际值 + +# 基础URL +BASE_URL=http://10.80.0.2:5045 + +# 登录凭据 +USERNAME=hao.wang@westwell-lab.com +PASSWORD=wh707297 + +# Flask配置 +FLASK_DEBUG=False +SECRET_KEY=dev-secret-key +PORT=5002 +HOST=0.0.0.0 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 1b13684..cfa864f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,9 +28,9 @@ RUN mkdir -p /var/log/multi_camera # 暴露端口 EXPOSE 5002 -# 健康检查 +# 健康检查(使用/health端点) HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD curl -f http://localhost:5002/status || exit 1 + CMD curl -f http://localhost:5002/health || exit 1 # 设置启动命令 -CMD ["python3", "complete_multi_camera_app.py"] \ No newline at end of file +CMD ["python3", "run.py"] \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..801a2d7 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,43 @@ +""" +Flask应用工厂 +""" +import logging +from flask import Flask +from .config import DEBUG, SECRET_KEY, HOST, PORT +from .health import register_health_routes +from .routes.main import register_main_routes + +def create_app(): + """创建Flask应用实例""" + app = Flask(__name__, + static_folder='../static', + template_folder='templates') + + # 配置 + app.config['DEBUG'] = DEBUG + app.config['SECRET_KEY'] = SECRET_KEY + + # 配置日志 + configure_logging(app) + + # 注册路由 + register_health_routes(app) + register_main_routes(app) + + return app + +def configure_logging(app): + """配置日志""" + if not app.debug: + # 在生产环境中,将日志输出到文件 + import logging + from logging.handlers import RotatingFileHandler + + file_handler = RotatingFileHandler('multi_camera.log', maxBytes=10240, backupCount=10) + file_handler.setFormatter(logging.Formatter( + '%(asctime)s - %(levelname)s - %(message)s' + )) + file_handler.setLevel(logging.INFO) + app.logger.addHandler(file_handler) + app.logger.setLevel(logging.INFO) + app.logger.info('多摄像头监控系统启动') \ No newline at end of file diff --git a/app/__pycache__/__init__.cpython-312.pyc b/app/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee89c78e51d7e10b4b854e68d39a3381fd0076bf GIT binary patch literal 1901 zcmb_dT}&KR6uvV%vpYMxupoa`N=q?Sri5JyCKgkDfI=&hVgM6eqm${*u*`O5huoP3 zA&{WfNNX&uApvY6KD4o>wLWN~DN$cde1V7oZ%Wj(3*il2p8DjuvwxJ-_~J?S-t%|Q z*?Z8t6bJA3PT`_ubBd^qk8ha5};*D9!K|Suekn*gM!kTPY@wlFhV?RAl(FR|_qYMw*^n&#d+9N}j zsYOOL+ZojiBXZJ;rA#g9*by^jIJz8z6j+gmv6awbjy&>AxFrIoBeJX~bw`%N!(&8@ zVGV8}gC18xRGP)^hh+-g7elkc`XOn(zG;_d1I<$%yCSNspX&Ia` zO{{PScfE+Oz8qWSo}gUqeCsNA_%3%~zG9WD`y)^~)t!N`C{_Rl2nEoLfOZbS2c|Cj z$o?|hJClXpnPeHnq6wDvkLtbUD?rLK;YrY9?IZAI5^lFXY*2+fRPsB6AVL8&MQ=D? zT9oqKo>M4Mme&^d1ntj#G~xNrQ~zK7s(r~pRQ<*LW2`$xf}q}h|D&z%Z*R_BPyaZF zX~)v>QPF2nv?t?Tx}lv`lB%KM@v^<(3IjxS)ClKYc(Oz=mABi+pe;z?tP(qy8t$-C zNoQO-+NjtuM>jReu5YeC+GuDann8OYC!3mWD{+ughah?S+9#V=&Zpm-NniVB>*|%w zOP_Cjy|lHon7;f~`r-{KKzLOID2ANRLq}LW5T>6Rm*Za%(fEEuao)4C9t5if{2?L(T{6`^+i)MC#< z&#EBh{ANNILdyq^|0=ZYJdT1@n1VtI5uYrpRt!cEy&B;M=_}$+CrpuCoLSgMMLL9u zH~TEO^N0kpk3*O~2<~ul=Z9N@FOZwWt;qx7glVZMLwgZ7K@8gcHsA&Aco>HHi)9%u i<3&tp1J(SFT7E$-8|c^uYW|xmXF{2$N}1=~NdEwPv%pjU literal 0 HcmV?d00001 diff --git a/app/__pycache__/camera_manager.cpython-312.pyc b/app/__pycache__/camera_manager.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cae822d940924697591788ae0d731ee0a44a871a GIT binary patch literal 4887 zcmai2eQZ;UlyinJUlqdr55k5$=0# zh-FaPvNh>okd8LAg#w~gQ_<3(R)eWj?H`>~Y5zDSQrx#pLn?9LFH+KJjQz87p8f2E zvh_+n_uO;uIq!a)^SkHzuU4y>K-n8=@b9-0@;x@R!eyA*I55LRARRif?6f$?18Nw2g4U}lcbG2x>#H3pt zR+h97!Elra#!GBQmfy!Z`RFESnNveYr(QWR_1?)896Oq7f5!|Vxq(djLKskXIbVr#Ey}&`sAn1V_an&7spV4Vjtj*q#NWD9~VQ-g6 zeVNTs?ejo&@SDvC=`fL1A(A-pWXH+c!J!mKt3^g#wSZKw@iH8Gx&-nyE>~a5u%MCE z7SO6m8>6;V7ea}^KwqBLC)+g+q}lJpNuWTJ=juR3j@P)rVvWny0xRgy(FB8FJfqhf zhar8$$+vBdNjoDke=z8EuW{8oYM%3lg-EaDXxrylS?{Xf-~hqn4I3PX9&d0untFPI z;&b8)JN&YH&FZzT)sH)Bc5K_Xv$fU{^dAr%Eu!y0#OY}6rjd~7Ucb^+?`mjRyUMk4 zRfD75+v%nLj9P4o7fktmUfCZBy8}`rT**i+{X`d<{C`k46{Nu+g6U1C^P3{5_t*dxbm>AJ4f8}5pN zi^MF=k+2LS>h|^Zh~Tx6a9H%=Y@7uYPg>z$@Jga5N`usZO;a$^p7j9!i$5W|Ws=#^wo+6KmuV)0AZ>=Ff!KTOdZ6l2;X zSm_qMf=DIB5beRB`;bvMNep&M=&p_j+uYr_y}j_oUNIPSZ;tq)Ay|s!4n>2q-;V&o>SzHE{Ik~uMfE(v4k`+s^E}JZU40~q`#AH2T z8MI9B6=QtG1Yb4ASIw|w(QYIo ziv!+ hkF?=Tx@9|aqN{y+^O^bGVa&@^+k1}cYjab2J=C)YT^psf^OBMU8Zsn#FJ z!ipv#M6)>;Xapm)vY-a*Dawu4INI<(cH}W_M6M1j$?+NoNN#$x<#)(g)<5^mG!^p5 z(=}^r1}HP;U_DTkRm(C$-lKONzc|N=rHaVQG^J&PEVdsu&SoB2oFyHz1{$JM2@eO;Nc!Lj5rn zZ)3(yqL+%)5w$^Y`opP#lT)vqyLx}Y5M$2jgGBR z^oX?1?N6?zuDqNc)#zw*7Agi6(Wt`t!<`X|QA6?8qqt0k>xoD*Mdw!XB-txRC66xx zFb8f=6kV4ntCOV|CyFi{>80qs6djl1oaiF#(2JBtsAAf32w@%JT2Cvn!<@!efJ-mp zvD6a@OQK@cIFH)Fh!SGE0}DlWRrIP_tZ+X3sX}xkX(>Q%Ke@%{pU4}`OBDTboUi@S zK+2W?8cr5hP8L-F0s>x@mTSv%Tr zwed>hXl=sz)QpZ@S@I)ci%Wmx$%4}1yrH~AMcsIT3ub$(pk&xGWJxSuf30Ssv2Coe zEwO%AqH_0m!JcGk#r<10`#r*%$|fxfPqYrUCKkCznGq>bwtC#sFj;8-&x{^AA4s^D zpEfVt>?B_smLheUwiM~UUS;26)BVM!2i}*#KNv^|9s;&!zXtLu12~>d#0Hp7$dr%r zfDl?%2xGLeIg45Xt7W^0iIPpure}g~ka-6(>$5s7vR`K6Ize9sMh?YVvsA(yUlP9R>N<$0mvK74#L2l7AMa`TpniN-XJ*VK^EsLF~=-%=1pdpVaTxs z9O+{|)&Zul3hNizJ|s?^!n*1e#;2Gv*Suel(7eOZuX_3e(?&>$#6vyw$(Vb-w?pV@ zIRj)fu8qNn&l{AUbY&IVz>b8;=Kb5`en0uPq~-LU(IumsuT)>#e`D3wQwPUNTK+9z zxW8hxGebr@)22e*oQ-fq5u_Zqt{tEH_%y(s!qG@1r07HbL!uBfJcX?%V%Fv9$v=@6 z+q7m{TIb{x4wfKO+!n?BtTz}Hw;&$Sg&@cv0euQPt(jd2M?)_FR2tGv+5~-R2>4AF z0`ljtKUUE^FBO{K+lmIFKwuwkEhxTG(sF*;2elV!$4gqiDY~(sZNTuIt$1=_#p(FS z_K^*vfkb7~jfI;MmQ6Qp#V0!lj|_0hJp1XEZ}X~>CFR4Bp-7^5`T5R~eIr{(>k@@e zCoE6jd|2-Os_v`BiNZYz%O2{0FZ<@k1?DYkB%ufWd_EM1$e4HIu})pI8dRqSuBP4` zO&xo6>hjy!W4n8OAT@F!_4{+FKOVpJ#hJU;2c!55$&A$=mX3W+mezut)&T(?@mM_t z31_MyZ9Ww{0gbyeuJ-UY&+q*eC`x$BIqWT2e!lPgfke@&aej5uR+!$3ENs9~+CoQ&aD}n;Ps-of*t7IQ`^xGOB}7^!RkjoxUq<=nCFoFtY+F zR{2v;nbZ9|s^g8{Pj1=@Ps9h~34YN}r=2~)BQSKBsQD&v50^(}K(IvAey9k{M>%W+ z6nPYa2|C1-c2Whqwx9HInG8iA#Xw43>`$G0|Mr*frvC7L`s%-f;0oK~go>^Jven6{ zmpZLS2|5LJ1JMqD4e;|0zrfcGPeZuxwO3Z`vH5%VaK9HVU?lgG8IGHCui|54g!{z$ zq4nCq1im|(2DT*&7flpaj}=yrtR5?LB`mJ{Gc02&`>xRbKt-jO*Q_Jg*o`{)7lUrX zX`1lmgQ=CrqnJIOP(+9Zk+*m}2czC#x`*OZnyy3Af@C|A9Y_!l=|&_DBzXBzJZP05 zBP|7Tl-y$%==1K?R_n`V_cCVvUgn-%r!Tr^X7v@byg^@jQpcdc{xD zXJH)1jkF%fjE-TLA6Om3&GLjP_#4UpJE{B!**!sa-!zpElunq+$4up?*WEBJ>o@$1 O`!&Oq-6KfVb^I4Zbig|R literal 0 HcmV?d00001 diff --git a/app/__pycache__/config.cpython-312.pyc b/app/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c15b2edb14f3610444e9df19fc4047a22984e670 GIT binary patch literal 1865 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 literal 0 HcmV?d00001 diff --git a/app/routes/__pycache__/main.cpython-312.pyc b/app/routes/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b6bc44bc68a821d5e7ea02da7638a803fdb4172 GIT binary patch literal 3198 zcmbVOU2Gf25#Iad9VLnhrP_-8LngMI(w0a&De6RZ)yS3t2T0NYP6LYtLa@A(MD~tU z_l}lKNP!9)HQS_hj5wAOBnT7a!H$eZK~h-xE9$=XMFbJZI4EdWym*P<8(g3>q0`%#e_(|37Sur5)y-0q%zr%>$EYd{&~j!muLYUY2KMiv?}6h zln9(;qrO55eDr^}`1$oKAFiGM$bW#wzbUAO2?l{9DP6^>XHvP;sEX6h%5+H{dUDo{|3Wv5rckzdmZ~5)rMp{& zJ&oMGPBVAV<~(h_Nt?ZcwxEck%w^v^YcHlqO*Y&eY~)*P{7p9e9c)@|v1x6x5l#l8 zZG|1MgVm2O)_(Vj2YRiv@T8V9eqHTcIs^H{ZH23JiYeixz+L)wZ&7lk7kPg4A5vYHfHKt@&4}N z-fq3OTZwcZ?dyJ~ulrb(wR~yHA;W4-7ch{{Oi~7lR+~MM)NOWI!BN#rrZg?-7+^v% zOtxonx%`AB873YfwwD-84rLI+MQ#qo$MU)wp8&>AsG1fZ%%=-FEMvs=f@WqMZ^?0- z&#GxNd18O;Ks+@*9>@8Dse)1}lZ%a?v;=yN;x4R?W(*S&gG?lCrPv`YpH68;e~c>K zPZs|PbTjCC5w$sOR+Sv+fD1cB!o20>1z^S&egN)jr7No!7FJ(5Q~UFq9&&`$Mj@S64a1VXT{n6REUXL@x^;iou7XpYq`WVX6?yAj?s%{OU_X3(9R z;@zB3d<}a=8e!AyFjC}c{w4mHGfprQXp$R76HL_aFyYMVAK&vhu=d4;+Sw~Ks#d_^ zK(3(wO2w9-XC_tUd5Ad)x5X8(_N4nqP%+Nq%&UO(vr!2X04#~547q(R4kVh3R$C*D zl3-6sWXVI5X?5Jp5KSnCQNiNjMo-z)+6k_3g!p4rgMc3)&CcI|9ean7#eH|@+a~31 z@MN5Vj?(SEt;@l1B^a&-A1v|LIyz^Mo;y1C%W6loBwy>?{*Ls9R1PmZTD1?ns> z4>2pD$a2Ug=;BYSp{GhmR@%GD=E9N6w#TaNk1w@8{_mAw=f4m`bYZ6OiLrR#;}ex_ z{nhrL{jGheeekQ+!T;1bGJliSl23OH>_As{$cJU_>YmQSJa@I90r~H|0DaL*uNu){dZR*+8#m)UXJ~yOoLl|<%e6mTTzlj5+Ly1>aJyANpnI^B zpRx14QBd(dFum7ZLmeCdMdKmpylmf73GJzden@kDPA(r@+PbeA>{*g}Xs(lIoKuydW|gb0}p*NIg3JCjSn-bHa?a%Q+HJVA7ZsEYxHXbmx* zT4}m`qC7^l;_IyJXzj04vA)G!XsSF?K2DTk#8od@4%9)Sa75cIUoxrW)5)ae13-Zm zqxO?R7$MIy`;9@e#ZJW#AQp47*?E^>QY0-I^6_b~hxC?`$s9asz@tb!LPFEqlK^!I z(N$^E-INaLd8MGK{rDhEk>Jxu18|vP)>)R}H$=n)zeYR$i5{(>N54ir-^g91t;=#( cMedq=tSaxG5!Xc&3cnVfY1`l*X6z&V57tWsf&c&j literal 0 HcmV?d00001 diff --git a/app/routes/main.py b/app/routes/main.py new file mode 100644 index 0000000..2b80184 --- /dev/null +++ b/app/routes/main.py @@ -0,0 +1,53 @@ +""" +主路由 +""" +import logging +from datetime import datetime +from flask import render_template, jsonify, request +from app.camera_manager import CameraManager + +logger = logging.getLogger(__name__) + +# 创建摄像头管理器实例(全局) +camera_manager = CameraManager() + +def register_main_routes(app): + """注册主路由到Flask应用""" + + @app.route('/') + def index(): + """主页面 - 显示6个摄像头的网格布局""" + cameras = camera_manager.get_all_cameras() + return render_template('index.html', + cameras=cameras, + current_time=datetime.now().strftime('%Y-%m-%d %H:%M:%S')) + + @app.route('/api/cameras') + def get_cameras(): + """获取摄像头列表API""" + cameras = camera_manager.get_all_cameras() + return jsonify(cameras) + + @app.route('/api/refresh/', methods=['POST']) + def refresh_camera(camera_id): + """刷新指定摄像头""" + success = camera_manager.refresh_camera(camera_id) + return jsonify({'success': success, 'camera_id': camera_id}) + + @app.route('/api/switch', methods=['POST']) + def switch_camera(): + """切换摄像头编号""" + data = request.get_json() + camera_id = data.get('camera_id') + camera_number = data.get('camera_number', 'mixed') + try: + url = camera_manager.get_camera_url(camera_id, camera_number) + return jsonify({'success': True, 'url': url}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}), 400 + + @app.route('/api/login', methods=['POST']) + def login(): + """手动登录""" + success = camera_manager.login() + return jsonify({'success': success}) \ No newline at end of file diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000..22dfcd3 --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,53 @@ + + + + + + 多摄像头实时监控 - AdaOps + + + +

+ +
+ 时间: {{ current_time }} +
+
+ +
+ {% for camera in cameras %} +
+
+
{{ camera.name }}
+
✅ 在线
+
+
+ +
+
+ + + + + + + + + + + +
+
+ {% endfor %} +
+ + + + \ No newline at end of file diff --git a/multi_camera.log b/multi_camera.log new file mode 100644 index 0000000..03affee --- /dev/null +++ b/multi_camera.log @@ -0,0 +1,2 @@ +2025-12-07 01:58:44,962 - INFO - 多摄像头监控系统启动 +2025-12-07 02:06:10,241 - INFO - 多摄像头监控系统启动 diff --git a/requirements.txt b/requirements.txt index ebc1cd5..f0c6928 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ Flask==2.3.3 -requests==2.31.0 \ No newline at end of file +requests==2.31.0 +python-dotenv==1.0.0 +psutil==5.9.6 \ No newline at end of file diff --git a/run.py b/run.py new file mode 100644 index 0000000..c464c8b --- /dev/null +++ b/run.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +""" +多摄像头监控系统启动脚本 +""" +import os +import sys +from app import create_app + +if __name__ == '__main__': + app = create_app() + + from app.config import HOST, PORT, DEBUG + + print("=" * 50) + print("🚀 多摄像头网格布局系统启动") + print(f"📡 访问地址: http://{HOST}:{PORT}") + print(f"🎥 摄像头数量: 6个") + print(f"🔧 调试模式: {DEBUG}") + print("=" * 50) + + app.run(host=HOST, port=PORT, debug=DEBUG) \ No newline at end of file diff --git a/start.sh b/start.sh index 0556c77..0968e62 100755 --- a/start.sh +++ b/start.sh @@ -24,6 +24,18 @@ if [ $? -ne 0 ]; then pip3 install requests==2.31.0 fi +python3 -c "import dotenv" 2>/dev/null +if [ $? -ne 0 ]; then + echo "📥 安装python-dotenv..." + pip3 install python-dotenv==1.0.0 +fi + +python3 -c "import psutil" 2>/dev/null +if [ $? -ne 0 ]; then + echo "📥 安装psutil..." + pip3 install psutil==5.9.6 +fi + # 启动应用 echo "🎬 启动应用..." -python3 complete_multi_camera_app.py \ No newline at end of file +python3 run.py \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..937c64d --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,214 @@ +/* 多摄像头监控系统样式 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} +body { + font-family: Arial, sans-serif; + background: #1a1a1a; + color: white; + overflow-x: hidden; +} +.controls { + background: #3a3a3a; + padding: 8px 12px; + display: flex; + gap: 8px; + flex-wrap: wrap; + border-bottom: 1px solid #555; + position: sticky; + top: 0; + z-index: 1000; +} +.btn { + padding: 10px 15px; + border: none; + border-radius: 5px; + background: #4CAF50; + color: white; + cursor: pointer; + font-weight: bold; + transition: all 0.3s; +} +.btn:hover { + background: #45a049; + transform: translateY(-2px); +} +.btn-refresh { + background: #2196F3; +} +.btn-refresh:hover { + background: #1976D2; +} +.camera-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 0; + padding: 0; + max-width: 100%; + height: calc(100vh - 80px); +} +.camera-item { + background: #2d2d2d; + border-radius: 0; + overflow: hidden; + box-shadow: none; + transition: all 0.3s ease; + border: 1px solid #444; + position: relative; +} +.camera-item:hover { + transform: none; + box-shadow: inset 0 0 0 3px #4CAF50; + border-color: #4CAF50; + z-index: 10; +} +.camera-header { + background: #3a3a3a; + padding: 8px 12px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #555; +} +.camera-title { + font-weight: bold; + color: #4CAF50; +} +.camera-status { + font-size: 12px; + padding: 3px 8px; + border-radius: 10px; + background: #4CAF50; +} +.camera-frame-container { + position: relative; + width: 100%; + height: calc(100% - 80px); + background: #000; +} +.camera-frame { + width: 100%; + height: 100%; + border: none; + background: #000; + object-fit: contain; +} +.camera-controls { + display: none; +} +.cam-btn { + padding: 5px 10px; + border: none; + border-radius: 3px; + background: #555; + color: white; + cursor: pointer; + font-size: 12px; + transition: all 0.3s; + min-width: 30px; +} +.cam-btn:hover { + background: #666; + transform: scale(1.1); +} +.cam-btn.active { + background: #4CAF50; + transform: scale(1.1); + box-shadow: 0 0 5px rgba(76, 175, 80, 0.5); +} +.cam-btn-fullscreen { + background: #FF9800; +} +.cam-btn-fullscreen:hover { + background: #F57C00; +} +.camera-controls-combined { + background: #3a3a3a; + padding: 6px; + display: flex; + gap: 3px; + justify-content: center; + align-items: center; + flex-wrap: wrap; + border-top: 1px solid #555; +} +.selector-btn { + padding: 4px 8px; + border: none; + border-radius: 2px; + background: #666; + color: white; + cursor: pointer; + font-size: 11px; + transition: all 0.2s; + min-width: 25px; +} +.selector-btn:hover { + background: #777; + transform: scale(1.05); +} +.selector-btn.active { + background: #2196F3; + transform: scale(1.1); + box-shadow: 0 0 3px rgba(33, 150, 243, 0.5); +} +.info-panel { + position: fixed; + top: 5px; + left: 5px; + background: rgba(45, 45, 45, 0.9); + padding: 5px 8px; + border-radius: 4px; + font-size: 9px; + max-width: 180px; + backdrop-filter: blur(5px); + z-index: 999; + border: 1px solid #555; + box-shadow: 0 2px 8px rgba(0,0,0,0.3); + line-height: 1.2; +} +.status-info { + display: flex; + gap: 15px; + align-items: center; + margin-left: auto; + font-size: 12px; + color: #ccc; +} +.status-item { + display: flex; + align-items: center; + gap: 5px; +} +.status-item strong { + color: #4CAF50; +} +.info-item { + margin: 5px 0; +} +.info-item strong { + color: #4CAF50; +} +@media (max-width: 1200px) { + .camera-grid { + grid-template-columns: repeat(2, 1fr); + } +} +@media (max-width: 768px) { + .camera-grid { + grid-template-columns: 1fr; + padding: 10px; + } + .controls { + flex-direction: column; + } + .info-panel { + position: relative; + top: auto; + left: auto; + max-width: 100%; + margin: 10px; + } +} \ No newline at end of file diff --git a/static/js/app.js b/static/js/app.js new file mode 100644 index 0000000..b5b7ff4 --- /dev/null +++ b/static/js/app.js @@ -0,0 +1,105 @@ +// 多摄像头监控系统前端逻辑 + +function refreshCamera(cameraId) { + const frame = document.getElementById(`frame-${cameraId}`); + const status = document.getElementById(`status-${cameraId}`); + + status.textContent = '🔄 刷新中...'; + + // 添加时间戳避免缓存 + const originalSrc = frame.src.split('?')[0]; + frame.src = originalSrc + `?t=${Date.now()}`; + + setTimeout(() => { + status.textContent = '✅ 在线'; + }, 2000); +} + +function refreshAllCameras() { + document.querySelectorAll('.camera-status').forEach(status => { + status.textContent = '🔄 刷新中...'; + }); + + for (let i = 1; i <= 6; i++) { + refreshCamera(i); + } +} + +function toggleFullscreen(cameraId) { + const frame = document.getElementById(`frame-${cameraId}`); + + if (!document.fullscreenElement) { + if (frame.requestFullscreen) { + frame.requestFullscreen(); + } else if (frame.webkitRequestFullscreen) { + frame.webkitRequestFullscreen(); + } else if (frame.msRequestFullscreen) { + frame.msRequestFullscreen(); + } + } else { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } + } +} + +function switchCameraNumber(cameraId, cameraNumber) { + const frame = document.getElementById(`frame-${cameraId}`); + const controls = document.getElementById(`controls-${cameraId}`); + const buttons = controls.querySelectorAll('.selector-btn'); + + // 更新按钮状态 + buttons.forEach(btn => { + btn.classList.remove('active'); + }); + event.target.classList.add('active'); + + // 更新摄像头URL + const currentUrl = frame.src; + const baseUrl = currentUrl.split('?')[0]; + const params = new URLSearchParams(currentUrl.split('?')[1]); + const room = params.get('room'); + + // 构建新的URL + let newUrl; + if (cameraNumber === 'mixed') { + newUrl = `${baseUrl}?room=${room}&camera=mixed&t=${Date.now()}`; + } else { + newUrl = `${baseUrl}?room=${room}&camera=camera-${cameraNumber}&t=${Date.now()}`; + } + frame.src = newUrl; + + // 更新状态显示 + const status = document.getElementById(`status-${cameraId}`); + status.textContent = '🔄 切换中...'; + + setTimeout(() => { + status.textContent = '✅ 在线'; + }, 1000); +} + +function updateCurrentTime() { + const now = new Date(); + const timeString = now.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }); + document.getElementById('currentTime').textContent = timeString; +} + +// 每秒更新当前时间 +setInterval(updateCurrentTime, 1000); + +// 页面加载时更新时间 +window.onload = function() { + updateCurrentTime(); +}; \ No newline at end of file