Files
2026-06-16 11:55:14 +08:00

510 lines
20 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Termination 4 远程控制系统</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.gvsds.com/font-awesome/css/all.min.css" rel="stylesheet">
<!-- 配置Tailwind主题 -->
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
primary: '#6C5CE7',
secondary: '#00F5D4',
accent: '#FF3E7E',
dark: '#0F172A',
darker: '#0A0F1C',
'dark-card': '#1E293B',
},
fontFamily: {
cyber: ['Orbitron', 'sans-serif'],
modern: ['Inter', 'sans-serif'],
},
animation: {
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
'float': 'float 6s ease-in-out infinite',
'glow': 'glow 2s ease-in-out infinite alternate',
'spin-slow': 'spin 2s linear infinite',
},
keyframes: {
float: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-10px)' },
},
glow: {
'0%': { boxShadow: '0 0 5px rgba(108, 92, 231, 0.5), 0 0 10px rgba(108, 92, 231, 0.3)' },
'100%': { boxShadow: '0 0 10px rgba(108, 92, 231, 0.8), 0 0 20px rgba(108, 92, 231, 0.5), 0 0 30px rgba(108, 92, 231, 0.3)' },
}
}
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.text-shadow {
text-shadow: 0 0 10px rgba(108, 92, 231, 0.7);
}
.text-shadow-glow {
text-shadow: 0 0 5px rgba(0, 245, 212, 0.8), 0 0 10px rgba(0, 245, 212, 0.5);
}
.bg-grid {
background-image:
linear-gradient(rgba(30, 41, 59, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(30, 41, 59, 0.1) 1px, transparent 1px);
background-size: 20px 20px;
}
.card-overlay {
background: linear-gradient(to bottom, rgba(14, 21, 38, 0) 0%, rgba(14, 21, 38, 0.8) 70%, rgba(14, 21, 38, 0.95) 100%);
}
.glass-effect {
backdrop-filter: blur(8px);
background: rgba(30, 41, 59, 0.6);
}
.refresh-pulse {
animation: refresh-pulse 2s infinite;
}
@keyframes refresh-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
}
</style>
<!-- 导入特殊字体 -->
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600;700;800;900&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- 自定义滚动条样式 -->
<style>
/* 整体滚动条 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
/* 滚动条轨道 */
::-webkit-scrollbar-track {
background: rgba(14, 21, 38, 0.8);
border-radius: 4px;
}
/* 滚动条滑块 */
::-webkit-scrollbar-thumb {
background: rgba(108, 92, 231, 0.4);
border-radius: 4px;
border: 1px solid rgba(108, 92, 231, 0.2);
box-shadow: 0 0 5px rgba(108, 92, 231, 0.5),
0 0 10px rgba(108, 92, 231, 0.3);
transition: all 0.3s ease;
}
/* 滚动条滑块悬停 */
::-webkit-scrollbar-thumb:hover {
background: rgba(108, 92, 231, 0.6);
box-shadow: 0 0 8px rgba(108, 92, 231, 0.7),
0 0 15px rgba(108, 92, 231, 0.5);
}
/* 滚动条滑块激活 */
::-webkit-scrollbar-thumb:active {
background: rgba(108, 92, 231, 0.8);
box-shadow: 0 0 10px rgba(108, 92, 231, 0.9),
0 0 20px rgba(108, 92, 231, 0.7);
}
/* 滚动条角落 */
::-webkit-scrollbar-corner {
background: transparent;
}
/* 自定义滚动条动画 */
@keyframes scroll-glow {
0% {
box-shadow: 0 0 5px rgba(108, 92, 231, 0.5),
0 0 10px rgba(108, 92, 231, 0.3);
}
50% {
box-shadow: 0 0 10px rgba(108, 92, 231, 0.8),
0 0 20px rgba(108, 92, 231, 0.5),
0 0 30px rgba(108, 92, 231, 0.3);
}
100% {
box-shadow: 0 0 5px rgba(108, 92, 231, 0.5),
0 0 10px rgba(108, 92, 231, 0.3);
}
}
/* 应用动画 */
.scroll-animate::-webkit-scrollbar-thumb {
animation: scroll-glow 2s ease-in-out infinite;
}
</style>
</head>
<body class="bg-darker text-gray-200 font-modern bg-grid min-h-screen scroll-animate">
<!-- 页面标题 -->
<header class="relative py-8 px-4 md:px-8 lg:px-16 overflow-hidden">
<div class="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_top_right,rgba(108,92,231,0.15),transparent_40%)]"></div>
<div class="relative z-10">
<h1 class="font-cyber text-[clamp(1.8rem,5vw,3.5rem)] font-bold text-white text-shadow mb-2 tracking-wider">
<span class="text-primary">Termination</span>
<span class="text-secondary text-shadow-glow"> 4</span>
</h1>
<p class="font-cyber text-gray-400 text-[clamp(1rem,2vw,1.25rem)] tracking-wide">
黯刃傀儡远程控制系统
</p>
<div class="w-32 h-1 bg-gradient-to-r from-primary to-secondary mt-4 animate-pulse-slow"></div>
</div>
</header>
<!-- 主内容区 -->
<main class="px-4 md:px-8 lg:px-16 pb-16">
<!-- 状态信息栏 -->
<div class="glass-effect rounded-lg p-4 mb-8 border border-primary/20">
<div class="flex flex-wrap justify-between items-center gap-4">
<div class="flex items-center gap-2">
<div class="w-3 h-3 rounded-full bg-green-500 animate-pulse"></div>
<span class="text-sm md:text-base">系统状态: 在线</span>
</div>
<div class="text-sm md:text-base text-gray-400">
<span id="current-time">加载中...</span>
</div>
<div class="text-sm md:text-base">
已感染设备: <span id="device-count" class="text-secondary font-semibold">0</span>
</div>
<div class="flex items-center gap-2">
<span id="refresh-countdown" class="text-xs md:text-sm text-gray-400">5:00</span>
<button id="refresh-btn" class="bg-primary/20 hover:bg-primary/30 text-primary p-2 rounded transition-all duration-300">
<i class="fa fa-refresh"></i>
</button>
</div>
</div>
</div>
<!-- 通知组件 -->
<div id="notification" class="fixed bottom-4 right-4 rounded-lg p-4 transform translate-y-20 opacity-0 transition-all duration-500 shadow-lg z-50 max-w-xs"></div>
<!-- 设备卡片容器 -->
<div id="device-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
<!-- 加载动画 - 初始显示 -->
<div id="loading-placeholder" class="col-span-full flex flex-col items-center justify-center py-16">
<div class="w-16 h-16 border-4 border-primary border-t-transparent rounded-full animate-spin mb-6"></div>
<p class="font-cyber text-primary animate-pulse">正在从服务器获取设备数据...</p>
</div>
<!-- 错误状态显示 -->
<div id="error-placeholder" class="col-span-full hidden flex-col items-center justify-center py-16 text-center">
<div class="w-16 h-16 rounded-full bg-accent/20 flex items-center justify-center mb-6">
<i class="fa fa-exclamation-triangle text-accent text-2xl"></i>
</div>
<h3 class="font-cyber text-white text-xl mb-2">数据加载失败</h3>
<p class="text-gray-400 mb-6 max-w-md">无法连接到服务器获取设备列表,请检查网络连接或稍后重试</p>
<button id="retry-btn" class="bg-primary hover:bg-primary/80 text-white py-2 px-6 rounded text-sm transition-all duration-300 font-medium">
重试连接
</button>
</div>
</div>
</main>
<script>
// 设备数据URL(实际项目中替换为真实API地址)
const DEVICES_API_URL = '/API/T4/computers.api';
// 更新当前时间
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'
});
document.getElementById('current-time').textContent = timeString;
}
// 每秒更新时间
setInterval(updateCurrentTime, 1000);
updateCurrentTime();
// 显示通知
function showNotification(message, type = 'info') {
const notification = document.getElementById('notification');
let bgColor, borderColor, icon, title;
switch(type) {
case 'success':
bgColor = 'bg-green-900/30';
borderColor = 'border-green-500/30';
icon = 'fa-check-circle';
title = '操作成功';
break;
case 'error':
bgColor = 'bg-red-900/30';
borderColor = 'border-red-500/30';
icon = 'fa-exclamation-circle';
title = '操作失败';
break;
case 'warning':
bgColor = 'bg-yellow-900/30';
borderColor = 'border-yellow-500/30';
icon = 'fa-exclamation-triangle';
title = '警告';
break;
default:
bgColor = 'bg-dark-card';
borderColor = 'border-primary/30';
icon = 'fa-info-circle';
title = '系统通知';
}
notification.className = `fixed bottom-4 right-4 ${bgColor} ${borderColor} rounded-lg p-4 transform translate-y-20 opacity-0 transition-all duration-500 shadow-lg z-50 max-w-xs`;
notification.innerHTML = `
<div class="flex items-start gap-3">
<div class="${bgColor.replace('bg-', 'bg-').replace('/30', '/20')} p-2 rounded-full text-${type === 'success' ? 'green-400' : type === 'error' ? 'red-400' : type === 'warning' ? 'yellow-400' : 'primary'}">
<i class="fa ${icon}"></i>
</div>
<div>
<h4 class="font-medium text-white">${title}</h4>
<p class="text-sm text-gray-400 mt-1">${message}</p>
</div>
</div>
`;
// 显示通知
setTimeout(() => {
notification.classList.remove('translate-y-20', 'opacity-0');
}, 100);
// 3秒后隐藏通知
setTimeout(() => {
notification.classList.add('translate-y-20', 'opacity-0');
}, 3000);
}
// 自动刷新倒计时
let countdownInterval;
let secondsLeft = 300; // 5分钟
function startCountdown() {
updateCountdown();
countdownInterval = setInterval(() => {
secondsLeft--;
updateCountdown();
if (secondsLeft <= 0) {
clearInterval(countdownInterval);
fetchDevicesData(true); // 自动刷新
secondsLeft = 300;
startCountdown();
}
}, 1000);
}
function updateCountdown() {
const minutes = Math.floor(secondsLeft / 60);
const seconds = secondsLeft % 60;
document.getElementById('refresh-countdown').textContent =
`${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
// 异步获取设备数据
async function fetchDevicesData(isAutoRefresh = false) {
const container = document.getElementById('device-container');
const refreshBtn = document.getElementById('refresh-btn');
const loadingPlaceholder = document.getElementById('loading-placeholder');
const errorPlaceholder = document.getElementById('error-placeholder');
// 显示加载状态
refreshBtn.innerHTML = '<i class="fa fa-refresh fa-spin"></i>';
refreshBtn.disabled = true;
// 隐藏错误状态,显示加载状态
errorPlaceholder.classList.add('hidden');
loadingPlaceholder.classList.remove('hidden');
// 关键修复:彻底清除所有设备卡片(保留占位符)
// 先保存占位符引用
const placeholders = [loadingPlaceholder, errorPlaceholder];
// 遍历并移除所有非占位符子元素
const children = Array.from(container.children);
children.forEach(child => {
if (!placeholders.includes(child)) {
container.removeChild(child);
}
});
try {
// 发起异步请求
const response = await fetch(DEVICES_API_URL);
// 检查请求是否成功
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
// 解析JSON数据(实际项目中直接使用response.json())
// 由于使用占位API,这里用模拟数据替代
const apiData = await response.json();
const devicesData = apiData;
// 隐藏加载占位符
loadingPlaceholder.classList.add('hidden');
// 更新设备计数
document.getElementById('device-count').textContent = devicesData.length;
// 为每个设备创建卡片
devicesData.forEach((device, index) => {
setTimeout(() => {
const card = document.createElement('div');
card.className = 'bg-dark-card rounded-lg overflow-hidden shadow-lg border border-primary/20 transform transition-all duration-500 hover:scale-[1.02] hover:z-10 hover:animate-glow opacity-0 translate-y-4';
card.style.animationDelay = `${index * 0.1}s`;
// 连接状态样式
const isConnected = device.PConnectime && device.PConnectime !== "NO CONNECTED";
const statusClass = isConnected ? "bg-green-500" : "bg-red-500";
const statusText = isConnected ? "CONNECTED" : "NO CONNECTED";
card.innerHTML = `
<div class="relative h-48 overflow-hidden">
<img src="/API/GetT4ComputerPCPng.api?PCMachineGUID=${device.PCMachineGUID}" alt="${device.PCName} 截图"
class="w-full h-full object-cover transition-transform duration-700 hover:scale-110">
<div class="absolute top-2 right-2 flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium glass-effect">
<span class="w-2 h-2 rounded-full ${statusClass}"></span>
${statusText}
</div>
<div class="absolute top-0 left-0 w-full h-full card-overlay"></div>
<div class="absolute bottom-3 left-3 font-cyber">
<h3 class="text-white font-bold text-lg">${device.PCName}</h3>
</div>
</div>
<div class="p-4 space-y-2">
<div class="text-sm">
<span class="text-gray-400">设备构建 ID:</span>
<span class="block truncate mt-1 text-primary">${device.PCMachineGUID}</span>
</div>
<div class="text-sm">
<span class="text-gray-400">设备感染时间:</span>
<span class="block mt-1">${device.PCreateTime}</span>
</div>
<div class="text-sm">
<span class="text-gray-400">设备连接时间:</span>
<span class="block mt-1 ${isConnected ? 'text-secondary' : 'text-gray-500'}">${device.PConnectime}</span>
</div>
<div class="text-sm">
<span class="text-gray-400">设备同步时间:</span>
<span class="block mt-1 ${isConnected ? 'text-secondary' : 'text-gray-500'}">${device.PCLastSynchronizationTime}</span>
</div>
<div class="grid grid-cols-2 gap-2 mt-2">
<div class="text-sm">
<span class="text-gray-400">T 版本:</span>
<span class="block mt-1">${device.PCVersion}</span>
</div>
<div class="text-sm">
<span class="text-gray-400">T 编译:</span>
<span class="block mt-1 truncate">${device.PCompilation}</span>
</div>
</div>
<div class="flex gap-2 mt-4">
<button class="view-btn flex-1 bg-primary/20 hover:bg-primary/30 text-primary py-2 px-3 rounded text-sm transition-all duration-300 flex items-center justify-center gap-1">
<i class="fa fa-eye"></i> 查看
</button>
<button class="delete-btn bg-accent/20 hover:bg-accent/30 text-accent py-2 px-3 rounded text-sm transition-all duration-300 flex items-center justify-center gap-1">
<i class="fa fa-trash"></i> 删除
</button>
</div>
</div>
`;
container.appendChild(card);
// 触发动画
setTimeout(() => {
card.classList.remove('opacity-0', 'translate-y-4');
}, 50);
// 为按钮添加事件
card.querySelector('.view-btn').addEventListener('click', () => {
window.open(`./console.html?PCMachineGUID=${device.PCMachineGUID}`);
showNotification(`正在查看设备 ${device.PCName} 的详细信息`, 'info');
});
card.querySelector('.delete-btn').addEventListener('click', () => {
if (confirm(`确定要删除设备 ${device.PCName} 吗?`)) {
card.classList.add('opacity-0', 'scale-95');
setTimeout(() => {
card.remove();
document.getElementById('device-count').textContent = container.children.length -
(loadingPlaceholder.classList.contains('hidden') ? 0 : 1) -
(errorPlaceholder.classList.contains('hidden') ? 0 : 1);
showNotification(`设备 ${device.PCName} 已成功删除`, 'success');
}, 300);
}
});
}, index * 100);
});
// 显示刷新通知(仅自动刷新时)
if (isAutoRefresh) {
showNotification('设备列表已自动更新', 'success');
}
} catch (error) {
console.error('获取设备数据失败:', error);
// 显示错误状态
loadingPlaceholder.classList.add('hidden');
errorPlaceholder.classList.remove('hidden');
// 显示错误通知
showNotification(`获取设备数据失败: ${error.message}`, 'error');
} finally {
// 恢复刷新按钮状态
refreshBtn.innerHTML = '<i class="fa fa-refresh"></i>';
refreshBtn.disabled = false;
}
}
// 页面加载完成后获取数据并启动倒计时
window.addEventListener('DOMContentLoaded', () => {
fetchDevicesData();
startCountdown();
// 绑定刷新按钮事件
document.getElementById('refresh-btn').addEventListener('click', () => {
// 重置倒计时
clearInterval(countdownInterval);
secondsLeft = 300;
updateCountdown();
startCountdown();
// 刷新设备
fetchDevicesData();
});
// 绑定重试按钮事件
document.getElementById('retry-btn').addEventListener('click', () => {
fetchDevicesData();
});
});
</script>
</body>
</html>