初始版本
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
win.app/.build/
|
||||||
|
win.app/.update-files/
|
||||||
|
win.app/dist/
|
||||||
|
uploaded_firmware/
|
203
firmware_upgrade.html
Normal file
203
firmware_upgrade.html
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>设备固件更新</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', consolas, sans-serif;
|
||||||
|
background: #f4f6fb;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 90%;
|
||||||
|
min-width: 400px;
|
||||||
|
margin: 30px 30px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
|
||||||
|
padding: 32px 24px 24px 24px;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
text-align: center;
|
||||||
|
color: #2d3a4b;
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #4a5a6a;
|
||||||
|
}
|
||||||
|
input[type="file"] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
input[type="text"], textarea {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
background: #3b82f6;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
button:disabled {
|
||||||
|
background: #a5b4fc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.progress-bar {
|
||||||
|
width: 100%;
|
||||||
|
background: #e5e7eb;
|
||||||
|
border-radius: 4px;
|
||||||
|
height: 18px;
|
||||||
|
margin-top: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.progress {
|
||||||
|
height: 100%;
|
||||||
|
background: #16a34a;
|
||||||
|
width: 0%;
|
||||||
|
transition: width 0.3s;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
margin-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
color: #16a34a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h2>设备固件更新</h2>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="firmwareFile">选择固件文件</label>
|
||||||
|
<input type="file" id="firmwareFile" accept=".bin,.hex,.img">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="version">固件版本信息</label>
|
||||||
|
<textarea id="version" readonly style="background:#f3f4f6;height:100px;resize:vertical;"></textarea>
|
||||||
|
</div>
|
||||||
|
<button id="uploadBtn" disabled>升级</button>
|
||||||
|
<div class="progress-bar" id="progressBar">
|
||||||
|
<div class="progress" id="progress"></div>
|
||||||
|
</div>
|
||||||
|
<div class="status" id="status"></div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
// 页面加载时获取版本信息并设置为placeholder
|
||||||
|
window.addEventListener('DOMContentLoaded', function() {
|
||||||
|
fetch('/api/version_info').then(res => {
|
||||||
|
if (!res.ok) throw new Error('获取版本信息失败');
|
||||||
|
return res.text();
|
||||||
|
}).then(text => {
|
||||||
|
versionInput.placeholder = text;
|
||||||
|
}).catch(() => {
|
||||||
|
versionInput.placeholder = '获取版本信息失败';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const firmwareFile = document.getElementById('firmwareFile');
|
||||||
|
const versionInput = document.getElementById('version');
|
||||||
|
const uploadBtn = document.getElementById('uploadBtn');
|
||||||
|
const progress = document.getElementById('progress');
|
||||||
|
const status = document.getElementById('status');
|
||||||
|
|
||||||
|
function checkEnable() {
|
||||||
|
uploadBtn.disabled = !(firmwareFile.files.length > 0 && versionInput.value.trim() !== '');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
firmwareFile.addEventListener('change', function() {
|
||||||
|
checkEnable();
|
||||||
|
const file = firmwareFile.files[0];
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
const bytesToRead = 256;
|
||||||
|
const blob = file.slice(0, bytesToRead);
|
||||||
|
reader.onload = function(e) {
|
||||||
|
let text = '';
|
||||||
|
try {
|
||||||
|
// 优先用utf-8解码,支持中英文混合
|
||||||
|
text = new TextDecoder('utf-8').decode(new Uint8Array(e.target.result));
|
||||||
|
} catch (err) {
|
||||||
|
try {
|
||||||
|
text = new TextDecoder('gb18030').decode(new Uint8Array(e.target.result));
|
||||||
|
} catch (err2) {
|
||||||
|
// fallback: latin1
|
||||||
|
text = new TextDecoder('latin1').decode(new Uint8Array(e.target.result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 处理换行(\r\n/\r/\n)
|
||||||
|
text = text.replace(/\r\n|\r|\n/g, '\n');
|
||||||
|
versionInput.value = text;
|
||||||
|
checkEnable();
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(blob);
|
||||||
|
} else {
|
||||||
|
versionInput.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
versionInput.addEventListener('input', checkEnable);
|
||||||
|
|
||||||
|
uploadBtn.addEventListener('click', function() {
|
||||||
|
uploadBtn.disabled = true;
|
||||||
|
status.textContent = '';
|
||||||
|
progress.style.width = '0%';
|
||||||
|
const file = firmwareFile.files[0];
|
||||||
|
if (!file) {
|
||||||
|
status.textContent = '未选择固件文件';
|
||||||
|
uploadBtn.disabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const chunkSize = 128;
|
||||||
|
const totalChunks = Math.ceil(file.size / chunkSize);
|
||||||
|
let currentChunk = 0;
|
||||||
|
|
||||||
|
function sendChunk() {
|
||||||
|
if (currentChunk >= totalChunks) {
|
||||||
|
status.textContent = '升级完成!';
|
||||||
|
uploadBtn.disabled = false;
|
||||||
|
progress.style.width = '100%';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const start = currentChunk * chunkSize;
|
||||||
|
const end = Math.min(file.size, start + chunkSize);
|
||||||
|
const chunk = file.slice(start, end);
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('chunk', chunk);
|
||||||
|
formData.append('index', currentChunk);
|
||||||
|
formData.append('total', totalChunks);
|
||||||
|
// 可根据后端实际接口调整URL和参数
|
||||||
|
fetch('/api/upload_firmware', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}).then(res => {
|
||||||
|
if (!res.ok) throw new Error('网络错误');
|
||||||
|
return res.text();
|
||||||
|
}).then(() => {
|
||||||
|
currentChunk++;
|
||||||
|
progress.style.width = ((currentChunk / totalChunks) * 100) + '%';
|
||||||
|
status.textContent = `正在升级:${currentChunk}/${totalChunks}`;
|
||||||
|
sendChunk();
|
||||||
|
}).catch(err => {
|
||||||
|
status.textContent = '升级失败: ' + err.message;
|
||||||
|
uploadBtn.disabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sendChunk();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
55
firmware_upload_server.py
Normal file
55
firmware_upload_server.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
from flask import Flask, request, jsonify
|
||||||
|
import os
|
||||||
|
from flask import send_from_directory
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
UPLOAD_FOLDER = 'uploaded_firmware'
|
||||||
|
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
||||||
|
|
||||||
|
# 临时保存每个上传会话的分包
|
||||||
|
chunk_cache = {}
|
||||||
|
|
||||||
|
@app.route('/api/upload_firmware', methods=['POST'])
|
||||||
|
def upload_firmware():
|
||||||
|
chunk = request.files.get('chunk')
|
||||||
|
index = int(request.form.get('index', -1))
|
||||||
|
total = int(request.form.get('total', -1))
|
||||||
|
session_id = request.remote_addr # 简单用IP区分会话,实际可用token等
|
||||||
|
if chunk is None or index < 0 or total < 1:
|
||||||
|
return '参数错误', 400
|
||||||
|
|
||||||
|
# 缓存分包
|
||||||
|
if session_id not in chunk_cache:
|
||||||
|
chunk_cache[session_id] = [None] * total
|
||||||
|
chunk_cache[session_id][index] = chunk.read()
|
||||||
|
|
||||||
|
# 如果全部收到,合并保存
|
||||||
|
if all(part is not None for part in chunk_cache[session_id]):
|
||||||
|
firmware_data = b''.join(chunk_cache[session_id])
|
||||||
|
save_path = os.path.join(UPLOAD_FOLDER, f'firmware_{session_id}.bin')
|
||||||
|
with open(save_path, 'wb') as f:
|
||||||
|
f.write(firmware_data)
|
||||||
|
del chunk_cache[session_id]
|
||||||
|
return jsonify({'status': 'ok', 'msg': '全部分包已接收并保存'})
|
||||||
|
return jsonify({'status': 'ok', 'msg': f'分包{index+1}/{total}已接收'})
|
||||||
|
|
||||||
|
# 版本信息接口
|
||||||
|
@app.route('/api/version_info')
|
||||||
|
def version_info():
|
||||||
|
# 实际可从设备、数据库或文件读取,这里演示写死
|
||||||
|
verstr = 'Bootloader版本:V1.0\n固件版本:V1.3\n编译日期:2025.09.06\n固件日志:修复若干bug,提升稳定性。'
|
||||||
|
return verstr, 200, {'Content-Type': 'text/plain; charset=utf-8'}
|
||||||
|
|
||||||
|
# 静态页面路由
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return send_from_directory('.', 'firmware_upgrade.html')
|
||||||
|
|
||||||
|
@app.route('/<path:filename>')
|
||||||
|
def static_files(filename):
|
||||||
|
# 允许访问同目录下的静态资源(如js/css等)
|
||||||
|
return send_from_directory('.', filename)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host='0.0.0.0', port=5000, debug=True)
|
3
verInfo.md
Normal file
3
verInfo.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
版本:V1.0
|
||||||
|
时间:23:54:15 2025年9月5日
|
||||||
|
日志:https://kangax.github.io/html-minifier/
|
10
win.app/default.aproj
Normal file
10
win.app/default.aproj
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<project ver="10" name="aardio 工程" libEmbed="true" icon="..." ui="win" output="aardio 工程.exe" CompanyName="aardio" FileDescription="aardio 工程" LegalCopyright="Copyright (C) 作者 2025" ProductName="aardio 工程" InternalName="aardio 工程" FileVersion="0.0.0.4" ProductVersion="0.0.0.4" publishDir="/dist/" dstrip="false" local="false" ignored="false">
|
||||||
|
<file name="main" path="main.aardio"/>
|
||||||
|
<folder name="窗体" path="forms" comment="" embed="true" local="false" ignored="false"/>
|
||||||
|
<folder name="网页" path="web" embed="true" comment="目录" local="true">
|
||||||
|
<file name="index.html" path="web\index.html" comment="web\index.html"/>
|
||||||
|
<file name="index.js" path="web\index.js" comment="web\index.js"/>
|
||||||
|
</folder>
|
||||||
|
<folder name="网页源码" path="web.src" embed="false" comment="目录" local="false" ignored="true"/>
|
||||||
|
</project>
|
59
win.app/main.aardio
Normal file
59
win.app/main.aardio
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import win.ui;
|
||||||
|
/*DSG{{*/
|
||||||
|
mainForm = win.form(text="aardio 工程";right=759;bottom=531;max=false)
|
||||||
|
mainForm.add()
|
||||||
|
/*}}*/
|
||||||
|
|
||||||
|
import web.view.7;
|
||||||
|
var wb = web.view(mainForm);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import wsock.tcp.asynHttpServer;
|
||||||
|
var httpServer = wsock.tcp.asynHttpServer();
|
||||||
|
|
||||||
|
//这里可以指定 IP 和端口,不指定则自动分配空闲端口
|
||||||
|
httpServer.start("127.0.0.1");
|
||||||
|
|
||||||
|
//服务端 aardio 支持模板语法: doc://language-reference/templating/syntax.md
|
||||||
|
var url = httpServer.getUrl("/web/firmware_upgrade.html"); //参数支持 aardio 工程嵌入资源目录路径
|
||||||
|
|
||||||
|
/*
|
||||||
|
import web.view;
|
||||||
|
var wb = web.view(winform);
|
||||||
|
*/
|
||||||
|
|
||||||
|
//用浏览器组件打开网页试试
|
||||||
|
wb.go(url);
|
||||||
|
|
||||||
|
|
||||||
|
import fsys.update.simpleMain;
|
||||||
|
if( fsys.update.simpleMain(
|
||||||
|
"aardio 工程",
|
||||||
|
"https://list.020824.xyz/d/Pics/FW/demo/v1/.update-files/version.txt?sign=SVvWq8CBYyAObIvQ9yC2yNC4XMik-QX2zMFs8UvCeGs=:0", /*网址也可以改用目录名,并使用服务端代码动态返回version.txt*/
|
||||||
|
"/download/update-files", /*绿色软件建议改为 io.appData("/软件厂商名/软件名字/update-files") */
|
||||||
|
function(version,description,status){
|
||||||
|
import console
|
||||||
|
console.log(version,description,status)
|
||||||
|
/*
|
||||||
|
version参数包含最新版本号,
|
||||||
|
description包含最新版本更新说明,
|
||||||
|
status参数值见下面的列表:
|
||||||
|
"ready": 下载已完成并准备更新,
|
||||||
|
"updated": 已更新到新版本并准备启动新版
|
||||||
|
"complete": 当前已更新并已启动新版本主程序,所有操作已完成
|
||||||
|
"latest": 已经是最新版不需要更新
|
||||||
|
"failed": 出错了,description参数为错误信息
|
||||||
|
|
||||||
|
这个回调函数不是必须的,
|
||||||
|
在此检测更新代码之前或之后都可以在界面线程调用
|
||||||
|
fsys.update.simpleMain.onStatusChanged 订阅更新状态变更信息。
|
||||||
|
该认阅回调函数的参数与上面的回调参数相同。
|
||||||
|
*/
|
||||||
|
} )){
|
||||||
|
return 0; //必须退出 main.aardio 以启动更新
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mainForm.show();
|
||||||
|
win.loopMessage();
|
203
win.app/web/firmware_upgrade.html
Normal file
203
win.app/web/firmware_upgrade.html
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>设备固件更新</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', consolas, sans-serif;
|
||||||
|
background: #f4f6fb;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 90%;
|
||||||
|
min-width: 400px;
|
||||||
|
margin: 30px 30px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
|
||||||
|
padding: 32px 24px 24px 24px;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
text-align: center;
|
||||||
|
color: #2d3a4b;
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #4a5a6a;
|
||||||
|
}
|
||||||
|
input[type="file"] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
input[type="text"], textarea {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
background: #3b82f6;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
button:disabled {
|
||||||
|
background: #a5b4fc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.progress-bar {
|
||||||
|
width: 100%;
|
||||||
|
background: #e5e7eb;
|
||||||
|
border-radius: 4px;
|
||||||
|
height: 18px;
|
||||||
|
margin-top: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.progress {
|
||||||
|
height: 100%;
|
||||||
|
background: #16a34a;
|
||||||
|
width: 0%;
|
||||||
|
transition: width 0.3s;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
margin-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
color: #16a34a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h2>设备固件更新</h2>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="firmwareFile">选择固件文件</label>
|
||||||
|
<input type="file" id="firmwareFile" accept=".bin,.hex,.img">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="version">固件版本信息</label>
|
||||||
|
<textarea id="version" readonly style="background:#f3f4f6;height:100px;resize:vertical;"></textarea>
|
||||||
|
</div>
|
||||||
|
<button id="uploadBtn" disabled>升级</button>
|
||||||
|
<div class="progress-bar" id="progressBar">
|
||||||
|
<div class="progress" id="progress"></div>
|
||||||
|
</div>
|
||||||
|
<div class="status" id="status"></div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
// 页面加载时获取版本信息并设置为placeholder
|
||||||
|
window.addEventListener('DOMContentLoaded', function() {
|
||||||
|
fetch('/api/version_info').then(res => {
|
||||||
|
if (!res.ok) throw new Error('获取版本信息失败');
|
||||||
|
return res.text();
|
||||||
|
}).then(text => {
|
||||||
|
versionInput.placeholder = text;
|
||||||
|
}).catch(() => {
|
||||||
|
versionInput.placeholder = '获取版本信息失败';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const firmwareFile = document.getElementById('firmwareFile');
|
||||||
|
const versionInput = document.getElementById('version');
|
||||||
|
const uploadBtn = document.getElementById('uploadBtn');
|
||||||
|
const progress = document.getElementById('progress');
|
||||||
|
const status = document.getElementById('status');
|
||||||
|
|
||||||
|
function checkEnable() {
|
||||||
|
uploadBtn.disabled = !(firmwareFile.files.length > 0 && versionInput.value.trim() !== '');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
firmwareFile.addEventListener('change', function() {
|
||||||
|
checkEnable();
|
||||||
|
const file = firmwareFile.files[0];
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
const bytesToRead = 256;
|
||||||
|
const blob = file.slice(0, bytesToRead);
|
||||||
|
reader.onload = function(e) {
|
||||||
|
let text = '';
|
||||||
|
try {
|
||||||
|
// 优先用utf-8解码,支持中英文混合
|
||||||
|
text = new TextDecoder('utf-8').decode(new Uint8Array(e.target.result));
|
||||||
|
} catch (err) {
|
||||||
|
try {
|
||||||
|
text = new TextDecoder('gb18030').decode(new Uint8Array(e.target.result));
|
||||||
|
} catch (err2) {
|
||||||
|
// fallback: latin1
|
||||||
|
text = new TextDecoder('latin1').decode(new Uint8Array(e.target.result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 处理换行(\r\n/\r/\n)
|
||||||
|
text = text.replace(/\r\n|\r|\n/g, '\n');
|
||||||
|
versionInput.value = text;
|
||||||
|
checkEnable();
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(blob);
|
||||||
|
} else {
|
||||||
|
versionInput.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
versionInput.addEventListener('input', checkEnable);
|
||||||
|
|
||||||
|
uploadBtn.addEventListener('click', function() {
|
||||||
|
uploadBtn.disabled = true;
|
||||||
|
status.textContent = '';
|
||||||
|
progress.style.width = '0%';
|
||||||
|
const file = firmwareFile.files[0];
|
||||||
|
if (!file) {
|
||||||
|
status.textContent = '未选择固件文件';
|
||||||
|
uploadBtn.disabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const chunkSize = 128;
|
||||||
|
const totalChunks = Math.ceil(file.size / chunkSize);
|
||||||
|
let currentChunk = 0;
|
||||||
|
|
||||||
|
function sendChunk() {
|
||||||
|
if (currentChunk >= totalChunks) {
|
||||||
|
status.textContent = '升级完成!';
|
||||||
|
uploadBtn.disabled = false;
|
||||||
|
progress.style.width = '100%';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const start = currentChunk * chunkSize;
|
||||||
|
const end = Math.min(file.size, start + chunkSize);
|
||||||
|
const chunk = file.slice(start, end);
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('chunk', chunk);
|
||||||
|
formData.append('index', currentChunk);
|
||||||
|
formData.append('total', totalChunks);
|
||||||
|
// 可根据后端实际接口调整URL和参数
|
||||||
|
fetch('/api/upload_firmware', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}).then(res => {
|
||||||
|
if (!res.ok) throw new Error('网络错误');
|
||||||
|
return res.text();
|
||||||
|
}).then(() => {
|
||||||
|
currentChunk++;
|
||||||
|
progress.style.width = ((currentChunk / totalChunks) * 100) + '%';
|
||||||
|
status.textContent = `正在升级:${currentChunk}/${totalChunks}`;
|
||||||
|
sendChunk();
|
||||||
|
}).catch(err => {
|
||||||
|
status.textContent = '升级失败: ' + err.message;
|
||||||
|
uploadBtn.disabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sendChunk();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Reference in New Issue
Block a user