feat:自动更换ip+流量监控
This commit is contained in:
1605
static/css/style.css
Normal file
1605
static/css/style.css
Normal file
File diff suppressed because it is too large
Load Diff
177
static/js/main.js
Normal file
177
static/js/main.js
Normal file
@@ -0,0 +1,177 @@
|
||||
/* ProxyAuto Pro - 主 JavaScript 文件 */
|
||||
|
||||
// Toast 通知
|
||||
function showToast(type, message, duration = 4000) {
|
||||
const container = document.getElementById('toast-container');
|
||||
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast ${type}`;
|
||||
toast.innerHTML = `
|
||||
<span class="toast-message">${escapeHtml(message)}</span>
|
||||
<button class="toast-close" onclick="this.parentElement.remove()">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"/>
|
||||
<line x1="6" y1="6" x2="18" y2="18"/>
|
||||
</svg>
|
||||
</button>
|
||||
`;
|
||||
|
||||
container.appendChild(toast);
|
||||
|
||||
// 自动移除
|
||||
setTimeout(() => {
|
||||
toast.style.opacity = '0';
|
||||
toast.style.transform = 'translateX(20px)';
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, duration);
|
||||
}
|
||||
|
||||
// HTML 转义
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// 通用 API 请求函数
|
||||
async function apiRequest(url, options = {}) {
|
||||
const defaultOptions = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
const mergedOptions = {
|
||||
...defaultOptions,
|
||||
...options,
|
||||
headers: {
|
||||
...defaultOptions.headers,
|
||||
...options.headers,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(url, mergedOptions);
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || `HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化日期时间
|
||||
function formatDateTime(isoString) {
|
||||
if (!isoString) return '--';
|
||||
|
||||
const date = new Date(isoString);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
});
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
function formatTime(isoString) {
|
||||
if (!isoString) return '--';
|
||||
|
||||
const date = new Date(isoString);
|
||||
return date.toLocaleTimeString('zh-CN', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
});
|
||||
}
|
||||
|
||||
// 确认对话框
|
||||
function confirmAction(message) {
|
||||
return confirm(message);
|
||||
}
|
||||
|
||||
// 页面加载完成后的初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 可以在这里添加全局初始化逻辑
|
||||
console.log('ProxyAuto Pro initialized');
|
||||
});
|
||||
|
||||
// 移动端侧边栏切换
|
||||
function toggleSidebar() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const overlay = document.querySelector('.sidebar-overlay');
|
||||
if (sidebar) {
|
||||
sidebar.classList.toggle('open');
|
||||
if (overlay) {
|
||||
overlay.classList.toggle('active');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化倒计时
|
||||
function formatCountdown(seconds) {
|
||||
if (seconds <= 0) return '即将更换';
|
||||
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}时${minutes}分${secs}秒`;
|
||||
} else if (minutes > 0) {
|
||||
return `${minutes}分${secs}秒`;
|
||||
} else {
|
||||
return `${secs}秒`;
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化24小时制时间
|
||||
function formatTime24(isoString) {
|
||||
if (!isoString) return '--';
|
||||
|
||||
const date = new Date(isoString);
|
||||
const pad = n => n.toString().padStart(2, '0');
|
||||
|
||||
return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
||||
}
|
||||
|
||||
// 更新所有倒计时
|
||||
function updateCountdowns() {
|
||||
const countdownElements = document.querySelectorAll('[data-next-run]');
|
||||
const now = Date.now();
|
||||
|
||||
countdownElements.forEach(el => {
|
||||
const nextRunTime = new Date(el.dataset.nextRun).getTime();
|
||||
const remaining = Math.max(0, Math.floor((nextRunTime - now) / 1000));
|
||||
|
||||
const countdownSpan = el.querySelector('.countdown-value');
|
||||
if (countdownSpan) {
|
||||
countdownSpan.textContent = formatCountdown(remaining);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 启动倒计时更新器
|
||||
let countdownInterval = null;
|
||||
function startCountdownUpdater() {
|
||||
if (countdownInterval) clearInterval(countdownInterval);
|
||||
updateCountdowns();
|
||||
countdownInterval = setInterval(updateCountdowns, 1000);
|
||||
}
|
||||
|
||||
// 最小加载时间包装器
|
||||
async function withMinLoadTime(promise, minMs = 500) {
|
||||
const start = Date.now();
|
||||
const result = await promise;
|
||||
const elapsed = Date.now() - start;
|
||||
if (elapsed < minMs) {
|
||||
await new Promise(resolve => setTimeout(resolve, minMs - elapsed));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user