596 lines
22 KiB
HTML
596 lines
22 KiB
HTML
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>Network Configuration</title>
|
||
<meta charset="utf-8">
|
||
<meta http-equiv="Content-Security-Policy" content="referrer no-referrer">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||
<style type="text/css">
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
margin: 0;
|
||
padding: 0;
|
||
background-color: #f0f0f0;
|
||
}
|
||
label {
|
||
display: block;
|
||
margin-bottom: 5px;
|
||
}
|
||
input {
|
||
width: 100%;
|
||
padding: 5px;
|
||
box-sizing: border-box;
|
||
border: 1px solid #ccc;
|
||
border-radius: 3px;
|
||
}
|
||
input[type="submit"] {
|
||
background-color: #007bff;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 3px;
|
||
padding: 10px;
|
||
cursor: pointer;
|
||
}
|
||
input[type="submit"]:hover {
|
||
background-color: #0056b3;
|
||
}
|
||
input[type="submit"]:disabled {
|
||
background-color: #ccc;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
#ap_list {
|
||
margin-top: 20px;
|
||
border-top: 1px solid #ccc;
|
||
padding-top: 10px;
|
||
}
|
||
#ap_list a {
|
||
display: block;
|
||
margin-top: 5px;
|
||
color: #007bff;
|
||
text-decoration: none;
|
||
}
|
||
#ap_list a:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.language-switch {
|
||
position: absolute;
|
||
top: 20px;
|
||
right: 20px;
|
||
}
|
||
.language-switch select {
|
||
padding: 5px;
|
||
border-radius: 3px;
|
||
border: 1px solid #ccc;
|
||
}
|
||
|
||
/* 标签页样式 */
|
||
.tabs {
|
||
display: flex;
|
||
margin-top: 20px;
|
||
margin-bottom: 0;
|
||
padding-left: 20px;
|
||
border-bottom: 1px solid #ccc;
|
||
}
|
||
.tab {
|
||
padding: 10px 20px;
|
||
cursor: pointer;
|
||
border: 1px solid #ccc;
|
||
border-bottom: none;
|
||
border-radius: 5px 5px 0 0;
|
||
margin-right: 5px;
|
||
background-color: #f0f0f0;
|
||
position: relative;
|
||
top: 1px;
|
||
}
|
||
.tab.active {
|
||
background-color: #fff;
|
||
}
|
||
.tab-content {
|
||
display: none;
|
||
border-top: none;
|
||
padding: 20px;
|
||
background-color: #fff;
|
||
}
|
||
.tab-content.active {
|
||
display: block;
|
||
}
|
||
|
||
#remember_bssid, #sleep_mode {
|
||
width: auto;
|
||
}
|
||
|
||
/* 提示框样式 */
|
||
.toast {
|
||
position: fixed;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
background-color: rgba(0, 0, 0, 0.8);
|
||
color: white;
|
||
padding: 15px 30px;
|
||
border-radius: 8px;
|
||
z-index: 1000;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease-in-out;
|
||
min-width: 200px;
|
||
text-align: center;
|
||
}
|
||
.toast.show {
|
||
opacity: 1;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="toast" id="toast"></div>
|
||
<div class="language-switch">
|
||
<select id="language" onchange="changeLanguage()">
|
||
<option value="en-US">English</option>
|
||
<option value="zh-CN">简体中文</option>
|
||
<option value="zh-TW">繁體中文</option>
|
||
<option value="ja-JP">日本語</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="tabs">
|
||
<div class="tab active" onclick="switchTab('wifi')" data-lang="wifi_tab">WiFi 配置</div>
|
||
<div class="tab" onclick="switchTab('advanced')" data-lang="advanced_tab">高级选项</div>
|
||
</div>
|
||
|
||
<div id="wifi-tab" class="tab-content active">
|
||
<form action="/submit" method="post" onsubmit="submitForm(event)">
|
||
<div id="saved_list_container" style="display: none;">
|
||
<h3 data-lang="saved_wifi">已保存的 WiFi</h3>
|
||
<ul id="saved_list">
|
||
<li>
|
||
<span>SSID</span>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<div>
|
||
<h3 data-lang="new_wifi">新的 WiFi</h3>
|
||
<p class="error" style="color: red; text-align: center;" id="error">
|
||
</p>
|
||
<p>
|
||
<label for="ssid">SSID:</label>
|
||
<input type="text" id="ssid" name="ssid" required>
|
||
</p>
|
||
<p>
|
||
<label for="password" data-lang="password">密码:</label>
|
||
<input type="password" id="password" name="password">
|
||
</p>
|
||
<p style="text-align: center;">
|
||
<input type="submit" value="连接" id="button" data-lang-value="connect">
|
||
</p>
|
||
<p id="ap_list">
|
||
</p>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div id="advanced-tab" class="tab-content">
|
||
<form action="/advanced/submit" method="post" onsubmit="submitAdvancedForm(event)">
|
||
<div>
|
||
<h3 data-lang="advanced_tab">高级选项</h3>
|
||
<p class="error" style="color: red; text-align: center;" id="advanced_error"></p>
|
||
|
||
<p>
|
||
<label for="ota_url" data-lang="ota_url">OTA服务器地址:</label>
|
||
<div style="display: flex; align-items: center; gap: 10px;">
|
||
<input type="text" id="ota_url" name="ota_url" style="flex: 1;">
|
||
<button type="button" onclick="clearOtaUrl()" style="padding: 5px 10px; border: 1px solid #ccc; border-radius: 3px; background-color: #f0f0f0; cursor: pointer;">❌</button>
|
||
</div>
|
||
</p>
|
||
|
||
<p style="display: flex; align-items: center; gap: 10px;">
|
||
<label for="max_tx_power" data-lang="max_tx_power" style="white-space: nowrap;">Wi-Fi发射功率:</label>
|
||
<select id="max_tx_power" name="max_tx_power">
|
||
<option value="8">2 dBm</option>
|
||
<option value="20">5 dBm</option>
|
||
<option value="28">7 dBm</option>
|
||
<option value="34">8 dBm</option>
|
||
<option value="44">11 dBm</option>
|
||
<option value="52">13 dBm</option>
|
||
<option value="56">14 dBm</option>
|
||
<option value="60">15 dBm</option>
|
||
<option value="66">16 dBm</option>
|
||
<option value="72">18 dBm</option>
|
||
<option value="80">20 dBm</option>
|
||
</select>
|
||
</p>
|
||
|
||
<p style="display: flex; align-items: center; gap: 10px;">
|
||
<label for="remember_bssid" style="margin: 0;">
|
||
<span data-lang="remember_bssid">连接 Wi-Fi 时记住 BSSID</span>
|
||
</label>
|
||
<input type="checkbox" id="remember_bssid" name="remember_bssid">
|
||
</p>
|
||
|
||
<p style="display: flex; align-items: center; gap: 10px;">
|
||
<label for="sleep_mode" style="margin: 0;">
|
||
<span data-lang="sleep_mode">启用睡眠模式</span>
|
||
</label>
|
||
<input type="checkbox" id="sleep_mode" name="sleep_mode">
|
||
</p>
|
||
|
||
<p style="text-align: center;">
|
||
<input type="submit" value="保存" id="advanced_button" data-lang-value="save">
|
||
</p>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<script type="text/javascript">
|
||
const button = document.getElementById('button');
|
||
const error = document.getElementById('error');
|
||
const ssid = document.getElementById('ssid');
|
||
|
||
// Add language translations
|
||
const translations = {
|
||
'zh-CN': {
|
||
title: '网络配置',
|
||
saved_wifi: '已保存的 Wi-Fi',
|
||
new_wifi: '新的 Wi-Fi',
|
||
password: '密码:',
|
||
connect: '连接',
|
||
select_wifi: '从下面列表选择 2.4G Wi-Fi:',
|
||
wifi_tab: 'Wi-Fi 配置',
|
||
advanced_tab: '高级选项',
|
||
ota_url: '自定义 OTA 地址:',
|
||
max_tx_power: 'Wi-Fi 最大发射功率:',
|
||
remember_bssid: '连接 Wi-Fi 时记住 BSSID',
|
||
sleep_mode: '启用睡眠模式',
|
||
save: '保存'
|
||
},
|
||
'zh-TW': {
|
||
title: '網路設定',
|
||
saved_wifi: '已儲存的 Wi-Fi',
|
||
new_wifi: '新的 Wi-Fi',
|
||
password: '密碼:',
|
||
connect: '連接',
|
||
select_wifi: '從下方列表選擇 2.4G Wi-Fi:',
|
||
wifi_tab: 'Wi-Fi 設定',
|
||
advanced_tab: '進階選項',
|
||
ota_url: '自定義 OTA 位址:',
|
||
max_tx_power: 'Wi-Fi 最大發射功率:',
|
||
remember_bssid: '連接 Wi-Fi 時記住 BSSID',
|
||
sleep_mode: '啟用睡眠模式',
|
||
save: '儲存'
|
||
},
|
||
'en-US': {
|
||
title: 'Network Configuration',
|
||
saved_wifi: 'Saved Wi-Fi',
|
||
new_wifi: 'New Wi-Fi',
|
||
password: 'Password:',
|
||
connect: 'Connect',
|
||
select_wifi: 'Select an 2.4G Wi-Fi from the list below:',
|
||
wifi_tab: 'Wi-Fi Config',
|
||
advanced_tab: 'Advanced',
|
||
ota_url: 'Custom OTA URL:',
|
||
max_tx_power: 'Wi-Fi Max TX Power:',
|
||
remember_bssid: 'Remember BSSID when connecting to Wi-Fi',
|
||
sleep_mode: 'Enable Sleep Mode',
|
||
save: 'Save'
|
||
},
|
||
'ja-JP': {
|
||
title: 'ネットワーク設定',
|
||
saved_wifi: '保存済みのWi-Fi',
|
||
new_wifi: '新しいWi-Fi',
|
||
password: 'パスワード:',
|
||
connect: '接続',
|
||
select_wifi: '以下のリストから2.4G Wi-Fiを選択してください:',
|
||
wifi_tab: 'Wi-Fi設定',
|
||
advanced_tab: '詳細設定',
|
||
ota_url: '自定義 OTA 位址:',
|
||
max_tx_power: 'Wi-Fi最大送信電力:',
|
||
remember_bssid: 'Wi-Fi接続時にBSSIDを記憶する',
|
||
sleep_mode: 'スリープモードを有効にする',
|
||
save: '保存'
|
||
}
|
||
};
|
||
|
||
function changeLanguage() {
|
||
const lang = document.getElementById('language').value;
|
||
// 检查语言值是否合法
|
||
if (!translations[lang]) {
|
||
console.warn(`不支持的语言: ${lang},默认使用中文`);
|
||
document.getElementById('language').value = 'zh-CN';
|
||
return changeLanguage();
|
||
}
|
||
// Set page title
|
||
document.title = translations[lang].title;
|
||
document.querySelectorAll('[data-lang]').forEach(element => {
|
||
const key = element.getAttribute('data-lang');
|
||
element.textContent = translations[lang][key];
|
||
});
|
||
document.querySelectorAll('[data-lang-value]').forEach(element => {
|
||
const key = element.getAttribute('data-lang-value');
|
||
element.value = translations[lang][key];
|
||
});
|
||
// Update AP list text
|
||
const apList = document.getElementById('ap_list');
|
||
if (apList.firstChild) {
|
||
apList.firstChild.textContent = translations[lang].select_wifi;
|
||
}
|
||
// Save language preference
|
||
localStorage.setItem('preferred_language', lang);
|
||
}
|
||
|
||
function renderSavedList(data) {
|
||
const savedListContainer = document.getElementById('saved_list_container');
|
||
const savedList = document.getElementById('saved_list');
|
||
savedList.innerHTML = '';
|
||
data.forEach((ssid, index) => {
|
||
const li = document.createElement('li');
|
||
let html = `<span>${ssid}</span>`;
|
||
// Only add priority and delete buttons after the first item
|
||
if (index > 0) {
|
||
html += ` <span>
|
||
<button type="button" onclick="setDefaultItem(this, ${index})">⬆️</button>
|
||
<button type="button" onclick="deleteItem(this, ${index})">❌</button>
|
||
</span>`;
|
||
} else {
|
||
html += ` <span><button type="button" onclick="deleteItem(this, ${index})">❌</button></span>`;
|
||
}
|
||
li.innerHTML = html;
|
||
savedList.appendChild(li);
|
||
});
|
||
if (data.length > 0) {
|
||
savedListContainer.style.display = 'block';
|
||
} else {
|
||
savedListContainer.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// Delete one item from the list
|
||
function deleteItem(item, index) {
|
||
// disable the button
|
||
item.disabled = true;
|
||
// /saved/delete?index=INDEX
|
||
fetch('/saved/delete?index=' + index)
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
loadSavedList();
|
||
});
|
||
}
|
||
|
||
function setDefaultItem(item, index) {
|
||
item.disabled = true;
|
||
fetch('/saved/set_default?index=' + index)
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
loadSavedList();
|
||
});
|
||
}
|
||
|
||
// Load saved ssid and password list
|
||
function loadSavedList() {
|
||
fetch('/saved/list')
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
renderSavedList(data);
|
||
});
|
||
}
|
||
|
||
// Load AP list from /scan
|
||
function loadAPList() {
|
||
if (button.disabled) {
|
||
return;
|
||
}
|
||
|
||
fetch('/scan')
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
const lang = document.getElementById('language').value;
|
||
const apList = document.getElementById('ap_list');
|
||
apList.innerHTML = '<p>' + translations[lang].select_wifi + '</p>';
|
||
data.forEach(ap => {
|
||
// Create a link for each AP
|
||
const link = document.createElement('a');
|
||
link.href = '#';
|
||
link.textContent = ap.ssid + ' (' + ap.rssi + ' dBm)';
|
||
if (ap.authmode === 0) {
|
||
link.textContent += ' 🌐';
|
||
} else {
|
||
link.textContent += ' 🔒';
|
||
}
|
||
link.addEventListener('click', () => {
|
||
ssid.value = ap.ssid;
|
||
});
|
||
apList.appendChild(link);
|
||
});
|
||
setTimeout(loadAPList, 5000);
|
||
})
|
||
.catch(error => {
|
||
console.error('Error:', error);
|
||
});
|
||
}
|
||
|
||
// Set initial language
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
// 从 URL 参数中获取语言设置
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const langParam = urlParams.get('lang');
|
||
|
||
// 获取浏览器语言
|
||
const browserLang = navigator.language || navigator.userLanguage;
|
||
|
||
// 语言映射表,将浏览器语言代码映射到支持的语言代码
|
||
const languageMap = {
|
||
'zh': 'zh-CN', // 简体中文
|
||
'zh-CN': 'zh-CN', // 简体中文
|
||
'zh-TW': 'zh-TW', // 繁体中文
|
||
'zh-HK': 'zh-TW', // 繁体中文(香港)
|
||
'ja': 'ja-JP', // 日语
|
||
'ja-JP': 'ja-JP', // 日语
|
||
'en': 'en-US', // 英语
|
||
'en-US': 'en-US', // 英语
|
||
'en-GB': 'en-US', // 英语(英国)
|
||
'en-CA': 'en-US', // 英语(加拿大)
|
||
'en-AU': 'en-US' // 英语(澳大利亚)
|
||
};
|
||
|
||
// 获取支持的语言代码
|
||
const getSupportedLanguage = (lang) => {
|
||
// 首先尝试完全匹配
|
||
if (languageMap[lang]) {
|
||
return languageMap[lang];
|
||
}
|
||
// 然后尝试只匹配主语言代码(例如 'zh' 而不是 'zh-CN')
|
||
const mainLang = lang.split('-')[0];
|
||
if (languageMap[mainLang]) {
|
||
return languageMap[mainLang];
|
||
}
|
||
// 如果都不匹配,返回英语
|
||
return 'en-US';
|
||
};
|
||
|
||
// 优先使用 URL 参数的语言设置,其次是本地存储的设置,最后是浏览器语言设置
|
||
const savedLang = langParam ||
|
||
localStorage.getItem('preferred_language') ||
|
||
getSupportedLanguage(browserLang);
|
||
|
||
document.getElementById('language').value = savedLang;
|
||
changeLanguage();
|
||
loadSavedList();
|
||
loadAPList();
|
||
loadAdvancedConfig();
|
||
});
|
||
|
||
// 监听 pageshow 事件以处理浏览器返回键
|
||
window.addEventListener('pageshow', (event) => {
|
||
if (event.persisted) {
|
||
loadSavedList();
|
||
} else {
|
||
// 正常加载时已处理
|
||
}
|
||
});
|
||
|
||
async function submitForm(event) {
|
||
event.preventDefault();
|
||
button.disabled = true;
|
||
error.textContent = '';
|
||
|
||
const ssidValue = ssid.value;
|
||
const passwordValue = document.getElementById('password').value;
|
||
|
||
const payload = {
|
||
ssid: ssidValue,
|
||
password: passwordValue
|
||
};
|
||
|
||
try {
|
||
const response = await fetch('/submit', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(payload)
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (!data.success) {
|
||
throw new Error(data.error || '连接失败');
|
||
}
|
||
|
||
// 连接成功,跳转到完成页面
|
||
button.disabled = false;
|
||
window.location.href = '/done.html';
|
||
} catch (err) {
|
||
error.textContent = err.message;
|
||
button.disabled = false;
|
||
}
|
||
}
|
||
|
||
function switchTab(tabName) {
|
||
// 隐藏所有标签页内容
|
||
document.querySelectorAll('.tab-content').forEach(content => {
|
||
content.classList.remove('active');
|
||
});
|
||
// 移除所有标签页的active类
|
||
document.querySelectorAll('.tab').forEach(tab => {
|
||
tab.classList.remove('active');
|
||
});
|
||
// 显示选中的标签页内容
|
||
document.getElementById(tabName + '-tab').classList.add('active');
|
||
// 激活选中的标签
|
||
document.querySelector(`.tab[onclick="switchTab('${tabName}')"]`).classList.add('active');
|
||
}
|
||
|
||
async function submitAdvancedForm(event) {
|
||
event.preventDefault();
|
||
const advancedButton = document.getElementById('advanced_button');
|
||
const advancedError = document.getElementById('advanced_error');
|
||
advancedButton.disabled = true;
|
||
advancedError.textContent = '';
|
||
|
||
const config = {
|
||
ota_url: document.getElementById('ota_url').value,
|
||
max_tx_power: parseInt(document.getElementById('max_tx_power').value),
|
||
remember_bssid: document.getElementById('remember_bssid').checked,
|
||
sleep_mode: document.getElementById('sleep_mode').checked
|
||
};
|
||
|
||
try {
|
||
const response = await fetch('/advanced/submit', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(config)
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (!data.success) {
|
||
throw new Error(data.error || '保存失败');
|
||
}
|
||
|
||
advancedButton.disabled = false;
|
||
showToast('配置已保存');
|
||
} catch (err) {
|
||
advancedError.textContent = err.message;
|
||
advancedButton.disabled = false;
|
||
}
|
||
}
|
||
|
||
// 显示提示框
|
||
function showToast(message) {
|
||
const toast = document.getElementById('toast');
|
||
toast.textContent = message;
|
||
toast.classList.add('show');
|
||
setTimeout(() => {
|
||
toast.classList.remove('show');
|
||
}, 3000);
|
||
}
|
||
|
||
// 加载高级配置
|
||
async function loadAdvancedConfig() {
|
||
try {
|
||
const response = await fetch('/advanced/config');
|
||
const data = await response.json();
|
||
|
||
if (data.ota_url) {
|
||
document.getElementById('ota_url').value = data.ota_url;
|
||
}
|
||
if (data.max_tx_power) {
|
||
document.getElementById('max_tx_power').value = data.max_tx_power;
|
||
}
|
||
if (data.remember_bssid !== undefined) {
|
||
document.getElementById('remember_bssid').checked = data.remember_bssid;
|
||
}
|
||
if (data.sleep_mode !== undefined) {
|
||
document.getElementById('sleep_mode').checked = data.sleep_mode;
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading advanced config:', error);
|
||
}
|
||
}
|
||
|
||
// 清空OTA地址
|
||
function clearOtaUrl() {
|
||
document.getElementById('ota_url').value = '';
|
||
}
|
||
</script>
|
||
</body>
|
||
</html> |