From 566efbcf1f2757cb83f8eeefdfebe59f18df2fc2 Mon Sep 17 00:00:00 2001 From: kicer Date: Sun, 1 Feb 2026 23:00:05 +0800 Subject: [PATCH] =?UTF-8?q?web=E9=85=8D=E7=BD=AE=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E6=A1=86=E6=9E=B6=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/rom/nanoweb.py | 5 +- src/rom/www/css/micro.min.css | 1 + src/rom/www/index.html | 878 ++++++++++++---------------------- src/rom/www/js/micro.min.js | 68 +++ 4 files changed, 379 insertions(+), 573 deletions(-) create mode 100644 src/rom/www/css/micro.min.css create mode 100644 src/rom/www/js/micro.min.js diff --git a/src/rom/nanoweb.py b/src/rom/nanoweb.py index b75c701..5eb34ec 100644 --- a/src/rom/nanoweb.py +++ b/src/rom/nanoweb.py @@ -36,7 +36,7 @@ async def error(request, code, reason): await request.write(str(reason)) -async def send_file(request, filename, segment=64, binary=True): +async def send_file(request, filename, segment=512, binary=True): try: with open(filename, "rb" if binary else "r") as f: while True: @@ -91,7 +91,8 @@ class Nanoweb: handler = (request.url, handler) if isinstance(handler, str): - await write(request, "HTTP/1.1 200 OK\r\n\r\n") + await write(request, "HTTP/1.1 200 OK\r\n") + await write(request, "Cache-Control: max-age=3600\r\n\r\n") await send_file(request, handler) elif isinstance(handler, tuple): await write(request, "HTTP/1.1 200 OK\r\n\r\n") diff --git a/src/rom/www/css/micro.min.css b/src/rom/www/css/micro.min.css new file mode 100644 index 0000000..19a8204 --- /dev/null +++ b/src/rom/www/css/micro.min.css @@ -0,0 +1 @@ +.card,body{padding:20px}.card,h1{margin-bottom:20px}.form-control,.table{width:100%;font-size:14px}.btn,.form-control,.table{font-size:14px}.list,.progress{overflow:hidden}*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;line-height:1.5;background:#f8f9fa;color:#333;max-width:800px;margin:0 auto}.card{background:#fff;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,.05);border:1px solid #e9ecef}h1{font-size:24px;color:#2c3e50}h2{font-size:20px;margin:15px 0;color:#34495e}h3{font-size:16px;margin:10px 0;color:#7f8c8d}.btn{display:inline-block;padding:8px 16px;background:#3498db;color:#fff;border:none;border-radius:4px;cursor:pointer;text-decoration:none;transition:background .2s;margin:2px}.btn:hover{background:#2980b9}.btn:active{transform:translateY(1px)}.btn-success{background:#27ae60}.btn-success:hover{background:#219653}.btn-danger{background:#e74c3c}.btn-danger:hover{background:#c0392b}.btn-warning{background:#f39c12}.btn-warning:hover{background:#d68910}.btn-outline{background:0 0;border:1px solid #3498db;color:#3498db}.badge-info,.btn-outline:hover{background:#3498db;color:#fff}.list-item:hover,.table th,.table tr:hover,.table-striped tr:nth-child(2n){background:#f8f9fa}.form-group{margin-bottom:15px}.form-label{display:block;margin-bottom:5px;font-weight:500;color:#555}.form-control{padding:10px;border:1px solid #ddd;border-radius:4px;transition:border .2s}.alert,.list-item{padding:12px 15px}.form-control:focus{outline:0;border-color:#3498db;box-shadow:0 0 0 2px rgba(52,152,219,.2)}.form-control:disabled{background:#f8f9fa;cursor:not-allowed}.checkbox,.radio{display:flex;align-items:center;margin:5px 0;cursor:pointer}.alert,.table{margin:10px 0}.checkbox input,.radio input{margin-right:8px}.table{border-collapse:collapse}.table td,.table th{padding:10px;text-align:left;border-bottom:1px solid #eee}.table th{font-weight:600;color:#555}.list{list-style:none;border:1px solid #eee;border-radius:4px}.list-item{border-bottom:1px solid #eee;background:#fff}.list-item:last-child{border-bottom:none}.alert{border-radius:4px;border-left:4px solid}.alert-success{background:#d4edda;border-left-color:#27ae60;color:#155724}.alert-warning{background:#fff3cd;border-left-color:#f39c12;color:#856404}.alert-error{background:#f8d7da;border-left-color:#e74c3c;color:#721c24}.alert-info{background:#d1ecf1;border-left-color:#3498db;color:#0c5460}.badge{display:inline-block;padding:3px 8px;font-size:12px;border-radius:20px;background:#e9ecef;color:#495057;margin:0 2px}.badge-success{background:#27ae60;color:#fff}.badge-warning{background:#f39c12;color:#fff}.badge-danger{background:#e74c3c;color:#fff}.progress{height:20px;background:#f0f0f0;border-radius:10px;margin:10px 0}.progress-bar{height:100%;background:#3498db;transition:width .3s;border-radius:10px}.row{display:flex;flex-wrap:wrap;margin:0 -10px}.col{flex:1;padding:0 10px;min-width:200px}.text-center{text-align:center}.text-right{text-align:right}.text-muted{color:#6c757d}.mt-1{margin-top:5px}.mt-2{margin-top:10px}.mt-3{margin-top:20px}.mb-1{margin-bottom:5px}.mb-2{margin-bottom:10px}.mb-3{margin-bottom:20px}.p-1{padding:5px}.p-2{padding:10px}.p-3{padding:20px}.hidden{display:none!important}@media (max-width:768px){body{padding:10px}.card{padding:15px}.row{flex-direction:column}.col{width:100%}} diff --git a/src/rom/www/index.html b/src/rom/www/index.html index 48792f3..a105ed0 100644 --- a/src/rom/www/index.html +++ b/src/rom/www/index.html @@ -1,571 +1,307 @@ - - - - - - WS2桌面气象站 - - - - -
-
-

WS2桌面气象站

-
- -
- - - - - -
-
-
-
- -

系统信息

-
-
- 时间: - -
-
- 运行时间: - - -
-
- 可用内存: - - -
-
- UUID: - -
-
- 平台: - - -
-
- 版本: - - -
-
-
- - -
-

LCD显示设置

-
-
- - -
-
- -
- -
- - -% -
-
- -
- - -
- - - -

LCD数据内容

- - - - - -
属性
-
- - -
-

天气站配置

-
- - - - 可输入城市名称或城市ID(查看城市ID列表) - -
- -
- - - 留空表示不自动熄屏 -
- - - -

当前配置

- - - - - -
配置项
-
- - -
-

关于

-

- WS2是一款基于ESP8266的桌面气象站,能够实时显示天气信息、环境数据和时间。 -

- -
-
-

硬件规格

-
-
- 硬件平台: ESP8266 - WiFi -
-
- 显示屏: LCD 240x240 - 彩色 -
-
- 环境参数: - 温度、湿度、PM2.5、气压、AQI -
-
-
-
-

软件信息

-
-
- 固件: - - ws2-v1.3.0 - - 开源 -
-
- 协议: HTTP REST API -
-
- 更新频率: 每小时 -
-
-
-
- -

开放源码

- - kicer@Github: ws2 - - - kicer@Foresh: ws2(国内访问) - -

软件许可

-

本项目采用MIT许可证开源,欢迎自由使用和修改。

-
-
- - - - - +WS2桌面气象站

WS2桌面气象站

系统信息

时间:-
运行时间:-
可用内存:-
UUID:-
平台:-
版本:-

LCD显示设置

-% +

LCD数据内容

属性

天气站配置

+可输入城市名称或城市ID(查看城市ID列表) +
留空表示不自动熄屏

当前配置

配置项

关于

+WS2是一款基于ESP8266的桌面气象站,能够实时显示天气信息、环境数据和时间。 +

硬件规格

硬件平台: ESP8266 +WiFi
显示屏: LCD 240x240 +彩色
环境参数: +温度、湿度、PM2.5、气压、AQI +

软件信息

固件: +ws2-v1.3.0 +开源
协议: HTTP REST API +
更新频率: 每小时 +

开放源码

+kicer@Github: ws2 + +kicer@Foresh: ws2(国内访问)

软件许可

本项目采用MIT许可证开源,欢迎自由使用和修改。

diff --git a/src/rom/www/js/micro.min.js b/src/rom/www/js/micro.min.js new file mode 100644 index 0000000..ce20f48 --- /dev/null +++ b/src/rom/www/js/micro.min.js @@ -0,0 +1,68 @@ +const MicroWeb={$:t=>document.querySelector(t),$$:t=>document.querySelectorAll(t),show:t=>t.style.display="block",hide:t=>t.style.display="none",toggle:t=>t.style.display="none"===t.style.display?"block":"none",text(t,e){if(void 0===e)return t.textContent;t.textContent=e},html(t,e){if(void 0===e)return t.innerHTML;t.innerHTML=e},val(t,e){if(void 0===e)return t.value;t.value=e},addClass:(t,e)=>t.classList.add(e),removeClass:(t,e)=>t.classList.remove(e),hasClass:(t,e)=>t.classList.contains(e),on(t,e,r,i){"function"==typeof r?(i=r,t.addEventListener(e,i)):t.addEventListener(e,t=>{t.target.matches(r)&&i.call(t.target,t)})},ajax:{async get(t){try{let e=await fetch(t);return await e.text()}catch(r){return console.error("GET failed:",r),null}},async post(t,e){try{let r=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});return await r.text()}catch(i){return console.error("POST failed:",i),null}},async postForm(t,e){let r=new FormData(e);try{let i=await fetch(t,{method:"POST",body:r});return await i.text()}catch(o){return console.error("Form POST failed:",o),null}}},bind(t,e=""){Object.keys(t).forEach(r=>{let i=document.querySelectorAll(`[data-bind="${e}${r}"]`);i.forEach(e=>{e.textContent=t[r]})})},render:(t,e)=>t.replace(/\{\{(\w+)\}\}/g,(t,r)=>void 0!==e[r]?e[r]:""),chart:{createProgress(t,e,r=100,i={}){let o=i.width||"100%",a=i.height||"20px",l=i.color||"#007bff",n=i.bgColor||"#f0f0f0",d=e/r*100,s=` +
+
+
+
+ ${e} / ${r} (${d.toFixed(1)}%) + `;t.innerHTML=s},createBarChart(t,e,r={}){let i=Math.max(...e.values);r.width;let o=r.height||200,a=r.colors||["#007bff","#28a745","#ffc107","#dc3545"],l=`
`;e.values.forEach((t,r)=>{let n=e.labels?e.labels[r]:`Item ${r+1}`,d=a[r%a.length];l+=` +
+
+
+
+ ${n}
+ ${t} +
+
+ `}),l+="
",t.innerHTML=l},createGauge(t,e,r=100,i={}){let o=i.size||150,a=i.color||"#007bff",l=` +
+
+
+
+
+
+ ${e} +
+ ${i.label||""} +
+
+
+ `;t.innerHTML=l}},utils:{debounce(t,e){let r;return function i(...o){let a=()=>{clearTimeout(r),t(...o)};clearTimeout(r),r=setTimeout(a,e)}},formatBytes(t,e=2){if(0===t)return"0 Bytes";let r=Math.floor(Math.log(t)/Math.log(1024));return parseFloat((t/Math.pow(1024,r)).toFixed(e))+" "+["Bytes","KB","MB","GB"][r]},uuid:()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(t){let e=16*Math.random()|0;return("x"===t?e:3&e|8).toString(16)})}};window.mw=MicroWeb;