Compare commits
1 Commits
main
...
servercode
Author | SHA1 | Date | |
---|---|---|---|
25620b5f68 |
69
404.html
Executable file
@ -0,0 +1,69 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>网站已封禁</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #cc0000; /* 调整后的红色背景 */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.warning-box {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border: none;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
border-radius: 10px;
|
||||
width: 80%;
|
||||
max-width: 600px;
|
||||
position: relative;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.warning-box h1 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 20px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.warning-box p {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: #000;
|
||||
border: none;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
font-size: 2.5rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
background: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="warning-box">
|
||||
<button class="close-button">×</button>
|
||||
<h1>此页面已被管理员封禁</h1>
|
||||
<p>您尝试访问的网站已被学校管理员封禁,如需了解更多详情,请联系学校网络管理部门。</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
949
a.html
Executable file
@ -0,0 +1,949 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>成绩追踪应用</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Tailwind 配置 -->
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: '#165DFF',
|
||||
secondary: '#7B61FF',
|
||||
success: '#00B42A',
|
||||
warning: '#FF7D00',
|
||||
danger: '#F53F3F',
|
||||
dark: '#1D2129',
|
||||
light: '#F2F3F5'
|
||||
},
|
||||
fontFamily: {
|
||||
inter: ['Inter', 'sans-serif'],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style type="text/tailwindcss">
|
||||
@layer utilities {
|
||||
.content-auto {
|
||||
content-visibility: auto;
|
||||
}
|
||||
.card-shadow {
|
||||
box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.input-focus {
|
||||
@apply focus:ring-2 focus:ring-primary/50 focus:border-primary;
|
||||
}
|
||||
.btn-hover {
|
||||
@apply hover:shadow-lg transform hover:-translate-y-0.5 transition-all duration-300;
|
||||
}
|
||||
.animate-fade-in {
|
||||
animation: fadeIn 0.5s ease-in-out;
|
||||
}
|
||||
.animate-slide-up {
|
||||
animation: slideUp 0.5s ease-out;
|
||||
}
|
||||
.toggle-checkbox:checked {
|
||||
right: 0;
|
||||
border-color: #165DFF;
|
||||
}
|
||||
.toggle-checkbox:checked + .toggle-label {
|
||||
background-color: #165DFF;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from { transform: translateY(20px); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="font-inter bg-gradient-to-br from-light to-white min-h-screen text-dark">
|
||||
<div class="container mx-auto px-4 py-8 max-w-6xl">
|
||||
<!-- 页面标题 -->
|
||||
<header class="text-center mb-12 animate-fade-in">
|
||||
<h1 class="text-[clamp(2rem,5vw,3.5rem)] font-bold text-primary mb-4">
|
||||
<i class="fa fa-line-chart mr-3"></i>成绩追踪
|
||||
</h1>
|
||||
<p class="text-gray-600 text-lg max-w-2xl mx-auto">
|
||||
记录、分析和可视化你的预估成绩与实际成绩,帮助你更好地掌握学习情况
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<main class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<!-- 左侧:成绩输入表单 -->
|
||||
<section class="lg:col-span-1 animate-slide-up" style="animation-delay: 0.1s">
|
||||
<div class="bg-white rounded-xl p-6 card-shadow h-full">
|
||||
<h2 class="text-xl font-semibold mb-6 flex items-center">
|
||||
<i class="fa fa-pencil-square-o text-primary mr-2"></i>添加成绩
|
||||
</h2>
|
||||
|
||||
<form id="gradeForm" class="space-y-4">
|
||||
<div>
|
||||
<label for="course" class="block text-sm font-medium text-gray-700 mb-1">课程名称</label>
|
||||
<div class="relative">
|
||||
<select id="course" name="course"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg input-focus transition-all appearance-none"
|
||||
required>
|
||||
<option value="">请选择课程</option>
|
||||
<!-- 课程选项会通过JavaScript动态添加 -->
|
||||
</select>
|
||||
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-3 text-gray-400">
|
||||
<i class="fa fa-chevron-down"></i>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="addCourseBtn" class="mt-2 text-sm text-primary hover:text-primary/80">
|
||||
<i class="fa fa-plus-circle mr-1"></i> 添加新课程
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="newCourseInput" class="hidden">
|
||||
<label for="newCourse" class="block text-sm font-medium text-gray-700 mb-1">新课程名称</label>
|
||||
<input type="text" id="newCourse" name="newCourse"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg input-focus transition-all"
|
||||
placeholder="输入新课程名称">
|
||||
<div class="flex space-x-2 mt-2">
|
||||
<button type="button" id="cancelAddCourse" class="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-50">
|
||||
取消
|
||||
</button>
|
||||
<button type="button" id="confirmAddCourse" class="px-3 py-1 text-sm bg-primary text-white rounded hover:bg-primary/90">
|
||||
确认添加
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="estimatedScore" class="block text-sm font-medium text-gray-700 mb-1">预估</label>
|
||||
<input type="number" id="estimatedScore" name="estimatedScore" min="0" max="100" step="0.1"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg input-focus transition-all"
|
||||
placeholder="0-100" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="actualScore" class="block text-sm font-medium text-gray-700 mb-1">实际</label>
|
||||
<input type="number" id="actualScore" name="actualScore" min="0" max="100" step="0.1"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg input-focus transition-all"
|
||||
placeholder="0-100" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="description" class="block text-sm font-medium text-gray-700 mb-1">备注</label>
|
||||
<textarea id="description" name="description" rows="3"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg input-focus transition-all"
|
||||
placeholder="考试类型、难度等信息(选填)"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit"
|
||||
class="w-full bg-primary hover:bg-primary/90 text-white font-medium py-3 px-6 rounded-lg
|
||||
btn-hover flex items-center justify-center">
|
||||
<i class="fa fa-plus-circle mr-2"></i>添加成绩
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="mt-8 grid grid-cols-2 gap-4">
|
||||
<div class="bg-light rounded-lg p-4">
|
||||
<p class="text-sm text-gray-500">平均预估</p>
|
||||
<p id="averageEstimated" class="text-2xl font-bold">--</p>
|
||||
</div>
|
||||
<div class="bg-light rounded-lg p-4">
|
||||
<p class="text-sm text-gray-500">平均实际</p>
|
||||
<p id="averageActual" class="text-2xl font-bold">--</p>
|
||||
</div>
|
||||
<div class="bg-light rounded-lg p-4">
|
||||
<p class="text-sm text-gray-500">预估偏差</p>
|
||||
<p id="averageDeviation" class="text-2xl font-bold">--</p>
|
||||
</div>
|
||||
<div class="bg-light rounded-lg p-4">
|
||||
<p class="text-sm text-gray-500">成绩总数</p>
|
||||
<p id="totalGrades" class="text-2xl font-bold">0</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 右侧:图表和成绩列表 -->
|
||||
<section class="lg:col-span-2 space-y-8 animate-slide-up" style="animation-delay: 0.3s">
|
||||
<!-- 图表区域 -->
|
||||
<div class="bg-white rounded-xl p-6 card-shadow">
|
||||
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6 gap-4">
|
||||
<h2 class="text-xl font-semibold flex items-center">
|
||||
<i class="fa fa-bar-chart text-primary mr-2"></i>成绩趋势
|
||||
</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button id="chartTypeLine" class="px-3 py-1 text-sm bg-primary text-white rounded-md">
|
||||
折线图
|
||||
</button>
|
||||
<button id="chartTypeBar" class="px-3 py-1 text-sm bg-gray-200 hover:bg-gray-300 rounded-md">
|
||||
柱状图
|
||||
</button>
|
||||
<button id="toggleAllCourses" class="px-3 py-1 text-sm bg-gray-200 hover:bg-gray-300 rounded-md">
|
||||
全部显示
|
||||
</button>
|
||||
<select id="courseSelector" class="px-3 py-1 text-sm border border-gray-300 rounded-md">
|
||||
<option value="">全部科目</option>
|
||||
<!-- 科目选项会通过JavaScript动态添加 -->
|
||||
</select>
|
||||
<button id="exportChart" class="px-3 py-1 text-sm bg-success/10 text-success rounded-md hover:bg-success/20">
|
||||
<i class="fa fa-download mr-1"></i>导出图片
|
||||
</button>
|
||||
<!-- 新增导出数据按钮 -->
|
||||
<button id="exportData" class="px-3 py-1 text-sm bg-secondary/10 text-secondary rounded-md hover:bg-secondary/20">
|
||||
<i class="fa fa-file-text-o mr-1"></i>导出数据
|
||||
</button>
|
||||
<button id="exportCode" class="px-3 py-2 bg-secondary/10 text-secondary rounded-lg hover:bg-secondary/20 transition-colors">
|
||||
<i class="fa fa-share-alt mr-1"></i>生成分享代码
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 科目显示控制 -->
|
||||
<div id="courseVisibilityControls" class="mb-4 flex flex-wrap gap-2">
|
||||
<!-- 科目控制按钮会通过JavaScript动态添加 -->
|
||||
</div>
|
||||
|
||||
<div class="h-80">
|
||||
<canvas id="gradeChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 成绩列表 -->
|
||||
<div class="bg-white rounded-xl p-6 card-shadow">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-xl font-semibold flex items-center">
|
||||
<i class="fa fa-list-alt text-primary mr-2"></i>成绩记录
|
||||
</h2>
|
||||
<div class="flex space-x-2">
|
||||
<button id="clearAll" class="px-3 py-2 bg-danger/10 text-danger rounded-lg hover:bg-danger/20 transition-colors">
|
||||
<i class="fa fa-trash-o mr-1"></i>清空
|
||||
</button>
|
||||
<button id="exportRecordImage" class="px-3 py-2 bg-success/10 text-success rounded-lg hover:bg-success/20 transition-colors">
|
||||
<i class="fa fa-download mr-1"></i>导出为图片
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
课程
|
||||
</th>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
预估
|
||||
</th>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
实际
|
||||
</th>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
偏差
|
||||
</th>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
备注
|
||||
</th>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
操作
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="gradesList" class="bg-white divide-y divide-gray-200">
|
||||
<!-- 成绩记录将通过 JavaScript 动态添加 -->
|
||||
<tr class="text-center">
|
||||
<td colspan="6" class="px-6 py-12 text-gray-500">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fa fa-file-text-o text-4xl mb-3 text-gray-300"></i>
|
||||
<p>暂无成绩记录</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 分页控件 -->
|
||||
<div class="flex justify-between items-center mt-4 text-sm">
|
||||
<div id="paginationInfo" class="text-gray-500">
|
||||
显示 0-0 条,共 0 条
|
||||
</div>
|
||||
<div class="flex space-x-1">
|
||||
<button id="prevPage" class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" disabled>
|
||||
上一页
|
||||
</button>
|
||||
<span id="currentPage" class="px-3 py-1">1</span>
|
||||
<button id="nextPage" class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" disabled>
|
||||
下一页
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="mt-16 text-center text-gray-500 text-sm py-4 border-t border-gray-200">
|
||||
<p>Copyright © 2023-2025 CCSIT Network.All rights reserved. © 圆周云境信息技术 保留所有权利。</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- 编辑模态框 -->
|
||||
<div id="editModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
||||
<div class="bg-white rounded-xl p-6 max-w-md w-full mx-4 animate-fade-in">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-semibold">编辑成绩</h3>
|
||||
<button id="closeModal" class="text-gray-400 hover:text-gray-500">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="editForm" class="space-y-4">
|
||||
<input type="hidden" id="editId">
|
||||
|
||||
<div>
|
||||
<label for="editCourse" class="block text-sm font-medium text-gray-700 mb-1">课程名称</label>
|
||||
<select id="editCourse" name="editCourse"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg input-focus transition-all" required>
|
||||
<!-- 课程选项会通过JavaScript动态添加 -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="editEstimatedScore" class="block text-sm font-medium text-gray-700 mb-1">预估</label>
|
||||
<input type="number" id="editEstimatedScore" name="editEstimatedScore" min="0" max="100" step="0.1"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg input-focus transition-all" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="editActualScore" class="block text-sm font-medium text-gray-700 mb-1">实际</label>
|
||||
<input type="number" id="editActualScore" name="editActualScore" min="0" max="100" step="0.1"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg input-focus transition-all" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="editDescription" class="block text-sm font-medium text-gray-700 mb-1">备注</label>
|
||||
<textarea id="editDescription" name="editDescription" rows="3"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg input-focus transition-all"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-3">
|
||||
<button type="button" id="cancelEdit" class="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-700 font-medium py-2 px-4 rounded-lg btn-hover">
|
||||
取消
|
||||
</button>
|
||||
<button type="submit" class="flex-1 bg-primary hover:bg-primary/90 text-white font-medium py-2 px-4 rounded-lg btn-hover">
|
||||
保存修改
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 删除确认模态框 -->
|
||||
<div id="deleteModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
||||
<div class="bg-white rounded-xl p-6 max-w-md w-full mx-4 animate-fade-in">
|
||||
<div class="text-center mb-4">
|
||||
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-danger/10 text-danger mb-4">
|
||||
<i class="fa fa-exclamation-triangle text-2xl"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold">确认删除</h3>
|
||||
<p class="text-gray-500 mt-2">你确定要删除这条成绩记录吗?此操作无法撤销。</p>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="deleteId">
|
||||
|
||||
<div class="flex space-x-3 mt-6">
|
||||
<button id="cancelDelete" class="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-700 font-medium py-2 px-4 rounded-lg btn-hover">
|
||||
取消
|
||||
</button>
|
||||
<button id="confirmDelete" class="flex-1 bg-danger hover:bg-danger/90 text-white font-medium py-2 px-4 rounded-lg btn-hover">
|
||||
确认删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="codeModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
||||
<div class="bg-white rounded-xl p-6 max-w-md w-full mx-4 animate-fade-in">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-semibold">输入成绩代码</h3>
|
||||
<button onclick="closeCodeModal()" class="text-gray-400 hover:text-gray-500">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<textarea id="importCode" class="w-full h-32 p-3 border rounded-lg" placeholder="粘贴成绩代码..."></textarea>
|
||||
<button onclick="loadFromCode()" class="w-full bg-primary text-white py-2 rounded-lg hover:bg-primary/90">
|
||||
加载成绩
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 清空确认模态框 -->
|
||||
<script>
|
||||
// 模拟数据
|
||||
let grades = [];
|
||||
let chartInstance;
|
||||
let chartType = 'line';
|
||||
let currentPage = 1;
|
||||
const itemsPerPage = 10;
|
||||
|
||||
// DOM 元素
|
||||
const gradeForm = document.getElementById('gradeForm');
|
||||
const courseSelect = document.getElementById('course');
|
||||
const addCourseBtn = document.getElementById('addCourseBtn');
|
||||
const newCourseInput = document.getElementById('newCourseInput');
|
||||
const cancelAddCourse = document.getElementById('cancelAddCourse');
|
||||
const confirmAddCourse = document.getElementById('confirmAddCourse');
|
||||
const gradesList = document.getElementById('gradesList');
|
||||
const averageEstimated = document.getElementById('averageEstimated');
|
||||
const averageActual = document.getElementById('averageActual');
|
||||
const averageDeviation = document.getElementById('averageDeviation');
|
||||
const totalGrades = document.getElementById('totalGrades');
|
||||
const chartTypeLine = document.getElementById('chartTypeLine');
|
||||
const chartTypeBar = document.getElementById('chartTypeBar');
|
||||
const toggleAllCourses = document.getElementById('toggleAllCourses');
|
||||
const courseVisibilityControls = document.getElementById('courseVisibilityControls');
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const clearAll = document.getElementById('clearAll');
|
||||
const prevPage = document.getElementById('prevPage');
|
||||
const nextPage = document.getElementById('nextPage');
|
||||
const currentPageDisplay = document.getElementById('currentPage');
|
||||
const paginationInfo = document.getElementById('paginationInfo');
|
||||
const editModal = document.getElementById('editModal');
|
||||
const editForm = document.getElementById('editForm');
|
||||
const closeModal = document.getElementById('closeModal');
|
||||
const cancelEdit = document.getElementById('cancelEdit');
|
||||
const deleteModal = document.getElementById('deleteModal');
|
||||
const cancelDelete = document.getElementById('cancelDelete');
|
||||
const confirmDelete = document.getElementById('confirmDelete');
|
||||
const courseSelector = document.getElementById('courseSelector');
|
||||
const exportCodeBtn = document.getElementById('exportCode');
|
||||
const codeModal = document.getElementById('codeModal');
|
||||
const importCode = document.getElementById('importCode');
|
||||
|
||||
// 保存数据到本地存储
|
||||
function saveDataToLocalStorage() {
|
||||
localStorage.setItem('grades', JSON.stringify(grades));
|
||||
}
|
||||
|
||||
// 从本地存储加载数据
|
||||
function loadDataFromLocalStorage() {
|
||||
const savedGrades = localStorage.getItem('grades');
|
||||
if (savedGrades) {
|
||||
grades = JSON.parse(savedGrades);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查 URL 中是否有成绩代码参数
|
||||
function getGradeCodeFromURL() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const gradeCode = urlParams.get('code');
|
||||
|
||||
if (gradeCode) {
|
||||
try {
|
||||
// 解码成绩代码
|
||||
const decodedData = decodeURIComponent(escape(atob(gradeCode)));
|
||||
const loadedGrades = JSON.parse(decodedData);
|
||||
|
||||
// 替换当前成绩数据
|
||||
grades = loadedGrades;
|
||||
|
||||
// 保存数据到本地存储
|
||||
saveDataToLocalStorage();
|
||||
|
||||
alert('成绩代码加载成功!');
|
||||
} catch (error) {
|
||||
alert('加载成绩代码失败:' + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 从本地存储加载数据
|
||||
loadDataFromLocalStorage();
|
||||
|
||||
// 检查 URL 中是否有成绩代码参数
|
||||
getGradeCodeFromURL();
|
||||
|
||||
// 初始化课程选择器
|
||||
updateCourseSelect();
|
||||
// 初始化下拉框选项
|
||||
updateCourseSelector();
|
||||
// 初始化图表
|
||||
updateChart();
|
||||
// 初始化成绩列表
|
||||
updateGradesList();
|
||||
|
||||
// 绑定事件监听器
|
||||
gradeForm.addEventListener('submit', handleGradeSubmit);
|
||||
addCourseBtn.addEventListener('click', showNewCourseInput);
|
||||
cancelAddCourse.addEventListener('click', hideNewCourseInput);
|
||||
confirmAddCourse.addEventListener('click', addNewCourse);
|
||||
chartTypeLine.addEventListener('click', () => changeChartType('line'));
|
||||
chartTypeBar.addEventListener('click', () => changeChartType('bar'));
|
||||
toggleAllCourses.addEventListener('click', toggleAllCoursesVisibility);
|
||||
searchInput.addEventListener('input', handleSearch);
|
||||
clearAll.addEventListener('click', confirmClearAll);
|
||||
prevPage.addEventListener('click', () => changePage(currentPage - 1));
|
||||
nextPage.addEventListener('click', () => changePage(currentPage + 1));
|
||||
closeModal.addEventListener('click', closeEditModal);
|
||||
cancelEdit.addEventListener('click', closeEditModal);
|
||||
cancelDelete.addEventListener('click', closeDeleteModal);
|
||||
confirmDelete.addEventListener('click', handleDelete);
|
||||
editForm.addEventListener('submit', handleEditSubmit);
|
||||
courseSelector.addEventListener('change', handleCourseSelect);
|
||||
exportCodeBtn.addEventListener('click', exportGradeCode);
|
||||
});
|
||||
|
||||
// 更新课程选择器
|
||||
function updateCourseSelect(selectElement = courseSelect) {
|
||||
const courses = [...new Set(grades.map(grade => grade.course))];
|
||||
selectElement.innerHTML = '<option value="">请选择课程</option>';
|
||||
courses.forEach(course => {
|
||||
const option = document.createElement('option');
|
||||
option.value = course;
|
||||
option.textContent = course;
|
||||
selectElement.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// 更新下拉框选项
|
||||
function updateCourseSelector() {
|
||||
while (courseSelector.options.length > 1) {
|
||||
courseSelector.remove(1);
|
||||
}
|
||||
|
||||
const courses = [...new Set(grades.map(grade => grade.course))];
|
||||
|
||||
courses.forEach(course => {
|
||||
const option = document.createElement('option');
|
||||
option.value = course;
|
||||
option.textContent = course;
|
||||
courseSelector.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// 处理科目选择事件
|
||||
function handleCourseSelect() {
|
||||
const selectedCourse = courseSelector.value;
|
||||
if (selectedCourse === '') {
|
||||
updateChart();
|
||||
} else {
|
||||
updateChart(selectedCourse);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理成绩提交
|
||||
function handleGradeSubmit(e) {
|
||||
e.preventDefault();
|
||||
const course = courseSelect.value;
|
||||
const estimatedScore = parseFloat(document.getElementById('estimatedScore').value);
|
||||
const actualScore = parseFloat(document.getElementById('actualScore').value);
|
||||
const description = document.getElementById('description').value;
|
||||
const id = grades.length + 1;
|
||||
|
||||
grades.push({
|
||||
id,
|
||||
course,
|
||||
estimatedScore,
|
||||
actualScore,
|
||||
description
|
||||
});
|
||||
|
||||
saveDataToLocalStorage();
|
||||
|
||||
gradeForm.reset();
|
||||
updateCourseSelect();
|
||||
updateCourseSelector();
|
||||
updateChart();
|
||||
updateGradesList();
|
||||
}
|
||||
|
||||
// 显示新增课程输入框
|
||||
function showNewCourseInput() {
|
||||
newCourseInput.classList.remove('hidden');
|
||||
addCourseBtn.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 隐藏新增课程输入框
|
||||
function hideNewCourseInput() {
|
||||
newCourseInput.classList.add('hidden');
|
||||
addCourseBtn.classList.remove('hidden');
|
||||
document.getElementById('newCourse').value = '';
|
||||
}
|
||||
|
||||
// 添加新课程
|
||||
function addNewCourse() {
|
||||
const newCourse = document.getElementById('newCourse').value;
|
||||
if (newCourse) {
|
||||
const option = document.createElement('option');
|
||||
option.value = newCourse;
|
||||
option.textContent = newCourse;
|
||||
courseSelect.appendChild(option);
|
||||
courseSelect.value = newCourse;
|
||||
hideNewCourseInput();
|
||||
}
|
||||
}
|
||||
|
||||
// 更新图表
|
||||
function updateChart(selectedCourse = null) {
|
||||
if (chartInstance) {
|
||||
chartInstance.destroy();
|
||||
}
|
||||
|
||||
let dataToChart = grades;
|
||||
if (selectedCourse) {
|
||||
dataToChart = grades.filter(grade => grade.course === selectedCourse);
|
||||
}
|
||||
|
||||
const labels = dataToChart.map(grade => grade.course);
|
||||
const estimatedScores = dataToChart.map(grade => grade.estimatedScore);
|
||||
const actualScores = dataToChart.map(grade => grade.actualScore);
|
||||
|
||||
const ctx = document.getElementById('gradeChart').getContext('2d');
|
||||
chartInstance = new Chart(ctx, {
|
||||
type: chartType,
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
label: '预估',
|
||||
data: estimatedScores,
|
||||
borderColor: 'rgba(22, 93, 255, 1)',
|
||||
backgroundColor: 'rgba(22, 93, 255, 0.2)',
|
||||
borderWidth: 1
|
||||
},
|
||||
{
|
||||
label: '实际',
|
||||
data: actualScores,
|
||||
borderColor: 'rgba(0, 180, 42, 1)',
|
||||
backgroundColor: 'rgba(0, 180, 42, 0.2)',
|
||||
borderWidth: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 更改图表类型
|
||||
function changeChartType(type) {
|
||||
chartType = type;
|
||||
if (type === 'line') {
|
||||
chartTypeLine.classList.add('bg-primary', 'text-white');
|
||||
chartTypeBar.classList.remove('bg-primary', 'text-white');
|
||||
chartTypeBar.classList.add('bg-gray-200', 'hover:bg-gray-300');
|
||||
} else {
|
||||
chartTypeBar.classList.add('bg-primary', 'text-white');
|
||||
chartTypeLine.classList.remove('bg-primary', 'text-white');
|
||||
chartTypeLine.classList.add('bg-gray-200', 'hover:bg-gray-300');
|
||||
}
|
||||
updateChart();
|
||||
}
|
||||
|
||||
// 切换所有科目可见性
|
||||
function toggleAllCoursesVisibility() {
|
||||
updateChart();
|
||||
}
|
||||
|
||||
|
||||
// 更新成绩列表
|
||||
function updateGradesList(filteredGrades = grades) {
|
||||
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||
const endIndex = startIndex + itemsPerPage;
|
||||
const currentGrades = filteredGrades.slice(startIndex, endIndex);
|
||||
|
||||
gradesList.innerHTML = '';
|
||||
if (currentGrades.length === 0) {
|
||||
gradesList.innerHTML = `
|
||||
<tr class="text-center">
|
||||
<td colspan="6" class="px-6 py-12 text-gray-500">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fa fa-file-text-o text-4xl mb-3 text-gray-300"></i>
|
||||
<p>暂无成绩记录</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
} else {
|
||||
currentGrades.forEach(grade => {
|
||||
const deviation = grade.estimatedScore - grade.actualScore;
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td class="px-6 py-4 whitespace-nowrap">${grade.course}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">${grade.estimatedScore}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">${grade.actualScore}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">${deviation.toFixed(1)}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">${(grade.description || '-').replace(/`/g, '\\`')}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<button class="text-primary hover:text-primary/80 mr-2" onclick="openEditModal(${grade.id})">
|
||||
<i class="fa fa-pencil"></i> 编辑
|
||||
</button>
|
||||
<button class="text-danger hover:text-danger/80" onclick="openDeleteModal(${grade.id})">
|
||||
<i class="fa fa-trash-o"></i> 删除
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
gradesList.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
const totalEstimated = filteredGrades.reduce((sum, grade) => sum + grade.estimatedScore, 0);
|
||||
const totalActual = filteredGrades.reduce((sum, grade) => sum + grade.actualScore, 0);
|
||||
const totalDeviation = filteredGrades.reduce((sum, grade) => sum + (grade.estimatedScore - grade.actualScore), 0);
|
||||
averageEstimated.textContent = filteredGrades.length > 0 ? (totalEstimated / filteredGrades.length).toFixed(1) : '--';
|
||||
averageActual.textContent = filteredGrades.length > 0 ? (totalActual / filteredGrades.length).toFixed(1) : '--';
|
||||
averageDeviation.textContent = filteredGrades.length > 0 ? (totalDeviation / filteredGrades.length).toFixed(1) : '--';
|
||||
totalGrades.textContent = filteredGrades.length;
|
||||
|
||||
const totalPages = Math.ceil(filteredGrades.length / itemsPerPage);
|
||||
prevPage.disabled = currentPage === 1;
|
||||
nextPage.disabled = currentPage === totalPages;
|
||||
currentPageDisplay.textContent = currentPage;
|
||||
const start = startIndex + 1;
|
||||
const end = Math.min(endIndex, filteredGrades.length);
|
||||
paginationInfo.textContent = `显示 ${start}-${end} 条,共 ${filteredGrades.length} 条`;
|
||||
}
|
||||
|
||||
// 打开编辑模态框
|
||||
function openEditModal(id) {
|
||||
const grade = grades.find(g => g.id === id);
|
||||
if (grade) {
|
||||
document.getElementById('editId').value = id;
|
||||
const editCourseSelect = document.getElementById('editCourse');
|
||||
updateCourseSelect(editCourseSelect);
|
||||
editCourseSelect.value = grade.course;
|
||||
document.getElementById('editEstimatedScore').value = grade.estimatedScore;
|
||||
document.getElementById('editActualScore').value = grade.actualScore;
|
||||
document.getElementById('editDescription').value = grade.description;
|
||||
editModal.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭编辑模态框
|
||||
function closeEditModal() {
|
||||
editModal.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 处理编辑提交
|
||||
function handleEditSubmit(e) {
|
||||
e.preventDefault();
|
||||
const id = parseInt(document.getElementById('editId').value);
|
||||
const course = document.getElementById('editCourse').value;
|
||||
const estimatedScore = parseFloat(document.getElementById('editEstimatedScore').value);
|
||||
const actualScore = parseFloat(document.getElementById('editActualScore').value);
|
||||
const description = document.getElementById('editDescription').value;
|
||||
|
||||
const index = grades.findIndex(grade => grade.id === id);
|
||||
if (index !== -1) {
|
||||
grades[index] = {
|
||||
id,
|
||||
course,
|
||||
estimatedScore,
|
||||
actualScore,
|
||||
description
|
||||
};
|
||||
|
||||
saveDataToLocalStorage();
|
||||
}
|
||||
|
||||
closeEditModal();
|
||||
updateCourseSelect();
|
||||
updateCourseSelector();
|
||||
updateChart();
|
||||
updateGradesList();
|
||||
}
|
||||
|
||||
// 打开删除确认模态框
|
||||
function openDeleteModal(id) {
|
||||
document.getElementById('deleteId').value = id;
|
||||
deleteModal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 关闭删除确认模态框
|
||||
function closeDeleteModal() {
|
||||
deleteModal.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 处理删除
|
||||
function handleDelete() {
|
||||
const id = parseInt(document.getElementById('deleteId').value);
|
||||
grades = grades.filter(grade => grade.id !== id);
|
||||
|
||||
saveDataToLocalStorage();
|
||||
|
||||
closeDeleteModal();
|
||||
updateCourseSelect();
|
||||
updateCourseSelector();
|
||||
updateChart();
|
||||
updateGradesList();
|
||||
}
|
||||
|
||||
// 确认清空所有记录
|
||||
function confirmClearAll() {
|
||||
if (confirm('你确定要清空所有成绩记录吗?此操作无法撤销。')) {
|
||||
grades = [];
|
||||
|
||||
saveDataToLocalStorage();
|
||||
|
||||
updateCourseSelect();
|
||||
updateCourseSelector();
|
||||
updateChart();
|
||||
updateGradesList();
|
||||
}
|
||||
}
|
||||
|
||||
// 更改页面
|
||||
function changePage(page) {
|
||||
if (page > 0) {
|
||||
currentPage = page;
|
||||
updateGradesList();
|
||||
}
|
||||
}
|
||||
|
||||
// 导出成绩数据为CSV文件
|
||||
function exportGradeData() {
|
||||
const selectedCourse = document.getElementById('courseSelector').value;
|
||||
const allGrades = JSON.parse(localStorage.getItem('grades') || '[]');
|
||||
let filteredGrades = allGrades;
|
||||
|
||||
if (selectedCourse) {
|
||||
filteredGrades = allGrades.filter(grade => grade.course === selectedCourse);
|
||||
}
|
||||
|
||||
if (filteredGrades.length === 0) {
|
||||
alert('没有可导出的成绩数据');
|
||||
return;
|
||||
}
|
||||
|
||||
const headers = ['课程', '预估', '实际', '偏差', '备注', '添加时间'];
|
||||
let csvContent = headers.join(',') + '\n';
|
||||
|
||||
filteredGrades.forEach(grade => {
|
||||
const deviation = (grade.actualScore - grade.estimatedScore).toFixed(1);
|
||||
const row = [
|
||||
`"${grade.course}"`,
|
||||
grade.estimatedScore,
|
||||
grade.actualScore,
|
||||
deviation,
|
||||
`"${grade.description || '-'}"`
|
||||
].join(',');
|
||||
csvContent += row + '\n';
|
||||
});
|
||||
|
||||
const bom = new Uint8Array([0xEF, 0xBB, 0xBF]);
|
||||
const blob = new Blob([bom, csvContent], { type: 'text/csv;charset=utf-8' });
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
const timestamp = new Date().toLocaleString().replace(/[\/:*?"<>|]/g, '-');
|
||||
link.download = `成绩_${selectedCourse || '全部科目'}_${timestamp}.csv`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(link.href);
|
||||
}
|
||||
|
||||
// 导出图表为图片
|
||||
function exportChartImage() {
|
||||
if (!chartInstance) {
|
||||
alert('请先添加成绩数据并生成图表');
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedCourse = document.getElementById('courseSelector').value;
|
||||
const courseName = selectedCourse || '所有科目';
|
||||
const timestamp = new Date().toLocaleString().replace(/[\/:*?"<>|]/g, '-');
|
||||
const fileName = `${courseName}_成绩图表_${timestamp}.png`;
|
||||
const imageURL = chartInstance.toBase64Image();
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = imageURL;
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
// 导出成绩代码
|
||||
function exportGradeCode() {
|
||||
try {
|
||||
const gradeData = JSON.stringify(grades);
|
||||
const encodedData = btoa(unescape(encodeURIComponent(gradeData)));
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent(encodedData);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
const currentUrl = window.location.origin + window.location.pathname;
|
||||
const shareUrl = `${currentUrl}?code=${encodeURIComponent(encodedData)}`;
|
||||
prompt('复制以下链接分享你的成绩:', shareUrl);
|
||||
|
||||
alert('成绩分享代码已生成!');
|
||||
} catch (error) {
|
||||
alert('导出成绩代码失败:' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载成绩代码
|
||||
function loadFromCode() {
|
||||
const code = importCode.value;
|
||||
if (!code) return;
|
||||
|
||||
try {
|
||||
const decodedData = decodeURIComponent(escape(atob(code)));
|
||||
const loadedGrades = JSON.parse(decodedData);
|
||||
|
||||
grades = loadedGrades;
|
||||
|
||||
saveDataToLocalStorage();
|
||||
|
||||
closeCodeModal();
|
||||
|
||||
updateCourseSelect();
|
||||
updateCourseSelector();
|
||||
updateChart();
|
||||
updateGradesList();
|
||||
|
||||
alert('成绩代码加载成功!');
|
||||
} catch (error) {
|
||||
alert('加载成绩代码失败:' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 打开代码模态框
|
||||
function openCodeModal() {
|
||||
codeModal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 关闭代码模态框
|
||||
function closeCodeModal() {
|
||||
codeModal.classList.add('hidden');
|
||||
importCode.value = '';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
291
api.php
Executable file
@ -0,0 +1,291 @@
|
||||
<?php
|
||||
// Minecraft 服务器查询类
|
||||
class MinecraftServerQuery {
|
||||
private $socket;
|
||||
private $host;
|
||||
private $port;
|
||||
private $timeout;
|
||||
private $info = [];
|
||||
private $ping;
|
||||
|
||||
public function __construct($host, $port = 25565, $timeout = 3) {
|
||||
$this->host = $host;
|
||||
$this->port = $port;
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
public function connect() {
|
||||
// 记录开始时间计算延迟
|
||||
$start = microtime(true);
|
||||
|
||||
// 创建 socket 连接
|
||||
$this->socket = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout);
|
||||
|
||||
if (!$this->socket) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置超时
|
||||
stream_set_timeout($this->socket, $this->timeout);
|
||||
|
||||
// 发送握手包
|
||||
$this->sendHandshake();
|
||||
|
||||
// 发送状态请求
|
||||
$this->sendStatusRequest();
|
||||
|
||||
// 读取状态响应
|
||||
$this->readStatusResponse();
|
||||
|
||||
// 计算延迟
|
||||
$this->ping = round((microtime(true) - $start) * 1000);
|
||||
|
||||
// 关闭连接
|
||||
fclose($this->socket);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function sendHandshake() {
|
||||
$packet = pack('c', 0x00); // 包ID (Handshake)
|
||||
$packet .= pack('c', 0x04); // 协议版本 (4 for 1.7.2+)
|
||||
$packet .= pack('c', strlen($this->host)) . $this->host; // 服务器地址
|
||||
$packet .= pack('n', $this->port); // 服务器端口
|
||||
$packet .= pack('c', 0x01); // 下一步状态 (1 for Status)
|
||||
|
||||
$packet = pack('c', strlen($packet)) . $packet; // 添加包长度前缀
|
||||
fwrite($this->socket, $packet);
|
||||
}
|
||||
|
||||
private function sendStatusRequest() {
|
||||
$packet = pack('c', 0x00); // 包ID (Status Request)
|
||||
$packet = pack('c', strlen($packet)) . $packet;
|
||||
fwrite($this->socket, $packet);
|
||||
}
|
||||
|
||||
private function readStatusResponse() {
|
||||
// 读取包长度
|
||||
$length = $this->readVarInt();
|
||||
|
||||
// 读取包ID
|
||||
$packetID = $this->readVarInt();
|
||||
|
||||
if ($packetID !== 0x00) {
|
||||
return false; // 不是状态响应包
|
||||
}
|
||||
|
||||
// 读取JSON数据长度
|
||||
$jsonLength = $this->readVarInt();
|
||||
|
||||
// 读取JSON数据
|
||||
$json = '';
|
||||
$bytesRead = 0;
|
||||
|
||||
while ($bytesRead < $jsonLength) {
|
||||
$chunk = fread($this->socket, $jsonLength - $bytesRead);
|
||||
if ($chunk === false) break;
|
||||
$json .= $chunk;
|
||||
$bytesRead += strlen($chunk);
|
||||
}
|
||||
|
||||
$this->info = json_decode($json, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private function readVarInt() {
|
||||
$value = 0;
|
||||
$size = 0;
|
||||
$b;
|
||||
|
||||
do {
|
||||
$b = ord(fgetc($this->socket));
|
||||
$value |= ($b & 0x7F) << ($size++ * 7);
|
||||
if ($size > 5) {
|
||||
throw new Exception('VarInt too big');
|
||||
}
|
||||
} while (($b & 0x80) == 0x80);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getInfo() {
|
||||
return $this->info;
|
||||
}
|
||||
|
||||
public function getPing() {
|
||||
return $this->ping;
|
||||
}
|
||||
|
||||
public function formatMotd($motd) {
|
||||
// 颜色代码映射
|
||||
$colors = [
|
||||
'0' => '#000000', '1' => '#0000AA', '2' => '#00AA00', '3' => '#00AAAA',
|
||||
'4' => '#AA0000', '5' => '#AA00AA', '6' => '#FFAA00', '7' => '#AAAAAA',
|
||||
'8' => '#555555', '9' => '#5555FF', 'a' => '#55FF55', 'b' => '#55FFFF',
|
||||
'c' => '#FF5555', 'd' => '#FF55FF', 'e' => '#FFFF55', 'f' => '#FFFFFF'
|
||||
];
|
||||
|
||||
// 格式化代码映射
|
||||
$formats = [
|
||||
'l' => 'font-weight:bold', // 粗体
|
||||
'm' => 'text-decoration:line-through', // 删除线
|
||||
'n' => 'text-decoration:underline', // 下划线
|
||||
'o' => 'font-style:italic', // 斜体
|
||||
'r' => '' // 重置
|
||||
];
|
||||
|
||||
$html = '';
|
||||
$currentStyles = [];
|
||||
$buffer = '';
|
||||
$chars = preg_split('//u', $motd, -1, PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
for ($i = 0; $i < count($chars); $i++) {
|
||||
$char = $chars[$i];
|
||||
|
||||
if ($char === '§' && isset($chars[$i + 1])) {
|
||||
$code = $chars[$i + 1];
|
||||
$i++;
|
||||
|
||||
// 处理颜色代码
|
||||
if (isset($colors[strtolower($code)])) {
|
||||
if ($buffer !== '') {
|
||||
$html .= $this->wrapWithStyles($buffer, $currentStyles);
|
||||
$buffer = '';
|
||||
}
|
||||
$currentStyles['color'] = $colors[strtolower($code)];
|
||||
}
|
||||
// 处理格式代码
|
||||
elseif (isset($formats[strtolower($code)])) {
|
||||
if ($buffer !== '') {
|
||||
$html .= $this->wrapWithStyles($buffer, $currentStyles);
|
||||
$buffer = '';
|
||||
}
|
||||
|
||||
if (strtolower($code) === 'r') {
|
||||
$currentStyles = []; // 重置所有样式
|
||||
} else {
|
||||
$currentStyles[] = $formats[strtolower($code)];
|
||||
}
|
||||
}
|
||||
// 忽略未知代码
|
||||
else {
|
||||
$buffer .= $char . $code;
|
||||
}
|
||||
} else {
|
||||
$buffer .= $char;
|
||||
}
|
||||
}
|
||||
|
||||
if ($buffer !== '') {
|
||||
$html .= $this->wrapWithStyles($buffer, $currentStyles);
|
||||
}
|
||||
|
||||
// 处理换行符
|
||||
$html = str_replace("\n", "<br>", $html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function wrapWithStyles($text, $styles) {
|
||||
if (empty($styles)) {
|
||||
return htmlspecialchars($text);
|
||||
}
|
||||
|
||||
$styleString = '';
|
||||
foreach ($styles as $key => $value) {
|
||||
if (is_numeric($key)) {
|
||||
$styleString .= $value . ';';
|
||||
} else {
|
||||
$styleString .= $key . ':' . $value . ';';
|
||||
}
|
||||
}
|
||||
|
||||
return '<span style="' . $styleString . '">' . htmlspecialchars($text) . '</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// 处理API请求
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
// 获取查询参数
|
||||
$host = isset($_GET['host']) ? $_GET['host'] : '';
|
||||
$port = isset($_GET['port']) ? (int)$_GET['port'] : 25565;
|
||||
|
||||
// 验证输入
|
||||
if (empty($host)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Missing host parameter']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 创建服务器查询实例
|
||||
$server = new MinecraftServerQuery($host, $port);
|
||||
|
||||
try {
|
||||
// 尝试连接并获取信息
|
||||
$connected = $server->connect();
|
||||
|
||||
if ($connected) {
|
||||
$info = $server->getInfo();
|
||||
$ping = $server->getPing();
|
||||
|
||||
// 处理玩家列表
|
||||
$players = [];
|
||||
if (isset($info['players']['sample'])) {
|
||||
foreach ($info['players']['sample'] as $player) {
|
||||
$players[] = $player['name'];
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化MOTD
|
||||
$motd = '';
|
||||
$formattedMotd = '';
|
||||
|
||||
if (isset($info['description'])) {
|
||||
if (is_string($info['description'])) {
|
||||
$motd = $info['description'];
|
||||
$formattedMotd = $server->formatMotd($motd);
|
||||
} elseif (is_array($info['description']) && isset($info['description']['text'])) {
|
||||
$motd = $info['description']['text'];
|
||||
$formattedMotd = $server->formatMotd($motd);
|
||||
}
|
||||
}
|
||||
|
||||
// 构建响应数据
|
||||
$response = [
|
||||
'online' => true,
|
||||
'host' => $host,
|
||||
'port' => $port,
|
||||
'ping' => $ping,
|
||||
'version' => $info['version']['name'] ?? 'Unknown',
|
||||
'protocol' => $info['version']['protocol'] ?? -1,
|
||||
'online_players' => $info['players']['online'] ?? 0,
|
||||
'max_players' => $info['players']['max'] ?? 0,
|
||||
'motd' => $motd,
|
||||
'formatted_motd' => $formattedMotd,
|
||||
'players' => $players
|
||||
];
|
||||
|
||||
echo json_encode($response);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'online' => false,
|
||||
'error' => '无法连接到服务器',
|
||||
'host' => $host,
|
||||
'port' => $port
|
||||
]);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'online' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'host' => $host,
|
||||
'port' => $port
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Method not allowed']);
|
||||
}
|
||||
?>
|
@ -237,24 +237,25 @@ if ($token) {
|
||||
|
||||
|
||||
<div id="register-form" class="form-container">
|
||||
<form action="register.php" method="post">
|
||||
<label for="username">用户名:</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
<label for="email">邮箱:</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
<label for="password">密码:</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
<label for="confirm-password">确认密码:</label>
|
||||
<input type="password" id="confirm-password" name="confirm-password" required>
|
||||
<!--<label for="organization">组织:</label>
|
||||
<input type="text" id="organization" name="organization">
|
||||
<label for="development_field">开发领域:</label>
|
||||
<input type="text" id="development_field" name="development_field">
|
||||
<label for="skill_tags">技能标签:</label>
|
||||
<input type="text" id="skill_tags" name="skill_tags">-->
|
||||
<input type="submit" value="注册">-->
|
||||
<form action="register.php" method="post" onsubmit="return validateRegistration()">
|
||||
<label for="username">用户名:</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
|
||||
<label for="email">邮箱:</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
|
||||
<label for="password">密码:</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
|
||||
<label for="confirm-password">确认密码:</label>
|
||||
<input type="password" id="confirm-password" name="confirm-password" required>
|
||||
|
||||
<label for="qq">QQ 号码:</label>
|
||||
<input type="text" id="qq" name="qq" placeholder="请输入您的QQ号码" required pattern="\d{5,11}" title="QQ号码必须是5到11位数字">
|
||||
|
||||
<input type="submit" value="注册">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="terms-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
|
@ -17,7 +17,8 @@ function get_user_full_info($conn, $user_id) {
|
||||
register_time,
|
||||
organization,
|
||||
development_field,
|
||||
skill_tags
|
||||
skill_tags,
|
||||
QQ
|
||||
FROM users
|
||||
WHERE id = ?");
|
||||
$stmt->bind_param("i", $user_id);
|
||||
@ -34,7 +35,8 @@ function get_user_full_info($conn, $user_id) {
|
||||
'register_time' => 'NULL',
|
||||
'organization' => 'NULL',
|
||||
'development_field' => 'NULL',
|
||||
'skill_tags' => 'NULL'
|
||||
'skill_tags' => 'NULL',
|
||||
'QQ' => 'NULL'
|
||||
];
|
||||
}
|
||||
|
||||
@ -47,7 +49,9 @@ $register_time = $user_info['register_time'];
|
||||
$organization = $user_info['organization'];
|
||||
$development_field = $user_info['development_field'];
|
||||
$skill_tags = explode(',', $user_info['skill_tags']);
|
||||
$QQ = $user_info['QQ'];
|
||||
|
||||
$UserPicture = "https://q2.qlogo.cn/headimg_dl?bs=$QQ&dst_uin=$QQ&spec=100&url_enc=0&referer=bu_interface&term_type=PC";
|
||||
$levelDescriptions = [1 => '云境初探者', 2 => '云境行者', 3 => '云智大师', 4 => '云核守护者'];
|
||||
$userLevelDescription = $levelDescriptions[$level] ?? '未知等级';
|
||||
|
||||
@ -67,7 +71,7 @@ $can_create_player = count($user_players) < $max_players;
|
||||
|
||||
// 管理功能 - 获取所有用户
|
||||
function get_all_users($conn) {
|
||||
$stmt = $conn->prepare("SELECT id, username, level, points, email, register_time FROM users");
|
||||
$stmt = $conn->prepare("SELECT id, username, level, points, email, register_time, QQ FROM users");
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$users = $result->fetch_all(MYSQLI_ASSOC);
|
||||
@ -96,6 +100,7 @@ if ($level == 4) {
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$_SESSION['player_token'] = $token;
|
||||
|
||||
|
||||
$conn->close();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
@ -165,9 +170,6 @@ $conn->close();
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-800 font-sans">
|
||||
<div class="fixed inset-0 z-0">
|
||||
<img src="back.jpeg" alt="背景图" class="w-full h-full object-cover opacity-40">
|
||||
</div>
|
||||
<div class="relative z-10">
|
||||
<header class="bg-white shadow-sm sticky top-0 z-10">
|
||||
<div class="container mx-auto px-4 py-3">
|
||||
@ -177,14 +179,9 @@ $conn->close();
|
||||
<h2 class="text-lg font-semibold">圆周云境</h2>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="relative">
|
||||
<button id="notificationBtn" class="text-gray-600 hover:text-primary transition-colors">
|
||||
<i class="fa fa-bell-o text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
<a href="./user_id_card.php"><div class="flex items-center space-x-2">
|
||||
<div class="w-8 h-8 rounded-full bg-gray-200 overflow-hidden">
|
||||
<img src="https://i.bobopic.com/small/87673990.jpg" alt="用户头像" class="w-full h-full object-cover">
|
||||
<img src="<? echo $UserPicture; ?>" alt="用户头像" class="w-full h-full object-cover">;
|
||||
</div>
|
||||
<span class="hidden sm:inline-block text-sm font-medium"><?php echo htmlspecialchars($username); ?></span>
|
||||
</div>
|
||||
@ -214,16 +211,25 @@ $conn->close();
|
||||
</div>
|
||||
|
||||
<div class="border-t border-gray-100 mt-4 pt-4 grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center text-primary">
|
||||
<i class="fa fa-server"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">欢迎</p>
|
||||
<p class="font-semibold"><?php echo htmlspecialchars($username); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center text-primary">
|
||||
<i class="fa fa-user"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">用户名</p>
|
||||
<p class="font-semibold"><?php echo htmlspecialchars($username); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center text-primary">
|
||||
<i class="fa fa-qq"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">QQ</p>
|
||||
<p class="font-semibold"><?php echo htmlspecialchars($QQ); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mb-6">
|
||||
@ -352,7 +358,7 @@ $conn->close();
|
||||
<i class="fa fa-cloud text-primary text-xl"></i>
|
||||
<span class="font-medium">圆周云境用户后台</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 mt-1">© 2025 圆周云境. 保留所有权利</p>
|
||||
<p class="text-sm text-gray-500 mt-1">一切,为了梦,为了你</p>
|
||||
<p class="text-sm text-gray-500 mt-1">本站由<a href="https://rainyun.com">雨云</a>提供技术支持</p>
|
||||
<p class="text-sm text-gray-500 mt-1">请遵守国家法律,本站可能与某些老旧浏览器不兼容 | 背景图片来源:<a href="https://bluearchive-cn.com/">《蔚蓝档案》</a>侵权请联系删除</p>
|
||||
</div>
|
||||
@ -368,7 +374,8 @@ $conn->close();
|
||||
<a href="./cookie.html" class="text-sm text-gray-500 hover:text-primary">Cookie政策</a>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
版本 2.4.1(Alpha)
|
||||
Copyright © 2023-2025 CCSIT Network.All rights reserved.
|
||||
© 圆周云境信息技术 保留所有权利。| 版本:2.8.9(submitID:NULL(未开源))
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -526,6 +533,7 @@ $conn->close();
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">积分</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">邮箱</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">注册时间</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">QQ号码</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -547,6 +555,7 @@ $conn->close();
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900"><?= $user['points'] ?></td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900"><?= htmlspecialchars($user['email']) ?></td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><?= date('Y-m-d', strtotime($user['register_time'])) ?></td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><?= htmlspecialchars($user['QQ']); ?></td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
<button class="text-primary hover:text-primary/80 mr-3 edit-user-btn" data-id="<?= $user['id'] ?>" data-username="<?= htmlspecialchars($user['username']) ?>" data-level="<?= $user['level'] ?>" data-points="<?= $user['points'] ?>" data-email="<?= htmlspecialchars($user['email']) ?>">
|
||||
<i class="fa fa-edit"></i> 编辑
|
||||
|
@ -1 +0,0 @@
|
||||
open_basedir=/www/wwwroot/ccsbeta/console/ins/:/tmp/
|
@ -6,8 +6,8 @@
|
||||
|
||||
// 配置信息 - 请根据实际情况修改
|
||||
$config = [
|
||||
'api_base_url' => 'http://154.222.30.227:23333/api', // 请替换为实际的 API 基础 URL
|
||||
'api_key' => 'c507ddc820254ea8a0ea8f279a961b01', // 重要:请替换为实际 API Key
|
||||
'api_base_url' => 'http://114.66.55.103:23333/api', // 请替换为实际的 API 基础 URL
|
||||
'api_key' => '40d7b2d8df484458816e80c4c9d26f7e', // 重要:请替换为实际 API Key
|
||||
'timeout' => 10,
|
||||
];
|
||||
|
||||
|
88
console/ins_beta/css/styles.css
Executable file
@ -0,0 +1,88 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
|
||||
/* 自定义滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn {
|
||||
@apply px-4 py-2 rounded-md font-medium transition-colors duration-200;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply bg-blue-500 text-white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
@apply bg-blue-600;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
@apply bg-red-500 text-white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
@apply bg-red-600;
|
||||
}
|
||||
|
||||
/* 模态框样式 */
|
||||
.modal {
|
||||
@apply fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
@apply bg-white rounded-lg p-6 w-full max-w-md mx-4;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.table {
|
||||
@apply w-full border-collapse;
|
||||
}
|
||||
|
||||
.table th, .table td {
|
||||
@apply border-b border-gray-200 p-2 text-left;
|
||||
}
|
||||
|
||||
.table th {
|
||||
@apply bg-gray-50;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.card {
|
||||
@apply bg-white rounded-lg shadow-md p-4 mb-4;
|
||||
}
|
||||
|
||||
/* 标签样式 */
|
||||
.tag {
|
||||
@apply inline-block px-2 py-1 rounded-full text-xs font-medium;
|
||||
}
|
||||
|
||||
.tag-success {
|
||||
@apply bg-green-100 text-green-800;
|
||||
}
|
||||
|
||||
.tag-warning {
|
||||
@apply bg-yellow-100 text-yellow-800;
|
||||
}
|
||||
|
||||
.tag-danger {
|
||||
@apply bg-red-100 text-red-800;
|
||||
}
|
91
console/ins_beta/index.html
Executable file
@ -0,0 +1,91 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>MCSManager 实例管理系统</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
<div class="container mx-auto px-4 py-6">
|
||||
<!-- 导航栏 -->
|
||||
<nav class="bg-blue-600 text-white p-4 mb-6 rounded-lg">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="font-bold text-xl">MCSManager 实例管理系统</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<!-- 左侧菜单 -->
|
||||
<div class="w-full md:w-64 bg-white p-4 rounded-lg shadow">
|
||||
<ul class="space-y-2">
|
||||
<li><a href="#" class="block p-2 rounded hover:bg-gray-100" id="dashboardLink">仪表盘</a></li>
|
||||
<li><a href="#" class="block p-2 rounded hover:bg-gray-100" id="instancesLink">实例管理</a></li>
|
||||
<li><a href="#" class="block p-2 rounded hover:bg-gray-100" id="filesLink">文件管理</a></li>
|
||||
<li><a href="#" class="block p-2 rounded hover:bg-gray-100" id="consoleLink">控制台</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 右侧内容区 -->
|
||||
<div class="w-full bg-white p-4 rounded-lg shadow" id="contentArea">
|
||||
<!-- 这里将动态加载不同内容 -->
|
||||
<h2 class="text-2xl mb-4">欢迎使用MCSManager API管理面板</h2>
|
||||
<p class="text-gray-600">
|
||||
您可以通过此界面管理您的Minecraft服务器实例、文件和控制台。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载中模态框 -->
|
||||
<div id="loadingModal" class="fixed top-0 left-0 w-full h-full bg-gray-900 bg-opacity-50 flex items-center justify-center hidden">
|
||||
<div class="animate-spin text-4xl text-blue-500">
|
||||
<i class="fas fa-spinner"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实例信息模态框 -->
|
||||
<div id="instanceModal" class="fixed top-0 left-0 w-full h-full bg-gray-900 bg-opacity-50 flex items-center justify-center hidden">
|
||||
<div class="bg-white p-6 rounded-lg w-96 overflow-y-auto max-h-96">
|
||||
<h2 class="text-2xl mb-4 text-center">实例信息</h2>
|
||||
<div id="instanceInfo"></div>
|
||||
<div class="text-center mt-4">
|
||||
<button id="closeInstanceModal" class="bg-gray-500 text-white px-4 py-1 rounded hover:bg-gray-600">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 控制台模态框 -->
|
||||
<div id="consoleModal" class="fixed top-0 left-0 w-full h-full bg-gray-900 bg-opacity-50 flex items-center justify-center hidden">
|
||||
<div class="bg-white p-6 rounded-lg w-11/12 max-w-5xl overflow-y-auto max-h-screen">
|
||||
<h2 class="text-2xl mb-4 text-center">控制台</h2>
|
||||
<div class="flex flex-col h-96">
|
||||
<div id="consoleOutput" class="overflow-y-auto border border-gray-300 p-2 flex-grow mb-2"></div>
|
||||
<div class="flex">
|
||||
<input type="text" id="consoleInput" class="flex-grow p-2 border border-gray-300 rounded-l" placeholder="输入命令...">
|
||||
<button id="sendConsoleCommand" class="bg-blue-500 text-white px-4 py-1 rounded-r hover:bg-blue-600">发送</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-4">
|
||||
<button id="closeConsoleModal" class="bg-gray-500 text-white px-4 py-1 rounded hover:bg-gray-600">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文件管理模态框 -->
|
||||
<div id="filesModal" class="fixed top-0 left-0 w-full h-full bg-gray-900 bg-opacity-50 flex items-center justify-center hidden">
|
||||
<div class="bg-white p-6 rounded-lg w-11/12 max-w-5xl overflow-y-auto max-h-screen">
|
||||
<h2 class="text-2xl mb-4 text-center">文件管理</h2>
|
||||
<div id="fileContent"></div>
|
||||
<div class="text-center mt-4">
|
||||
<button id="closeFilesModal" class="bg-gray-500 text-white px-4 py-1 rounded hover:bg-gray-600">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/api.js"></script>
|
||||
<script src="js/util.js"></script>
|
||||
</body>
|
||||
</html>
|
668
console/ins_beta/js/api.js
Executable file
@ -0,0 +1,668 @@
|
||||
// 解析URL参数
|
||||
function getQueryParams() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return {
|
||||
serverUUID: urlParams.get('serverUUID'),
|
||||
nodeUUID: urlParams.get('nodeUUID')
|
||||
};
|
||||
}
|
||||
|
||||
// 检查是否包含必要的URL参数
|
||||
function checkParams() {
|
||||
const { serverUUID, nodeUUID } = getQueryParams();
|
||||
|
||||
if (!serverUUID || !nodeUUID) {
|
||||
alert('URL中缺少必要的参数: serverUUID 和 nodeUUID');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 显示加载模态框
|
||||
function showLoadingModal() {
|
||||
document.getElementById('loadingModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 隐藏加载模态框
|
||||
function hideLoadingModal() {
|
||||
document.getElementById('loadingModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
// 加载实例信息
|
||||
function loadInstanceInfo() {
|
||||
showLoadingModal();
|
||||
|
||||
const { serverUUID, nodeUUID } = getQueryParams();
|
||||
|
||||
fetch(`php/api.php?action=getInstanceInfo&serverUUID=${serverUUID}&nodeUUID=${nodeUUID}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
hideLoadingModal();
|
||||
|
||||
if (data.status !== 200) {
|
||||
alert(`获取实例信息失败: ${data.message || '未知错误'}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const instanceInfo = data.data;
|
||||
let html = `
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="card">
|
||||
<h3 class="text-xl mb-2">基础信息</h3>
|
||||
<p><strong>实例名称:</strong> ${instanceInfo.name || '未命名'}</p>
|
||||
<p><strong>实例ID:</strong> ${instanceInfo.id}</p>
|
||||
<p><strong>实例UUID:</strong> ${serverUUID}</p>
|
||||
<p><strong>节点UUID:</strong> ${nodeUUID}</p>
|
||||
<p><strong>状态:</strong> <span class="${instanceInfo.running ? 'text-green-500' : 'text-red-500'}">${instanceInfo.running ? '运行中' : '已停止'}</span></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3 class="text-xl mb-2">运行状态</h3>
|
||||
${instanceInfo.running ? `
|
||||
<p><strong>CPU使用率:</strong> ${instanceInfo.cpuUsage}%</p>
|
||||
<p><strong>内存使用:</strong> ${(instanceInfo.memoryUsage / 1024 / 1024).toFixed(2)} MB / ${(instanceInfo.totalMemory / 1024 / 1024).toFixed(2)} MB</p>
|
||||
<p><strong>运行时间:</strong> ${formatUptime(instanceInfo.uptime)}</p>
|
||||
` : `
|
||||
<p class="text-center py-4">实例未运行</p>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<button id="startInstance" class="btn btn-primary mr-2 ${instanceInfo.running ? 'hidden' : ''}">启动实例</button>
|
||||
<button id="stopInstance" class="btn btn-danger ml-2 ${!instanceInfo.running ? 'hidden' : ''}">停止实例</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('instanceInfo').innerHTML = html;
|
||||
|
||||
// 绑定按钮事件
|
||||
document.getElementById('startInstance')?.addEventListener('click', startInstance);
|
||||
document.getElementById('stopInstance')?.addEventListener('click', stopInstance);
|
||||
|
||||
// 显示模态框
|
||||
document.getElementById('instanceModal').classList.remove('hidden');
|
||||
})
|
||||
.catch(error => {
|
||||
hideLoadingModal();
|
||||
console.error('获取实例信息失败:', error);
|
||||
alert('获取实例信息失败,请检查网络连接或参数是否正确');
|
||||
});
|
||||
}
|
||||
|
||||
// 格式化运行时间
|
||||
function formatUptime(uptime) {
|
||||
const hours = Math.floor(uptime / 3600);
|
||||
const minutes = Math.floor((uptime % 3600) / 60);
|
||||
const seconds = uptime % 60;
|
||||
|
||||
return `${hours}小时 ${minutes}分钟 ${seconds}秒`;
|
||||
}
|
||||
|
||||
// 启动实例
|
||||
function startInstance() {
|
||||
const { serverUUID, nodeUUID } = getQueryParams();
|
||||
|
||||
if (!confirm('确定要启动实例吗?')) return;
|
||||
|
||||
showLoadingModal();
|
||||
|
||||
fetch(`php/api.php?action=startInstance&serverUUID=${serverUUID}&nodeUUID=${nodeUUID}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
hideLoadingModal();
|
||||
|
||||
if (data.status !== 200) {
|
||||
alert(`启动实例失败: ${data.message || '未知错误'}`);
|
||||
return;
|
||||
}
|
||||
|
||||
alert('实例启动成功');
|
||||
// 刷新实例信息
|
||||
loadInstanceInfo();
|
||||
})
|
||||
.catch(error => {
|
||||
hideLoadingModal();
|
||||
console.error('启动实例失败:', error);
|
||||
alert('启动实例失败,请检查网络连接或参数是否正确');
|
||||
});
|
||||
}
|
||||
|
||||
// 停止实例
|
||||
function stopInstance() {
|
||||
const { serverUUID, nodeUUID } = getQueryParams();
|
||||
|
||||
if (!confirm('确定要停止实例吗?')) return;
|
||||
|
||||
showLoadingModal();
|
||||
|
||||
fetch(`php/api.php?action=stopInstance&serverUUID=${serverUUID}&nodeUUID=${nodeUUID}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
hideLoadingModal();
|
||||
|
||||
if (data.status !== 200) {
|
||||
alert(`停止实例失败: ${data.message || '未知错误'}`);
|
||||
return;
|
||||
}
|
||||
|
||||
alert('实例停止成功');
|
||||
// 刷新实例信息
|
||||
loadInstanceInfo();
|
||||
})
|
||||
.catch(error => {
|
||||
hideLoadingModal();
|
||||
console.error('停止实例失败:', error);
|
||||
alert('停止实例失败,请检查网络连接或参数是否正确');
|
||||
});
|
||||
}
|
||||
|
||||
// 加载控制台
|
||||
function loadConsole() {
|
||||
showLoadingModal();
|
||||
|
||||
const { serverUUID, nodeUUID } = getQueryParams();
|
||||
|
||||
fetch(`php/api.php?action=getConsole&serverUUID=${serverUUID}&nodeUUID=${nodeUUID}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
hideLoadingModal();
|
||||
|
||||
if (data.status !== 200) {
|
||||
alert(`获取控制台失败: ${data.message || '未知错误'}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 清空控制台输出
|
||||
document.getElementById('consoleOutput').innerHTML = '';
|
||||
|
||||
// 显示模态框
|
||||
document.getElementById('consoleModal').classList.remove('hidden');
|
||||
|
||||
// 绑定发送命令事件
|
||||
document.getElementById('sendConsoleCommand').addEventListener('click', sendConsoleCommand);
|
||||
|
||||
// 监听输入框回车事件
|
||||
document.getElementById('consoleInput').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
sendConsoleCommand();
|
||||
}
|
||||
});
|
||||
|
||||
// 开始轮询控制台输出
|
||||
startConsolePolling();
|
||||
})
|
||||
.catch(error => {
|
||||
hideLoadingModal();
|
||||
console.error('获取控制台失败:', error);
|
||||
alert('获取控制台失败,请检查网络连接或参数是否正确');
|
||||
});
|
||||
}
|
||||
|
||||
// 发送控制台命令
|
||||
function sendConsoleCommand() {
|
||||
const { serverUUID, nodeUUID } = getQueryParams();
|
||||
const command = document.getElementById('consoleInput').value.trim();
|
||||
|
||||
if (!command) return;
|
||||
|
||||
showLoadingModal();
|
||||
|
||||
fetch(`php/api.php?action=sendConsoleCommand&serverUUID=${serverUUID}&nodeUUID=${nodeUUID}&command=${encodeURIComponent(command)}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
hideLoadingModal();
|
||||
|
||||
if (data.status !== 200) {
|
||||
alert(`发送命令失败: ${data.message || '未知错误'}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加命令到输出
|
||||
addConsoleOutput(`> ${command}\n`);
|
||||
|
||||
// 清空输入框
|
||||
document.getElementById('consoleInput').value = '';
|
||||
})
|
||||
.catch(error => {
|
||||
hideLoadingModal();
|
||||
console.error('发送命令失败:', error);
|
||||
alert('发送命令失败,请检查网络连接或参数是否正确');
|
||||
});
|
||||
}
|
||||
|
||||
// 添加控制台输出
|
||||
function addConsoleOutput(text) {
|
||||
const output = document.getElementById('consoleOutput');
|
||||
output.innerHTML += text;
|
||||
output.scrollTop = output.scrollHeight;
|
||||
}
|
||||
|
||||
// 开始控制台轮询
|
||||
function startConsolePolling() {
|
||||
const { serverUUID, nodeUUID } = getQueryParams();
|
||||
|
||||
// 清除之前的轮询
|
||||
if (window.consolePollingInterval) {
|
||||
clearInterval(window.consolePollingInterval);
|
||||
}
|
||||
|
||||
// 开始新的轮询
|
||||
window.consolePollingInterval = setInterval(() => {
|
||||
fetch(`php/api.php?action=getConsoleOutput&serverUUID=${serverUUID}&nodeUUID=${nodeUUID}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 200) {
|
||||
addConsoleOutput(data.data);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('轮询控制台输出失败:', error);
|
||||
});
|
||||
}, 1000); // 每秒轮询一次
|
||||
}
|
||||
|
||||
// 停止控制台轮询
|
||||
function stopConsolePolling() {
|
||||
if (window.consolePollingInterval) {
|
||||
clearInterval(window.consolePollingInterval);
|
||||
window.consolePollingInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 加载文件管理
|
||||
function loadFiles() {
|
||||
showLoadingModal();
|
||||
|
||||
const { serverUUID, nodeUUID } = getQueryParams();
|
||||
|
||||
fetch(`php/api.php?action=getFiles&serverUUID=${serverUUID}&nodeUUID=${nodeUUID}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
hideLoadingModal();
|
||||
|
||||
if (data.status !== 200) {
|
||||
alert(`获取文件列表失败: ${data.message || '未知错误'}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const files = data.data;
|
||||
|
||||
let html = `
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl">文件管理</h3>
|
||||
<div class="flex">
|
||||
<button id="refreshFiles" class="btn btn-primary mr-2">
|
||||
<i class="fas fa-sync-alt"></i> 刷新
|
||||
</button>
|
||||
<button id="uploadFile" class="btn btn-primary">
|
||||
<i class="fas fa-upload"></i> 上传文件
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-container overflow-x-auto">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>名称</th>
|
||||
<th>类型</th>
|
||||
<th>大小</th>
|
||||
<th>修改时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="fileList">
|
||||
`;
|
||||
|
||||
files.forEach(file => {
|
||||
html += `
|
||||
<tr>
|
||||
<td>${file.name}</td>
|
||||
<td>${file.type}</td>
|
||||
<td>${formatFileSize(file.size)}</td>
|
||||
<td>${new Date(file.modifiedTime).toLocaleString()}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary mr-1 view-file" data-path="${file.path}">
|
||||
查看
|
||||
</button>
|
||||
<button class="btn btn-sm btn-primary mr-1 download-file" data-path="${file.path}">
|
||||
下载
|
||||
</button>
|
||||
${file.type === 'file' ? `
|
||||
<button class="btn btn-sm btn-primary mr-1 edit-file" data-path="${file.path}">
|
||||
编辑
|
||||
</button>
|
||||
` : ''}
|
||||
<button class="btn btn-sm btn-danger delete-file" data-path="${file.path}">
|
||||
删除
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
html += `
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('fileContent').innerHTML = html;
|
||||
|
||||
// 显示模态框
|
||||
document.getElementById('filesModal').classList.remove('hidden');
|
||||
|
||||
// 绑定事件
|
||||
document.getElementById('refreshFiles').addEventListener('click', loadFiles);
|
||||
document.getElementById('uploadFile').addEventListener('click', showUploadFileDialog);
|
||||
|
||||
// 绑定文件操作事件
|
||||
document.querySelectorAll('.view-file').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
viewFile(this.dataset.path);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.download-file').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
downloadFile(this.dataset.path);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.edit-file').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
editFile(this.dataset.path);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.delete-file').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
deleteFile(this.dataset.path);
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
hideLoadingModal();
|
||||
console.error('获取文件列表失败:', error);
|
||||
alert('获取文件列表失败,请检查网络连接或参数是否正确');
|
||||
});
|
||||
}
|
||||
|
||||
// 格式化文件大小
|
||||
function formatFileSize(size) {
|
||||
if (size < 1024) {
|
||||
return `${size} B`;
|
||||
} else if (size < 1024 * 1024) {
|
||||
return `${(size / 1024).toFixed(2)} KB`;
|
||||
} else if (size < 1024 * 1024 * 1024) {
|
||||
return `${(size / 1024 / 1024).toFixed(2)} MB`;
|
||||
} else {
|
||||
return `${(size / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示上传文件对话框
|
||||
function showUploadFileDialog() {
|
||||
const { serverUUID, nodeUUID } = getQueryParams();
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.onchange = function() {
|
||||
if (this.files && this.files[0]) {
|
||||
uploadFile(this.files[0], serverUUID, nodeUUID);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
function uploadFile(file, serverUUID, nodeUUID) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('serverUUID', serverUUID);
|
||||
formData.append('nodeUUID', nodeUUID);
|
||||
formData.append('path', '/'); // 默认上传到根目录
|
||||
|
||||
showLoadingModal();
|
||||
|
||||
fetch('php/api.php?action=uploadFile', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
hideLoadingModal();
|
||||
|
||||
if (data.status !== 200) {
|
||||
alert(`上传文件失败: ${data.message || '未知错误'}`);
|
||||
return;
|
||||
}
|
||||
|
||||
alert('文件上传成功');
|
||||
loadFiles(); // 刷新文件列表
|
||||
})
|
||||
.catch(error => {
|
||||
hideLoadingModal();
|
||||
console.error('上传文件失败:', error);
|
||||
alert('上传文件失败,请检查网络连接或参数是否正确');
|
||||
});
|
||||
}
|
||||
|
||||
// 查看文件
|
||||
function viewFile(path) {
|
||||
const { serverUUID, nodeUUID } = getQueryParams();
|
||||
|
||||
showLoadingModal();
|
||||
|
||||
fetch(`php/api.php?action=viewFile&serverUUID=${serverUUID}&nodeUUID=${nodeUUID}&path=${encodeURIComponent(path)}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
hideLoadingModal();
|
||||
|
||||
if (data.status !== 200) {
|
||||
alert(`查看文件失败: ${data.message || '未知错误'}`);
|
||||
return;
|
||||
}
|
||||
|
||||
alert(`文件内容:\n${data.data}`);
|
||||
})
|
||||
.catch(error => {
|
||||
hideLoadingModal();
|
||||
console.error('查看文件失败:', error);
|
||||
alert('查看文件失败,请检查网络连接或参数是否正确');
|
||||
});
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
function downloadFile(path) {
|
||||
const { serverUUID, nodeUUID } = getQueryParams();
|
||||
|
||||
window.location.href = `php/api.php?action=downloadFile&serverUUID=${serverUUID}&nodeUUID=${nodeUUID}&path=${encodeURIComponent(path)}`;
|
||||
}
|
||||
|
||||
// 编辑文件
|
||||
function editFile(path) {
|
||||
const { serverUUID, nodeUUID } = getQueryParams();
|
||||
|
||||
showLoadingModal();
|
||||
|
||||
fetch(`php/api.php?action=viewFile&serverUUID=${serverUUID}&nodeUUID=${nodeUUID}&path=${encodeURIComponent(path)}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
hideLoadingModal();
|
||||
|
||||
if (data.status !== 200) {
|
||||
alert(`编辑文件失败: ${data.message || '未知错误'}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示编辑模态框
|
||||
const modalHtml = `
|
||||
<div id="editFileModal" class="fixed top-0 left-0 w-full h-full bg-gray-900 bg-opacity-50 flex items-center justify-center">
|
||||
<div class="bg-white p-6 rounded-lg w-11/12 max-w-3xl overflow-y-auto max-h-screen">
|
||||
<h3 class="text-xl mb-4 text-center">编辑文件: ${path.split('/').pop()}</h3>
|
||||
<textarea id="editFileContent" class="w-full h-96 p-2 border border-gray-300">${data.data}</textarea>
|
||||
<div class="text-center mt-4">
|
||||
<button id="saveEditFile" class="bg-blue-500 text-white px-4 py-1 rounded hover:bg-blue-600 mr-2">保存</button>
|
||||
<button id="cancelEditFile" class="bg-gray-500 text-white px-4 py-1 rounded hover:bg-gray-600">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||||
|
||||
// 绑定保存按钮事件
|
||||
document.getElementById('saveEditFile').addEventListener('click', function() {
|
||||
const content = document.getElementById('editFileContent').value;
|
||||
saveEditFile(path, content);
|
||||
});
|
||||
|
||||
// 绑定取消按钮事件
|
||||
document.getElementById('cancelEditFile').addEventListener('click', function() {
|
||||
document.getElementById('editFileModal').remove();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
hideLoadingModal();
|
||||
console.error('编辑文件失败:', error);
|
||||
alert('编辑文件失败,请检查网络连接或参数是否正确');
|
||||
});
|
||||
}
|
||||
|
||||
// 保存编辑的文件
|
||||
function saveEditFile(path, content) {
|
||||
const { serverUUID, nodeUUID } = getQueryParams();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('content', content);
|
||||
formData.append('serverUUID', serverUUID);
|
||||
formData.append('nodeUUID', nodeUUID);
|
||||
formData.append('path', path);
|
||||
|
||||
showLoadingModal();
|
||||
|
||||
fetch('php/api.php?action=saveEditFile', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
hideLoadingModal();
|
||||
|
||||
if (data.status !== 200) {
|
||||
alert(`保存文件失败: ${data.message || '未知错误'}`);
|
||||
return;
|
||||
}
|
||||
|
||||
alert('文件保存成功');
|
||||
document.getElementById('editFileModal').remove();
|
||||
loadFiles(); // 刷新文件列表
|
||||
})
|
||||
.catch(error => {
|
||||
hideLoadingModal();
|
||||
console.error('保存文件失败:', error);
|
||||
alert('保存文件失败,请检查网络连接或参数是否正确');
|
||||
});
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
function deleteFile(path) {
|
||||
const { serverUUID, nodeUUID } = getQueryParams();
|
||||
|
||||
if (!confirm('确定要删除此文件/文件夹吗?此操作不可撤销')) return;
|
||||
|
||||
showLoadingModal();
|
||||
|
||||
fetch(`php/api.php?action=deleteFile&serverUUID=${serverUUID}&nodeUUID=${nodeUUID}&path=${encodeURIComponent(path)}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
hideLoadingModal();
|
||||
|
||||
if (data.status !== 200) {
|
||||
alert(`删除文件失败: ${data.message || '未知错误'}`);
|
||||
return;
|
||||
}
|
||||
|
||||
alert('文件删除成功');
|
||||
loadFiles(); // 刷新文件列表
|
||||
})
|
||||
.catch(error => {
|
||||
hideLoadingModal();
|
||||
console.error('删除文件失败:', error);
|
||||
alert('删除文件失败,请检查网络连接或参数是否正确');
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化页面
|
||||
function initPage() {
|
||||
// 检查URL参数
|
||||
if (!checkParams()) return;
|
||||
|
||||
// 绑定菜单点击事件
|
||||
document.getElementById('dashboardLink').addEventListener('click', function() {
|
||||
document.getElementById('contentArea').innerHTML = `
|
||||
<h2 class="text-2xl mb-4">仪表盘</h2>
|
||||
<p class="text-gray-600">
|
||||
这里将显示实例的概览信息。
|
||||
</p>
|
||||
<button id="viewInstanceInfo" class="btn btn-primary mt-4">查看实例信息</button>
|
||||
`;
|
||||
|
||||
// 绑定按钮事件
|
||||
document.getElementById('viewInstanceInfo').addEventListener('click', loadInstanceInfo);
|
||||
});
|
||||
|
||||
document.getElementById('instancesLink').addEventListener('click', function() {
|
||||
document.getElementById('contentArea').innerHTML = `
|
||||
<h2 class="text-2xl mb-4">实例信息</h2>
|
||||
<button id="refreshInstanceInfo" class="btn btn-primary mb-4">刷新信息</button>
|
||||
<div id="instanceInfoContainer"></div>
|
||||
`;
|
||||
|
||||
// 加载实例信息
|
||||
loadInstanceInfo();
|
||||
|
||||
// 绑定刷新按钮事件
|
||||
document.getElementById('refreshInstanceInfo').addEventListener('click', loadInstanceInfo);
|
||||
});
|
||||
|
||||
document.getElementById('filesLink').addEventListener('click', function() {
|
||||
document.getElementById('contentArea').innerHTML = `
|
||||
<h2 class="text-2xl mb-4">文件管理</h2>
|
||||
<button id="openFilesModal" class="btn btn-primary">打开文件管理</button>
|
||||
`;
|
||||
|
||||
// 绑定按钮事件
|
||||
document.getElementById('openFilesModal').addEventListener('click', loadFiles);
|
||||
});
|
||||
|
||||
document.getElementById('consoleLink').addEventListener('click', function() {
|
||||
document.getElementById('contentArea').innerHTML = `
|
||||
<h2 class="text-2xl mb-4">控制台</h2>
|
||||
<button id="openConsoleModal" class="btn btn-primary">打开控制台</button>
|
||||
`;
|
||||
|
||||
// 绑定按钮事件
|
||||
document.getElementById('openConsoleModal').addEventListener('click', loadConsole);
|
||||
});
|
||||
|
||||
// 绑定模态框关闭事件
|
||||
document.getElementById('closeInstanceModal')?.addEventListener('click', function() {
|
||||
document.getElementById('instanceModal').classList.add('hidden');
|
||||
});
|
||||
|
||||
document.getElementById('closeConsoleModal')?.addEventListener('click', function() {
|
||||
document.getElementById('consoleModal').classList.add('hidden');
|
||||
stopConsolePolling(); // 停止轮询
|
||||
});
|
||||
|
||||
document.getElementById('closeFilesModal')?.addEventListener('click', function() {
|
||||
document.getElementById('filesModal').classList.add('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', initPage);
|
445
console/ins_beta/js/util.js
Executable file
@ -0,0 +1,445 @@
|
||||
// 通用工具函数
|
||||
|
||||
// 显示通知消息
|
||||
function showToast(message, type = 'info') {
|
||||
// 创建通知元素
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast fixed top-4 right-4 p-3 rounded-lg shadow-lg ${type === 'success' ? 'bg-green-500 text-white' : type === 'error' ? 'bg-red-500 text-white' : 'bg-blue-500 text-white'}`;
|
||||
toast.innerHTML = message;
|
||||
|
||||
// 添加到页面
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// 3秒后自动消失
|
||||
setTimeout(() => {
|
||||
toast.style.opacity = '0';
|
||||
toast.style.transition = 'opacity 0.5s ease';
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(toast);
|
||||
}, 500);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
function formatDate(date) {
|
||||
return new Date(date).toLocaleString();
|
||||
}
|
||||
|
||||
// 转换字节为可读格式
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
// 检查响应是否成功
|
||||
function checkResponse(response) {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
// 生成随机字符串
|
||||
function generateRandomString(length = 16) {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 检查是否为有效的UUID
|
||||
function isValidUUID(uuid) {
|
||||
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
return uuidRegex.test(uuid);
|
||||
}
|
||||
|
||||
// URL编码特殊字符
|
||||
function urlEncodeParams(params) {
|
||||
const encodedParams = {};
|
||||
for (const key in params) {
|
||||
encodedParams[key] = encodeURIComponent(params[key]);
|
||||
}
|
||||
return encodedParams;
|
||||
}
|
||||
|
||||
// 将对象转换为查询字符串
|
||||
function objectToQueryString(obj) {
|
||||
return new URLSearchParams(obj).toString();
|
||||
}
|
||||
|
||||
// 获取元素的DOM节点
|
||||
function $(selector) {
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
|
||||
// 显示元素
|
||||
function showElement(element) {
|
||||
element.style.display = 'block';
|
||||
}
|
||||
|
||||
// 隐藏元素
|
||||
function hideElement(element) {
|
||||
element.style.display = 'none';
|
||||
}
|
||||
|
||||
// 设置元素文本内容
|
||||
function setTextContent(element, text) {
|
||||
element.textContent = text;
|
||||
}
|
||||
|
||||
// 获取元素文本内容
|
||||
function getTextContent(element) {
|
||||
return element.textContent;
|
||||
}
|
||||
|
||||
// 添加事件监听器
|
||||
function addEventListener(element, event, handler) {
|
||||
element.addEventListener(event, handler);
|
||||
}
|
||||
|
||||
// 移除事件监听器
|
||||
function removeEventListener(element, event, handler) {
|
||||
element.removeEventListener(event, handler);
|
||||
}
|
||||
|
||||
// 滚动到页面顶部
|
||||
function scrollToTop() {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
|
||||
// 滚动到元素
|
||||
function scrollToElement(element) {
|
||||
element.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
|
||||
// 创建DOM元素
|
||||
function createElement(tagName, className = '', innerHTML = '') {
|
||||
const element = document.createElement(tagName);
|
||||
if (className) {
|
||||
element.className = className;
|
||||
}
|
||||
if (innerHTML) {
|
||||
element.innerHTML = innerHTML;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
// 克隆DOM元素
|
||||
function cloneElement(element) {
|
||||
return element.cloneNode(true);
|
||||
}
|
||||
|
||||
// 移除DOM元素
|
||||
function removeElement(element) {
|
||||
if (element && element.parentNode) {
|
||||
element.parentNode.removeChild(element);
|
||||
}
|
||||
}
|
||||
|
||||
// 插入到DOM元素之前
|
||||
function insertBefore(referenceNode, newNode) {
|
||||
referenceNode.parentNode.insertBefore(newNode, referenceNode);
|
||||
}
|
||||
|
||||
// 插入到DOM元素之后
|
||||
function insertAfter(referenceNode, newNode) {
|
||||
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
|
||||
}
|
||||
|
||||
// 替换DOM元素
|
||||
function replaceElement(oldNode, newNode) {
|
||||
if (oldNode.parentNode) {
|
||||
oldNode.parentNode.replaceChild(newNode, oldNode);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加CSS类
|
||||
function addClass(element, className) {
|
||||
element.classList.add(className);
|
||||
}
|
||||
|
||||
// 移除CSS类
|
||||
function removeClass(element, className) {
|
||||
element.classList.remove(className);
|
||||
}
|
||||
|
||||
// 切换CSS类
|
||||
function toggleClass(element, className) {
|
||||
element.classList.toggle(className);
|
||||
}
|
||||
|
||||
// 检查CSS类是否存在
|
||||
function hasClass(element, className) {
|
||||
return element.classList.contains(className);
|
||||
}
|
||||
|
||||
// 设置CSS样式
|
||||
function setStyle(element, style) {
|
||||
for (const key in style) {
|
||||
element.style[key] = style[key];
|
||||
}
|
||||
}
|
||||
|
||||
// 获取CSS样式
|
||||
function getStyle(element, property) {
|
||||
return window.getComputedStyle(element).getPropertyValue(property);
|
||||
}
|
||||
|
||||
// 设置数据属性
|
||||
function setData(element, key, value) {
|
||||
element.dataset[key] = value;
|
||||
}
|
||||
|
||||
// 获取数据属性
|
||||
function getData(element, key) {
|
||||
return element.dataset[key];
|
||||
}
|
||||
|
||||
// 移除数据属性
|
||||
function removeData(element, key) {
|
||||
delete element.dataset[key];
|
||||
}
|
||||
|
||||
// 获取或设置表单字段值
|
||||
function getFieldValue(element) {
|
||||
const type = element.type;
|
||||
if (type === 'checkbox' || type === 'radio') {
|
||||
return element.checked;
|
||||
}
|
||||
return element.value;
|
||||
}
|
||||
|
||||
function setFieldValue(element, value) {
|
||||
const type = element.type;
|
||||
if (type === 'checkbox' || type === 'radio') {
|
||||
element.checked = value;
|
||||
} else {
|
||||
element.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// 阻止事件冒泡
|
||||
function stopEventPropagation(event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
// 阻止事件默认行为
|
||||
function preventEventDefault(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// 检查元素是否在视口中
|
||||
function isInViewport(element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return (
|
||||
rect.top >= 0 &&
|
||||
rect.left >= 0 &&
|
||||
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
||||
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
||||
);
|
||||
}
|
||||
|
||||
// 观察元素变化
|
||||
function observeElement(element, callback) {
|
||||
const observer = new MutationObserver(callback);
|
||||
observer.observe(element, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
characterData: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
return observer;
|
||||
}
|
||||
|
||||
// 取消观察元素
|
||||
function disconnectObserver(observer) {
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
// 加载外部CSS文件
|
||||
function loadCSS(url) {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = url;
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
// 加载外部JS文件
|
||||
function loadJS(url, callback) {
|
||||
const script = document.createElement('script');
|
||||
script.src = url;
|
||||
script.onload = callback;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
// 获取浏览器窗口大小
|
||||
function getWindowSize() {
|
||||
return {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
};
|
||||
}
|
||||
|
||||
// 监听窗口大小变化
|
||||
function watchWindowSize(callback) {
|
||||
window.addEventListener('resize', callback);
|
||||
}
|
||||
|
||||
// 取消监听窗口大小变化
|
||||
function unwatchWindowSize(callback) {
|
||||
window.removeEventListener('resize', callback);
|
||||
}
|
||||
|
||||
// 模拟点击
|
||||
function simulateClick(element) {
|
||||
const event = new MouseEvent('click', {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
element.dispatchEvent(event);
|
||||
}
|
||||
|
||||
// 创建并下载文件
|
||||
function downloadFileBlob(blob, fileName) {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
|
||||
// 复制文本到剪贴板
|
||||
async function copyToClipboard(text) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('无法复制文本: ', err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 加载更多内容
|
||||
function loadMoreContent(element, loadFn, params = {}) {
|
||||
const loadMoreBtn = document.createElement('button');
|
||||
loadMoreBtn.className = 'btn btn-primary w-full mt-4';
|
||||
loadMoreBtn.textContent = '加载更多';
|
||||
|
||||
element.appendChild(loadMoreBtn);
|
||||
|
||||
loadMoreBtn.addEventListener('click', function() {
|
||||
showLoadingModal();
|
||||
loadFn(params)
|
||||
.then(data => {
|
||||
hideLoadingModal();
|
||||
if (data.status === 200) {
|
||||
element.removeChild(loadMoreBtn);
|
||||
// 处理加载的数据
|
||||
// 这里需要根据具体需求实现
|
||||
} else {
|
||||
alert(`加载失败: ${data.message || '未知错误'}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
hideLoadingModal();
|
||||
console.error('加载失败:', error);
|
||||
alert('加载失败,请检查网络连接');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 创建模态框
|
||||
function createModal(title, content, footer = '') {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'fixed top-0 left-0 w-full h-full bg-gray-900 bg-opacity-50 flex items-center justify-center';
|
||||
modal.innerHTML = `
|
||||
<div class="bg-white p-6 rounded-lg w-96 max-w-full">
|
||||
<h2 class="text-2xl mb-4 text-center">${title}</h2>
|
||||
<div class="mb-4">${content}</div>
|
||||
${footer}
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
return modal;
|
||||
}
|
||||
|
||||
// 关闭模态框
|
||||
function closeModal(modal) {
|
||||
if (modal && modal.parentNode) {
|
||||
modal.parentNode.removeChild(modal);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建确认框
|
||||
function createConfirmModal(message, onConfirm, onCancel = null, title = '确认') {
|
||||
const modal = createModal(title, message, `
|
||||
<div class="flex justify-center gap-2">
|
||||
<button id="confirmBtn" class="btn btn-primary">确认</button>
|
||||
<button id="cancelBtn" class="btn btn-danger">取消</button>
|
||||
</div>
|
||||
`);
|
||||
|
||||
document.getElementById('confirmBtn').addEventListener('click', function() {
|
||||
closeModal(modal);
|
||||
if (typeof onConfirm === 'function') {
|
||||
onConfirm();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('cancelBtn').addEventListener('click', function() {
|
||||
closeModal(modal);
|
||||
if (typeof onCancel === 'function') {
|
||||
onCancel();
|
||||
}
|
||||
});
|
||||
|
||||
return modal;
|
||||
}
|
||||
|
||||
// 创建加载提示
|
||||
function createLoadingTip(message = '加载中...') {
|
||||
const tip = document.createElement('div');
|
||||
tip.className = 'fixed top-0 left-0 w-full h-full bg-gray-900 bg-opacity-50 flex items-center justify-center';
|
||||
tip.innerHTML = `
|
||||
<div class="bg-white p-6 rounded-lg">
|
||||
<div class="animate-spin text-4xl text-blue-500 mb-4">
|
||||
<i class="fas fa-spinner"></i>
|
||||
</div>
|
||||
<p class="text-center">${message}</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(tip);
|
||||
|
||||
return tip;
|
||||
}
|
||||
|
||||
// 移除加载提示
|
||||
function removeLoadingTip(tip) {
|
||||
if (tip && tip.parentNode) {
|
||||
tip.parentNode.removeChild(tip);
|
||||
}
|
||||
}
|
385
console/ins_beta/php/api.php
Executable file
@ -0,0 +1,385 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
|
||||
header('Access-Control-Allow-Headers: Content-Type, X-Requested-With');
|
||||
|
||||
require_once 'config.php';
|
||||
|
||||
// 解析URL参数
|
||||
$action = isset($_GET['action']) ? $_GET['action'] : null;
|
||||
$serverUUID = isset($_GET['serverUUID']) ? $_GET['serverUUID'] : null;
|
||||
$nodeUUID = isset($_GET['nodeUUID']) ? $_GET['nodeUUID'] : null;
|
||||
|
||||
// 校验参数
|
||||
if (!$serverUUID || !$nodeUUID) {
|
||||
echo json_encode([
|
||||
'status' => CONFIG_API_BAD_REQUEST,
|
||||
'message' => '缺少必要的参数: serverUUID 或 nodeUUID'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 根据不同的操作执行不同的API请求
|
||||
switch ($action) {
|
||||
case 'getInstanceInfo':
|
||||
$response = getInstanceInfo($serverUUID, $nodeUUID);
|
||||
break;
|
||||
case 'startInstance':
|
||||
$response = startInstance($serverUUID, $nodeUUID);
|
||||
break;
|
||||
case 'stopInstance':
|
||||
$response = stopInstance($serverUUID, $nodeUUID);
|
||||
break;
|
||||
case 'getConsole':
|
||||
$response = getConsole($serverUUID, $nodeUUID);
|
||||
break;
|
||||
case 'sendConsoleCommand':
|
||||
$command = isset($_GET['command']) ? $_GET['command'] : null;
|
||||
$response = sendConsoleCommand($serverUUID, $nodeUUID, $command);
|
||||
break;
|
||||
case 'getConsoleOutput':
|
||||
$response = getConsoleOutput($serverUUID, $nodeUUID);
|
||||
break;
|
||||
case 'getFiles':
|
||||
$response = getFiles($serverUUID, $nodeUUID);
|
||||
break;
|
||||
case 'viewFile':
|
||||
$path = isset($_GET['path']) ? $_GET['path'] : null;
|
||||
$response = viewFile($serverUUID, $nodeUUID, $path);
|
||||
break;
|
||||
case 'downloadFile':
|
||||
$path = isset($_GET['path']) ? $_GET['path'] : null;
|
||||
downloadFile($serverUUID, $nodeUUID, $path);
|
||||
exit;
|
||||
case 'uploadFile':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode([
|
||||
'status' => CONFIG_API_BAD_REQUEST,
|
||||
'message' => '无效的请求方法'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
$file = $_FILES['file'] ?? null;
|
||||
$path = isset($_POST['path']) ? $_POST['path'] : '/';
|
||||
$response = uploadFile($serverUUID, $nodeUUID, $file, $path);
|
||||
break;
|
||||
case 'saveEditFile':
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode([
|
||||
'status' => CONFIG_API_BAD_REQUEST,
|
||||
'message' => '无效的请求方法'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
$content = isset($_POST['content']) ? $_POST['content'] : null;
|
||||
$path = isset($_POST['path']) ? $_POST['path'] : null;
|
||||
$response = saveEditFile($serverUUID, $nodeUUID, $content, $path);
|
||||
break;
|
||||
case 'deleteFile':
|
||||
$path = isset($_GET['path']) ? $_GET['path'] : null;
|
||||
$response = deleteFile($serverUUID, $nodeUUID, $path);
|
||||
break;
|
||||
default:
|
||||
echo json_encode([
|
||||
'status' => CONFIG_API_BAD_REQUEST,
|
||||
'message' => '无效的操作'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 输出响应
|
||||
echo json_encode($response);
|
||||
|
||||
// 获取实例信息
|
||||
function getInstanceInfo($serverUUID, $nodeUUID) {
|
||||
$url = CONFIG_MCSM_API_URL . "/api/service/remote_services_system?nodeUUID=" . urlencode($nodeUUID) . "&serverUUID=" . urlencode($serverUUID);
|
||||
|
||||
return sendRequest($url, 'GET');
|
||||
}
|
||||
|
||||
// 启动实例
|
||||
function startInstance($serverUUID, $nodeUUID) {
|
||||
$url = CONFIG_MCSM_API_URL . "/api/service/start_server?nodeUUID=" . urlencode($nodeUUID) . "&serverUUID=" . urlencode($serverUUID);
|
||||
|
||||
return sendRequest($url, 'GET');
|
||||
}
|
||||
|
||||
// 停止实例
|
||||
function stopInstance($serverUUID, $nodeUUID) {
|
||||
$url = CONFIG_MCSM_API_URL . "/api/service/stop_server?nodeUUID=" . urlencode($nodeUUID) . "&serverUUID=" . urlencode($serverUUID);
|
||||
|
||||
return sendRequest($url, 'GET');
|
||||
}
|
||||
|
||||
// 获取控制台
|
||||
function getConsole($serverUUID, $nodeUUID) {
|
||||
$url = CONFIG_MCSM_API_URL . "/api/service/console?nodeUUID=" . urlencode($nodeUUID) . "&serverUUID=" . urlencode($serverUUID);
|
||||
|
||||
return sendRequest($url, 'GET');
|
||||
}
|
||||
|
||||
// 发送控制台命令
|
||||
function sendConsoleCommand($serverUUID, $nodeUUID, $command) {
|
||||
if (!$command) {
|
||||
return [
|
||||
'status' => CONFIG_API_BAD_REQUEST,
|
||||
'message' => '命令不能为空'
|
||||
];
|
||||
}
|
||||
|
||||
$url = CONFIG_MCSM_API_URL . "/api/service/console?nodeUUID=" . urlencode($nodeUUID) . "&serverUUID=" . urlencode($serverUUID);
|
||||
|
||||
return sendRequest($url, 'POST', json_encode(['command' => $command]));
|
||||
}
|
||||
|
||||
// 获取控制台输出
|
||||
function getConsoleOutput($serverUUID, $nodeUUID) {
|
||||
$url = CONFIG_MCSM_API_URL . "/api/service/console?nodeUUID=" . urlencode($nodeUUID) . "&serverUUID=" . urlencode($serverUUID);
|
||||
|
||||
return sendRequest($url, 'GET');
|
||||
}
|
||||
|
||||
// 获取文件列表
|
||||
function getFiles($serverUUID, $nodeUUID) {
|
||||
$url = CONFIG_MCSM_API_URL . "/api/service/files?nodeUUID=" . urlencode($nodeUUID) . "&serverUUID=" . urlencode($serverUUID);
|
||||
|
||||
return sendRequest($url, 'GET');
|
||||
}
|
||||
|
||||
// 查看文件内容
|
||||
function viewFile($serverUUID, $nodeUUID, $path) {
|
||||
if (!$path) {
|
||||
return [
|
||||
'status' => CONFIG_API_BAD_REQUEST,
|
||||
'message' => '文件路径不能为空'
|
||||
];
|
||||
}
|
||||
|
||||
$url = CONFIG_MCSM_API_URL . "/api/service/file_content?nodeUUID=" . urlencode($nodeUUID) . "&serverUUID=" . urlencode($serverUUID);
|
||||
|
||||
return sendRequest($url, 'POST', json_encode(['path' => $path]));
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
function downloadFile($serverUUID, $nodeUUID, $path) {
|
||||
if (!$path) {
|
||||
echo json_encode([
|
||||
'status' => CONFIG_API_BAD_REQUEST,
|
||||
'message' => '文件路径不能为空'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$url = CONFIG_MCSM_API_URL . "/api/service/file_download?nodeUUID=" . urlencode($nodeUUID) . "&serverUUID=" . urlencode($serverUUID);
|
||||
|
||||
try {
|
||||
// 发送请求到MCSManager API
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['path' => $path]));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json; charset=utf-8',
|
||||
'X-Requested-With: XMLHttpRequest'
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, CONFIG_API_TIMEOUT);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
// 检查API响应状态码
|
||||
if ($httpCode !== CONFIG_API_SUCCESS) {
|
||||
echo json_encode([
|
||||
'status' => $httpCode,
|
||||
'message' => 'API请求失败'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if ($data['status'] !== CONFIG_API_SUCCESS) {
|
||||
echo json_encode([
|
||||
'status' => $data['status'],
|
||||
'message' => $data['message'] ?? '未知错误'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 设置下载响应头
|
||||
header('Content-Type: application/octet-stream');
|
||||
header('Content-Disposition: attachment; filename="' . basename($path) . '"');
|
||||
header('Content-Length: ' . strlen($data['data']));
|
||||
|
||||
// 输出文件内容
|
||||
echo $data['data'];
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([
|
||||
'status' => CONFIG_API_INTERNAL_ERROR,
|
||||
'message' => '下载文件失败: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
function uploadFile($serverUUID, $nodeUUID, $file, $path) {
|
||||
if (!$file || !is_uploaded_file($file['tmp_name'])) {
|
||||
return [
|
||||
'status' => CONFIG_API_BAD_REQUEST,
|
||||
'message' => '未上传有效的文件'
|
||||
];
|
||||
}
|
||||
|
||||
$url = CONFIG_MCSM_API_URL . "/api/service/file_upload?nodeUUID=" . urlencode($nodeUUID) . "&serverUUID=" . urlencode($serverUUID);
|
||||
|
||||
try {
|
||||
// 打开文件
|
||||
$fileData = file_get_contents($file['tmp_name']);
|
||||
|
||||
// 发送请求到MCSManager API
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, [
|
||||
'path' => $path,
|
||||
'file' => base64_encode($fileData)
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/x-www-form-urlencoded; charset=utf-8',
|
||||
'X-Requested-With: XMLHttpRequest'
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, CONFIG_API_TIMEOUT);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
// 检查API响应状态码
|
||||
if ($httpCode !== CONFIG_API_SUCCESS) {
|
||||
return [
|
||||
'status' => $httpCode,
|
||||
'message' => 'API请求失败'
|
||||
];
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if ($data['status'] !== CONFIG_API_SUCCESS) {
|
||||
return [
|
||||
'status' => $data['status'],
|
||||
'message' => $data['message'] ?? '未知错误'
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => CONFIG_API_SUCCESS,
|
||||
'message' => '文件上传成功'
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'status' => CONFIG_API_INTERNAL_ERROR,
|
||||
'message' => '上传文件失败: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 保存编辑的文件
|
||||
function saveEditFile($serverUUID, $nodeUUID, $content, $path) {
|
||||
if (!$content || !$path) {
|
||||
return [
|
||||
'status' => CONFIG_API_BAD_REQUEST,
|
||||
'message' => '内容或路径不能为空'
|
||||
];
|
||||
}
|
||||
|
||||
$url = CONFIG_MCSM_API_URL . "/api/service/file_content?nodeUUID=" . urlencode($nodeUUID) . "&serverUUID=" . urlencode($serverUUID);
|
||||
|
||||
return sendRequest($url, 'PUT', json_encode(['path' => $path, 'content' => $content]));
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
function deleteFile($serverUUID, $nodeUUID, $path) {
|
||||
if (!$path) {
|
||||
return [
|
||||
'status' => CONFIG_API_BAD_REQUEST,
|
||||
'message' => '文件路径不能为空'
|
||||
];
|
||||
}
|
||||
|
||||
$url = CONFIG_MCSM_API_URL . "/api/service/file_delete?nodeUUID=" . urlencode($nodeUUID) . "&serverUUID=" . urlencode($serverUUID);
|
||||
|
||||
return sendRequest($url, 'DELETE', json_encode(['path' => $path]));
|
||||
}
|
||||
|
||||
// 发送API请求
|
||||
function sendRequest($url, $method = 'GET', $data = null) {
|
||||
try {
|
||||
// 发送请求到MCSManager API
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
// 设置请求方法
|
||||
switch ($method) {
|
||||
case 'POST':
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||
break;
|
||||
case 'PUT':
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||
break;
|
||||
case 'DELETE':
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||
break;
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json; charset=utf-8',
|
||||
'X-Requested-With: XMLHttpRequest'
|
||||
]);
|
||||
|
||||
// 设置超时时间
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, CONFIG_API_TIMEOUT);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
// 检查API响应状态码
|
||||
if ($httpCode !== CONFIG_API_SUCCESS) {
|
||||
return [
|
||||
'status' => $httpCode,
|
||||
'message' => 'API请求失败'
|
||||
];
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if ($data['status'] !== CONFIG_API_SUCCESS) {
|
||||
return [
|
||||
'status' => $data['status'],
|
||||
'message' => $data['message'] ?? '未知错误'
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => CONFIG_API_SUCCESS,
|
||||
'data' => $data['data'] ?? []
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'status' => CONFIG_API_INTERNAL_ERROR,
|
||||
'message' => '请求失败: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
21
console/ins_beta/php/config.php
Executable file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
// 配置文件
|
||||
|
||||
// MCSManager API的基本URL
|
||||
define('CONFIG_MCSM_API_URL', 'http://http://114.66.55.103:23333'); // 替换为你的MCSManager API地址
|
||||
|
||||
// 默认的API超时时间(秒)
|
||||
define('CONFIG_API_TIMEOUT', 10);
|
||||
|
||||
// 错误代码映射
|
||||
define('CONFIG_API_SUCCESS', 200);
|
||||
define('CONFIG_API_BAD_REQUEST', 400);
|
||||
define('CONFIG_API_FORBIDDEN', 403);
|
||||
define('CONFIG_API_INTERNAL_ERROR', 500);
|
||||
|
||||
// 日志配置
|
||||
define('CONFIG_LOG_ENABLED', true);
|
||||
define('CONFIG_LOG_FILE', __DIR__ . '/app.log');
|
||||
|
||||
// 允许的请求方法
|
||||
define('CONFIG_ALLOWED_METHODS', ['GET', 'POST', 'PUT', 'DELETE']);
|
649
console/motd.html
Executable file
@ -0,0 +1,649 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CCS | Minecraft服务器在线状态查询</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: '#5865F2',
|
||||
secondary: '#3BA55C',
|
||||
danger: '#ED4245',
|
||||
dark: '#23272A',
|
||||
light: '#F2F3F5',
|
||||
},
|
||||
fontFamily: {
|
||||
minecraft: ['"Minecraft"', 'sans-serif'],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style type="text/tailwindcss">
|
||||
@layer utilities {
|
||||
.content-auto {
|
||||
content-visibility: auto;
|
||||
}
|
||||
.minecraft-font {
|
||||
font-family: 'Minecraft', sans-serif;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.pixel-borders {
|
||||
box-shadow: 0 -4px 0 4px rgba(0, 0, 0, 0.2),
|
||||
0 4px 0 4px rgba(255, 255, 255, 0.2),
|
||||
-4px 0 0 4px rgba(0, 0, 0, 0.2),
|
||||
4px 0 0 4px rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
.animate-pulse-slow {
|
||||
animation: pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
.minecraft-obfuscated {
|
||||
animation: obfuscate 0.5s steps(4) infinite;
|
||||
}
|
||||
.loading-overlay {
|
||||
@apply fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-40 opacity-0 pointer-events-none transition-opacity duration-300;
|
||||
}
|
||||
.loading-overlay.active {
|
||||
@apply opacity-100 pointer-events-auto;
|
||||
}
|
||||
}
|
||||
@keyframes obfuscate {
|
||||
0% { opacity: 1; }
|
||||
25% { opacity: 0.25; }
|
||||
50% { opacity: 0.5; }
|
||||
75% { opacity: 0.75; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
.server-card {
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.server-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.player-avatar {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.player-avatar:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
</style>
|
||||
<!-- 加载Minecraft字体 -->
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Minecraft';
|
||||
src: url('https://fonts.cdnfonts.com/css/minecraft-4');
|
||||
font-display: swap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gradient-to-br from-gray-900 to-gray-800 min-h-screen text-light">
|
||||
<!-- 页面顶部装饰 -->
|
||||
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary via-secondary to-danger"></div>
|
||||
|
||||
<div class="container mx-auto px-4 py-12 max-w-4xl">
|
||||
<!-- 标题区域 -->
|
||||
<header class="text-center mb-12">
|
||||
<h1 class="text-[clamp(2rem,5vw,3.5rem)] font-bold mb-4 text-transparent bg-clip-text bg-gradient-to-r from-primary to-secondary minecraft-font tracking-wider">
|
||||
Minecraft(JE)状态查询
|
||||
</h1>
|
||||
<p class="text-gray-300 text-lg max-w-2xl mx-auto">
|
||||
CCSNetwork | 圆周云境技术部门
|
||||
</p>
|
||||
<!-- 自动刷新指示器 -->
|
||||
<div id="autoRefreshIndicator" class="mt-4 text-sm text-gray-400">
|
||||
<span id="refreshCountdown">20</span>秒后自动刷新
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<main class="bg-dark/80 backdrop-blur-md rounded-xl p-6 border border-gray-700/50 relative server-card">
|
||||
<!-- 加载遮罩 -->
|
||||
<div id="loadingOverlay" class="loading-overlay">
|
||||
<div class="bg-dark p-8 rounded-lg shadow-xl flex flex-col items-center">
|
||||
<div class="inline-block animate-spin rounded-full h-12 w-12 border-b-2 border-primary mb-4"></div>
|
||||
<p class="text-gray-300">正在刷新服务器状态...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 服务器输入表单 -->
|
||||
<div class="mb-8">
|
||||
<form id="serverForm" class="flex flex-col sm:flex-row gap-4">
|
||||
<div class="flex-1 relative">
|
||||
<label for="serverAddress" class="block text-sm font-medium text-gray-300 mb-1">服务器地址</label>
|
||||
<div class="relative">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-400">
|
||||
<i class="fa fa-server"></i>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
id="serverAddress"
|
||||
placeholder="play.example.com"
|
||||
value="mc.xinpan.xin"
|
||||
class="w-full bg-gray-800 border border-gray-700 rounded-lg py-3 pl-10 pr-4 focus:ring-2 focus:ring-primary/50 focus:border-primary outline-none transition-all duration-300"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full sm:w-32">
|
||||
<label for="serverPort" class="block text-sm font-medium text-gray-300 mb-1">端口 (可选)</label>
|
||||
<div class="relative">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-400">
|
||||
<i class="fa fa-link"></i>
|
||||
</span>
|
||||
<input
|
||||
type="number"
|
||||
id="serverPort"
|
||||
placeholder="25565"
|
||||
value="17112"
|
||||
min="1"
|
||||
max="65535"
|
||||
class="w-full bg-gray-800 border border-gray-700 rounded-lg py-3 pl-10 pr-4 focus:ring-2 focus:ring-primary/50 focus:border-primary outline-none transition-all duration-300"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full sm:w-auto flex items-end">
|
||||
<button
|
||||
type="submit"
|
||||
id="fetchButton"
|
||||
class="w-full sm:w-auto bg-gradient-to-r from-primary to-primary/80 hover:from-primary/90 hover:to-primary/70 text-white font-semibold py-3 px-6 rounded-lg shadow-lg hover:shadow-primary/30 transform hover:-translate-y-0.5 transition-all duration-300 flex items-center justify-center gap-2"
|
||||
>
|
||||
<i class="fa fa-search"></i>
|
||||
<span>查询服务器</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 服务器信息结果区域 -->
|
||||
<div id="serverInfo" class="hidden mt-8">
|
||||
<!-- 服务器状态指示器 -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<div id="statusIndicator" class="w-4 h-4 rounded-full bg-danger"></div>
|
||||
<h2 id="serverName" class="text-xl font-bold text-white"></h2>
|
||||
</div>
|
||||
<div class="text-sm text-gray-400">
|
||||
<span id="lastUpdated">刚刚更新</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 服务器MOTD显示 -->
|
||||
<div id="serverMotd" class="bg-gray-800/50 rounded-lg p-4 mb-6 border border-gray-700/30 pixel-borders min-h-[100px] flex items-center justify-center minecraft-font text-lg text-center">
|
||||
<!-- MOTD将在这里显示 -->
|
||||
</div>
|
||||
|
||||
<!-- 服务器详细信息 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||
<div class="bg-gray-800/30 rounded-lg p-4 border border-gray-700/30 hover:border-primary/50 transition-all duration-300">
|
||||
<div class="text-gray-400 text-sm mb-1">版本</div>
|
||||
<div id="serverVersion" class="font-semibold"></div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-800/30 rounded-lg p-4 border border-gray-700/30 hover:border-primary/50 transition-all duration-300">
|
||||
<div class="text-gray-400 text-sm mb-1">在线玩家</div>
|
||||
<div id="playerCount" class="font-semibold"></div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-800/30 rounded-lg p-4 border border-gray-700/30 hover:border-primary/50 transition-all duration-300">
|
||||
<div class="text-gray-400 text-sm mb-1">延迟</div>
|
||||
<div id="serverPing" class="font-semibold"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 玩家列表 -->
|
||||
<div id="playersSection" class="hidden bg-gray-800/30 rounded-lg p-4 border border-gray-700/30 mb-6">
|
||||
<h3 class="text-lg font-semibold mb-3 flex items-center gap-2">
|
||||
<i class="fa fa-users"></i>
|
||||
<span>在线玩家</span>
|
||||
<span id="playerCountBadge" class="bg-primary/20 text-primary text-xs px-2 py-0.5 rounded-full"></span>
|
||||
</h3>
|
||||
<div id="playerList" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-2">
|
||||
<!-- 玩家头像将在这里动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 服务器图标 -->
|
||||
<div id="serverIconSection" class="hidden">
|
||||
<h3 class="text-lg font-semibold mb-3 flex items-center gap-2">
|
||||
<i class="fa fa-image"></i>
|
||||
<span>服务器图标</span>
|
||||
</h3>
|
||||
<div class="bg-gray-800/30 rounded-lg p-4 border border-gray-700/30 flex justify-center">
|
||||
<img id="serverIcon" src="" alt="服务器图标" class="rounded-lg max-w-[128px] max-h-[128px]">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 调试信息(仅开发阶段显示) -->
|
||||
<div id="debugInfo" class="hidden mt-6 bg-gray-800/50 rounded-lg p-4 border border-gray-700/30">
|
||||
<h3 class="text-lg font-semibold mb-2 text-gray-300 flex items-center justify-between">
|
||||
<span>调试信息</span>
|
||||
<button id="closeDebugBtn" class="text-gray-400 hover:text-white transition-colors">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</h3>
|
||||
<pre id="rawData" class="text-xs text-gray-400 max-h-64 overflow-y-auto"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 错误信息区域 -->
|
||||
<div id="errorMessage" class="hidden bg-danger/10 border border-danger/30 rounded-lg p-4 mt-6">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="text-danger mt-0.5">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-danger">无法获取服务器信息</h3>
|
||||
<p id="errorText" class="text-sm text-gray-300 mt-1"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="mt-12 text-center text-gray-500 text-sm">
|
||||
<p>© 2025 圆周云境. | 数据通过MCAPI获取</p>
|
||||
<p class="mt-2">本工具不隶属于Minecraft或Mojang Studios</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 全局变量
|
||||
let autoRefreshInterval = null;
|
||||
let refreshCountdown = 20;
|
||||
let currentServer = 'mc.xinpan.xin';
|
||||
let currentPort = 17112;
|
||||
let debugInfoVisible = false; // 跟踪调试信息框的显示状态
|
||||
|
||||
// 颜色代码转换函数 - 将Minecraft颜色代码转换为HTML颜色
|
||||
function convertMinecraftColors(text) {
|
||||
if (!text) return '';
|
||||
|
||||
// 替换Minecraft颜色代码
|
||||
const colorMap = {
|
||||
'§0': '<span style="color: #000000;">', // 黑色
|
||||
'§1': '<span style="color: #0000AA;">', // 深蓝色
|
||||
'§2': '<span style="color: #00AA00;">', // 深绿色
|
||||
'§3': '<span style="color: #00AAAA;">', // 湖蓝色
|
||||
'§4': '<span style="color: #AA0000;">', // 深红色
|
||||
'§5': '<span style="color: #AA00AA;">', // 紫色
|
||||
'§6': '<span style="color: #FFAA00;">', // 金色
|
||||
'§7': '<span style="color: #AAAAAA;">', // 灰色
|
||||
'§8': '<span style="color: #555555;">', // 深灰色
|
||||
'§9': '<span style="color: #5555FF;">', // 蓝色
|
||||
'§a': '<span style="color: #55FF55;">', // 绿色
|
||||
'§b': '<span style="color: #55FFFF;">', // 天蓝色
|
||||
'§c': '<span style="color: #FF5555;">', // 红色
|
||||
'§d': '<span style="color: #FF55FF;">', // 粉色
|
||||
'§e': '<span style="color: #FFFF55;">', // 黄色
|
||||
'§f': '<span style="color: #FFFFFF;">', // 白色
|
||||
'§k': '<span class="minecraft-obfuscated">', // 随机字符(混淆)
|
||||
'§l': '<span style="font-weight: bold;">', // 粗体
|
||||
'§m': '<span style="text-decoration: line-through;">', // 删除线
|
||||
'§n': '<span style="text-decoration: underline;">', // 下划线
|
||||
'§o': '<span style="font-style: italic;">', // 斜体
|
||||
'§r': '</span>' // 重置
|
||||
};
|
||||
|
||||
// 处理特殊格式
|
||||
let formattedText = text;
|
||||
|
||||
// 先处理换行符
|
||||
formattedText = formattedText.replace(/\n/g, '<br>');
|
||||
|
||||
// 处理颜色代码
|
||||
Object.keys(colorMap).forEach(code => {
|
||||
formattedText = formattedText.replace(new RegExp(code, 'g'), colorMap[code]);
|
||||
});
|
||||
|
||||
// 确保所有标签都有闭合标签
|
||||
const openTags = (formattedText.match(/<span/g) || []).length;
|
||||
const closeTags = (formattedText.match(/<\/span>/g) || []).length;
|
||||
if (openTags > closeTags) {
|
||||
formattedText += '</span>'.repeat(openTags - closeTags);
|
||||
}
|
||||
|
||||
return formattedText;
|
||||
}
|
||||
|
||||
// 解析MOTD对象为文本
|
||||
function parseMotd(motd) {
|
||||
if (!motd) return '';
|
||||
|
||||
// 处理特殊的MCAPI格式(包含clean/html/raw数组)
|
||||
if (motd.raw && Array.isArray(motd.raw)) {
|
||||
return motd.raw.join('\n');
|
||||
}
|
||||
|
||||
// 处理字符串格式
|
||||
if (typeof motd === 'string') {
|
||||
return motd;
|
||||
}
|
||||
|
||||
// 处理JSON对象格式
|
||||
if (motd.text) {
|
||||
return motd.text;
|
||||
}
|
||||
|
||||
// 处理包含extra数组的格式
|
||||
if (motd.extra && Array.isArray(motd.extra)) {
|
||||
return motd.extra.map(part => {
|
||||
// 处理嵌套的extra数组
|
||||
if (part.extra && Array.isArray(part.extra)) {
|
||||
return part.extra.map(p => p.text || '').join('');
|
||||
}
|
||||
return part.text || '';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// 未知格式
|
||||
console.warn('无法解析MOTD格式:', motd);
|
||||
return JSON.stringify(motd, null, 2);
|
||||
}
|
||||
|
||||
// 更新最后更新时间
|
||||
function updateLastUpdatedTime() {
|
||||
const now = new Date();
|
||||
const hours = now.getHours().toString().padStart(2, '0');
|
||||
const minutes = now.getMinutes().toString().padStart(2, '0');
|
||||
const seconds = now.getSeconds().toString().padStart(2, '0');
|
||||
document.getElementById('lastUpdated').textContent = `最后更新: ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
// 显示错误信息
|
||||
function showError(message) {
|
||||
document.getElementById('serverInfo').classList.add('hidden');
|
||||
document.getElementById('errorMessage').classList.remove('hidden');
|
||||
document.getElementById('errorText').textContent = message;
|
||||
stopAutoRefresh(); // 发生错误时停止自动刷新
|
||||
hideLoading(); // 确保在出错时隐藏加载遮罩
|
||||
}
|
||||
|
||||
// 隐藏错误信息
|
||||
function hideError() {
|
||||
document.getElementById('errorMessage').classList.add('hidden');
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
function showLoading() {
|
||||
const loadingOverlay = document.getElementById('loadingOverlay');
|
||||
loadingOverlay.classList.add('active');
|
||||
hideError();
|
||||
}
|
||||
|
||||
// 隐藏加载状态
|
||||
function hideLoading() {
|
||||
const loadingOverlay = document.getElementById('loadingOverlay');
|
||||
loadingOverlay.classList.remove('active');
|
||||
}
|
||||
|
||||
// 更新倒计时显示
|
||||
function updateCountdown() {
|
||||
const countdownEl = document.getElementById('refreshCountdown');
|
||||
if (countdownEl) {
|
||||
countdownEl.textContent = refreshCountdown;
|
||||
}
|
||||
}
|
||||
|
||||
// 开始自动刷新
|
||||
function startAutoRefresh() {
|
||||
if (!currentServer) return; // 没有当前服务器,不启动自动刷新
|
||||
|
||||
refreshCountdown = 20;
|
||||
updateCountdown();
|
||||
|
||||
// 清除之前的定时器
|
||||
if (autoRefreshInterval) {
|
||||
clearInterval(autoRefreshInterval);
|
||||
}
|
||||
|
||||
// 设置新的定时器
|
||||
autoRefreshInterval = setInterval(() => {
|
||||
refreshCountdown--;
|
||||
updateCountdown();
|
||||
|
||||
if (refreshCountdown <= 0) {
|
||||
fetchServerInfo(currentServer, currentPort);
|
||||
refreshCountdown = 20;
|
||||
updateLastUpdatedTime();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// 停止自动刷新
|
||||
function stopAutoRefresh() {
|
||||
if (autoRefreshInterval) {
|
||||
clearInterval(autoRefreshInterval);
|
||||
autoRefreshInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取服务器信息
|
||||
async function fetchServerInfo(address, port = 25565) {
|
||||
try {
|
||||
// 保存当前服务器和端口
|
||||
currentServer = address;
|
||||
currentPort = port;
|
||||
|
||||
showLoading();
|
||||
|
||||
// 使用MCAPI获取服务器信息
|
||||
const response = await fetch(`https://api.mcsrvstat.us/3/${address}:${port}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API请求失败: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('服务器数据:', data); // 调试日志
|
||||
|
||||
// 保存调试信息框的当前状态
|
||||
debugInfoVisible = !document.getElementById('debugInfo').classList.contains('hidden');
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('获取服务器信息时出错:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
// 无论成功或失败,都隐藏加载遮罩
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
// 显示服务器信息
|
||||
function displayServerInfo(data) {
|
||||
// 显示服务器信息区域
|
||||
document.getElementById('serverInfo').classList.remove('hidden');
|
||||
|
||||
// 更新服务器名称和状态
|
||||
const statusIndicator = document.getElementById('statusIndicator');
|
||||
const serverName = document.getElementById('serverName');
|
||||
|
||||
if (data.online) {
|
||||
statusIndicator.className = 'w-4 h-4 rounded-full bg-secondary animate-pulse-slow';
|
||||
serverName.textContent = data.hostname || currentServer;
|
||||
} else {
|
||||
statusIndicator.className = 'w-4 h-4 rounded-full bg-danger';
|
||||
serverName.textContent = `${data.hostname || currentServer} (离线)`;
|
||||
}
|
||||
|
||||
// 更新MOTD
|
||||
const serverMotd = document.getElementById('serverMotd');
|
||||
if (data.motd) {
|
||||
const motdText = parseMotd(data.motd);
|
||||
if (motdText) {
|
||||
serverMotd.innerHTML = convertMinecraftColors(motdText);
|
||||
} else {
|
||||
serverMotd.textContent = 'MOTD为空';
|
||||
}
|
||||
} else {
|
||||
serverMotd.textContent = '无MOTD信息';
|
||||
}
|
||||
|
||||
// 更新版本信息
|
||||
document.getElementById('serverVersion').textContent = data.version || '未知版本';
|
||||
|
||||
// 更新玩家信息
|
||||
const playerCount = document.getElementById('playerCount');
|
||||
const playersSection = document.getElementById('playersSection');
|
||||
const playerList = document.getElementById('playerList');
|
||||
const playerCountBadge = document.getElementById('playerCountBadge');
|
||||
|
||||
if (data.players && data.players.online !== undefined) {
|
||||
playerCount.textContent = `${data.players.online}/${data.players.max}`;
|
||||
|
||||
if (data.players.list && data.players.list.length > 0) {
|
||||
playerCountBadge.textContent = `${data.players.list.length} 名玩家`;
|
||||
playersSection.classList.remove('hidden');
|
||||
|
||||
// 清空玩家列表
|
||||
playerList.innerHTML = '';
|
||||
|
||||
// 添加玩家头像
|
||||
data.players.list.forEach(player => {
|
||||
const playerCard = document.createElement('div');
|
||||
playerCard.className = 'bg-gray-700/50 rounded-lg p-2 flex flex-col items-center hover:bg-gray-700 transition-colors duration-300';
|
||||
playerCard.innerHTML = `
|
||||
<img src="https://minotar.net/avatar/${player}/64" alt="${player}的头像" class="w-12 h-12 rounded-full mb-1 player-avatar">
|
||||
<span class="text-xs truncate w-full text-center">${player}</span>
|
||||
`;
|
||||
playerList.appendChild(playerCard);
|
||||
});
|
||||
} else {
|
||||
playersSection.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
playerCount.textContent = '未知';
|
||||
playersSection.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 更新延迟信息(改进版)
|
||||
const pingValue = getValidPing(data);
|
||||
document.getElementById('serverPing').textContent = pingValue !== null ? `${pingValue}ms` : '未知';
|
||||
|
||||
// 更新服务器图标
|
||||
const serverIconSection = document.getElementById('serverIconSection');
|
||||
const serverIcon = document.getElementById('serverIcon');
|
||||
|
||||
if (data.icon) {
|
||||
serverIconSection.classList.remove('hidden');
|
||||
serverIcon.src = data.icon;
|
||||
} else {
|
||||
serverIconSection.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 更新最后更新时间
|
||||
updateLastUpdatedTime();
|
||||
|
||||
// 恢复调试信息框的状态
|
||||
if (debugInfoVisible) {
|
||||
document.getElementById('debugInfo').classList.remove('hidden');
|
||||
} else {
|
||||
document.getElementById('debugInfo').classList.add('hidden');
|
||||
}
|
||||
|
||||
// 启动自动刷新(即使之前出错)
|
||||
startAutoRefresh();
|
||||
}
|
||||
|
||||
// 获取有效的ping值
|
||||
function getValidPing(data) {
|
||||
// 尝试从不同位置获取ping值
|
||||
if (typeof data.ping === 'number') {
|
||||
return Math.round(data.ping);
|
||||
}
|
||||
|
||||
if (data.debug && typeof data.debug.ping === 'number') {
|
||||
return Math.round(data.debug.ping);
|
||||
}
|
||||
|
||||
// 检查可能的其他位置...
|
||||
|
||||
return null; // 没有找到有效的ping值
|
||||
}
|
||||
|
||||
// 表单提交处理
|
||||
document.getElementById('serverForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const address = document.getElementById('serverAddress').value.trim();
|
||||
const portInput = document.getElementById('serverPort').value;
|
||||
const port = portInput ? parseInt(portInput, 10) : 25565;
|
||||
|
||||
console.log('提交请求:', address, port); // 调试日志
|
||||
|
||||
if (!address) {
|
||||
showError('请输入有效的服务器地址');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await fetchServerInfo(address, port);
|
||||
|
||||
if (!data) {
|
||||
throw new Error('未获取到服务器数据');
|
||||
}
|
||||
|
||||
// 显示原始数据(用于调试)
|
||||
document.getElementById('rawData').textContent = JSON.stringify(data, null, 2);
|
||||
|
||||
displayServerInfo(data);
|
||||
} catch (error) {
|
||||
showError(`获取服务器信息失败: ${error.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
// 关闭调试信息按钮
|
||||
document.getElementById('closeDebugBtn').addEventListener('click', function() {
|
||||
document.getElementById('debugInfo').classList.add('hidden');
|
||||
debugInfoVisible = false;
|
||||
});
|
||||
|
||||
// 页面加载完成后自动查询初始服务器
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 自动查询初始服务器
|
||||
fetchServerInfo(currentServer, currentPort)
|
||||
.then(data => {
|
||||
if (data) {
|
||||
// 显示原始数据(用于调试)
|
||||
document.getElementById('rawData').textContent = JSON.stringify(data, null, 2);
|
||||
displayServerInfo(data);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showError(`获取初始服务器信息失败: ${error.message}`);
|
||||
});
|
||||
|
||||
// 添加键盘快捷键(按F12显示/隐藏调试信息)
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'F12') {
|
||||
e.preventDefault();
|
||||
const debugInfo = document.getElementById('debugInfo');
|
||||
debugInfo.classList.toggle('hidden');
|
||||
debugInfoVisible = !debugInfo.classList.contains('hidden');
|
||||
|
||||
// 如果显示调试信息,则更新原始数据
|
||||
if (debugInfoVisible) {
|
||||
fetchServerInfo(currentServer, currentPort)
|
||||
.then(data => {
|
||||
if (data) {
|
||||
document.getElementById('rawData').textContent = JSON.stringify(data, null, 2);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('更新调试信息失败:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
309
console/tools.html
Executable file
@ -0,0 +1,309 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>实用工具导航</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: '#165DFF',
|
||||
secondary: '#00B42A',
|
||||
accent: '#FF7D00',
|
||||
neutral: '#1D2129',
|
||||
'neutral-light': '#F2F3F5',
|
||||
},
|
||||
fontFamily: {
|
||||
inter: ['Inter', 'system-ui', 'sans-serif'],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style type="text/tailwindcss">
|
||||
@layer utilities {
|
||||
.content-auto {
|
||||
content-visibility: auto;
|
||||
}
|
||||
.card-hover {
|
||||
@apply transition-all duration-300 hover:shadow-lg hover:-translate-y-1;
|
||||
}
|
||||
.category-title {
|
||||
@apply text-xl font-bold mb-4 flex items-center text-neutral;
|
||||
}
|
||||
.tool-card {
|
||||
@apply bg-white rounded-lg p-4 card-hover border border-gray-100;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 font-inter min-h-screen">
|
||||
<header class="bg-white shadow-sm sticky top-0 z-50">
|
||||
<div class="container mx-auto px-4 py-4 flex justify-between items-center">
|
||||
<div class="flex items-center space-x-2">
|
||||
<i class="fa fa-th-large text-primary text-2xl"></i>
|
||||
<h1 class="text-xl font-bold text-neutral">实用工具导航</h1>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="text" placeholder="搜索工具..." class="pl-10 pr-4 py-2 rounded-full border border-gray-200 focus:outline-none focus:ring-2 focus:ring-primary/20 w-64 transition-all duration-300">
|
||||
<i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container mx-auto px-4 py-8">
|
||||
<!-- 开发工具 -->
|
||||
<section class="mb-12">
|
||||
<h2 class="category-title">
|
||||
<i class="fa fa-code text-primary mr-2"></i>
|
||||
开发工具
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
<a href="https://github.com" class="tool-card" target="_blank">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center text-primary">
|
||||
<i class="fa fa-github"></i>
|
||||
</div>
|
||||
<h3 class="ml-3 font-semibold">GitHub</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">代码托管与协作平台</p>
|
||||
</a>
|
||||
|
||||
<a href="https://code.visualstudio.com" class="tool-card" target="_blank">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-orange-100 flex items-center justify-center text-accent">
|
||||
<i class="fa fa-terminal"></i>
|
||||
</div>
|
||||
<h3 class="ml-3 font-semibold">VS Code</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">强大的开源代码编辑器</p>
|
||||
</a>
|
||||
|
||||
<a href="https://www.mongodb.com" class="tool-card" target="_blank">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center text-secondary">
|
||||
<i class="fa fa-database"></i>
|
||||
</div>
|
||||
<h3 class="ml-3 font-semibold">MongoDB</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">NoSQL 数据库</p>
|
||||
</a>
|
||||
|
||||
<a href="https://apifox.com/" class="tool-card" target="_blank">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center text-purple-600">
|
||||
<i class="fa fa-flask"></i>
|
||||
</div>
|
||||
<h3 class="ml-3 font-semibold">Apifox</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">API 开发与测试工具</p>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 安全工具 -->
|
||||
<section class="mb-12">
|
||||
<h2 class="category-title">
|
||||
<i class="fa fa-shield text-red-500 mr-2"></i>
|
||||
安全工具
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
<a href="https://www.virustotal.com" class="tool-card" target="_blank">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-red-100 flex items-center justify-center text-red-600">
|
||||
<i class="fa fa-bug"></i>
|
||||
</div>
|
||||
<h3 class="ml-3 font-semibold">VirusTotal</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">多引擎文件安全扫描</p>
|
||||
</a>
|
||||
|
||||
<a href="https://www.nmap.org" class="tool-card" target="_blank">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center text-primary">
|
||||
<i class="fa fa-search"></i>
|
||||
</div>
|
||||
<h3 class="ml-3 font-semibold">Nmap</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">网络扫描与安全审计</p>
|
||||
</a>
|
||||
|
||||
<a href="https://www.wireshark.org" class="tool-card" target="_blank">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center text-secondary">
|
||||
<i class="fa fa-wifi"></i>
|
||||
</div>
|
||||
<h3 class="ml-3 font-semibold">Wireshark</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">网络协议分析工具</p>
|
||||
</a>
|
||||
|
||||
<a href="https://haveibeenpwned.com" class="tool-card" target="_blank">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center text-purple-600">
|
||||
<i class="fa fa-lock"></i>
|
||||
</div>
|
||||
<h3 class="ml-3 font-semibold">Have I Been Pwned</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">账号泄露查询</p>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 设计工具 -->
|
||||
<section class="mb-12">
|
||||
<h2 class="category-title">
|
||||
<i class="fa fa-paint-brush text-accent mr-2"></i>
|
||||
设计工具
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
<a href="https://www.figma.com" class="tool-card" target="_blank">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-red-100 flex items-center justify-center text-red-600">
|
||||
<i class="fa fa-picture-o"></i>
|
||||
</div>
|
||||
<h3 class="ml-3 font-semibold">Figma</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">矢量图形设计工具</p>
|
||||
</a>
|
||||
|
||||
<a href="https://www.adobe.com/products/photoshop.html" class="tool-card" target="_blank">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center text-primary">
|
||||
<i class="fa fa-image"></i>
|
||||
</div>
|
||||
<h3 class="ml-3 font-semibold">Adobe Photoshop</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">专业图像处理软件</p>
|
||||
</a>
|
||||
|
||||
<a href="https://www.canva.com" class="tool-card" target="_blank">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center text-secondary">
|
||||
<i class="fa fa-font"></i>
|
||||
</div>
|
||||
<h3 class="ml-3 font-semibold">Canva</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">在线设计平台</p>
|
||||
</a>
|
||||
|
||||
<a href="https://www.sketch.com" class="tool-card" target="_blank">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center text-purple-600">
|
||||
<i class="fa fa-object-group"></i>
|
||||
</div>
|
||||
<h3 class="ml-3 font-semibold">Sketch</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">界面设计工具</p>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 效率工具 -->
|
||||
<section class="mb-12">
|
||||
<h2 class="category-title">
|
||||
<i class="fa fa-bolt text-secondary mr-2"></i>
|
||||
效率工具
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
<a href="https://trello.com" class="tool-card" target="_blank">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center text-primary">
|
||||
<i class="fa fa-tasks"></i>
|
||||
</div>
|
||||
<h3 class="ml-3 font-semibold">Trello</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">看板任务管理工具</p>
|
||||
</a>
|
||||
|
||||
<a href="https://www.notion.so" class="tool-card" target="_blank">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-orange-100 flex items-center justify-center text-accent">
|
||||
<i class="fa fa-calendar"></i>
|
||||
</div>
|
||||
<h3 class="ml-3 font-semibold">Notion</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">一体化工作空间</p>
|
||||
</a>
|
||||
|
||||
<a href="https://slack.com" class="tool-card" target="_blank">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center text-secondary">
|
||||
<i class="fa fa-users"></i>
|
||||
</div>
|
||||
<h3 class="ml-3 font-semibold">Slack</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">团队沟通协作平台</p>
|
||||
</a>
|
||||
|
||||
<a href="https://www.rescuetime.com" class="tool-card" target="_blank">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center text-purple-600">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
</div>
|
||||
<h3 class="ml-3 font-semibold">RescueTime</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">时间管理与效率追踪</p>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer py-12 border-t border-primary/20">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 mb-8">
|
||||
<div>
|
||||
<div class="flex items-center space-x-2 mb-4">
|
||||
<span class="text-xl font-bold text-black">圆周云境</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm" >一切,为了梦,为了你</p>
|
||||
<p class="text-gray-400 text-sm">本站由:<a href="rainyun.com" class="text-gray-400 text-sm"> 雨云 提供技术支持</a></p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-black font-bold mb-4">使用协议</h4>
|
||||
<ul class="space-y-2">
|
||||
<li><a href="./console/cookie.html" class="text-gray-400 hover:text-primary transition-colors">cookies政策</a></li>
|
||||
<li><a href="./console/terms.html" class="text-gray-400 hover:text-primary transition-colors">隐私政策</a></li>
|
||||
<li><a href="https://gitea.ccsnetwork.cn/CCSIT/ccswebsite/commits/branch/main" class="text-gray-400 hover:text-primary transition-colors">更新日志</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-black font-bold mb-4">社区</h4>
|
||||
<ul class="space-y-2">
|
||||
<li><a href="https://qm.qq.com/cgi-bin/qm/qr?k=GFwl1pDbLeeUGOh3kuVH0664EIdI-N5w&jump_from=webapi&authKey=UzTFWTWasv6xxgl7aU+kHAb80/Ps/gA4AWoJ3v5r4hfj07iDIXBl/sMfY+teTnf3" class="text-gray-400 hover:text-blue-400 transition-colors">QQ群</a></li>
|
||||
<li><a href="https://space.bilibili.com/1382004137?spm_id_from=333.1007.0.0" class="text-gray-400 hover:text-blue-400 transition-colors">Bilibili</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-8 border-t border-primary/10 text-center text-gray-500 text-sm">
|
||||
Copyright © 2023-2025 CCSIT Network.All rights reserved.
|
||||
© 圆周云境信息技术 保留所有权利。| 版本:2.8.9(submitID:NULL(未开源))<br>请遵守国家法律,本站可能与某些老旧浏览器不兼容 | 背景图片来源:《蔚蓝档案》侵权请联系删除
|
||||
<br />ICP备案/许可证号:<a href="https://beian.miit.gov.cn/" target="_blank">赣ICP备2025065357号</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// 添加卡片悬停效果的交互逻辑
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const toolCards = document.querySelectorAll('.tool-card');
|
||||
|
||||
toolCards.forEach(card => {
|
||||
card.addEventListener('mouseenter', function() {
|
||||
this.classList.add('shadow-lg', '-translate-y-1');
|
||||
});
|
||||
|
||||
card.addEventListener('mouseleave', function() {
|
||||
this.classList.remove('shadow-lg', '-translate-y-1');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
28
get_server_status.php
Executable file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
// get_server_status.php
|
||||
|
||||
// 设置服务器IP和端口
|
||||
$serverIp = "9666.fun";
|
||||
$serverPort = 23418;
|
||||
|
||||
// 构造API请求URL
|
||||
$apiUrl = "https://forum.blmcpia.com/open-api/motdpe/api.php?ip=$serverIp&port=$serverPort";
|
||||
|
||||
// 使用cURL获取数据
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $apiUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
// 检查是否有错误
|
||||
if ($response === false) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Failed to fetch server status"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 返回JSON数据
|
||||
header('Content-Type: application/json');
|
||||
echo $response;
|
||||
?>
|
494
haresworld.html
Executable file
@ -0,0 +1,494 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CCSIT | Hare's World服务器</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: '#8B5CF6', // 蔚蓝档案主色调:紫色
|
||||
secondary: '#C084FC', // 浅紫色
|
||||
accent: '#EC4899', // 强调色:粉色
|
||||
dark: '#121826', // 深色背景
|
||||
light: '#F3F4F6', // 浅色文字
|
||||
wa: '#8B5CF6', // 蔚蓝档案紫色
|
||||
'wa-dark': '#5B21B6', // 深紫色
|
||||
'wa-light': '#C084FC', // 浅紫色
|
||||
'wa-accent': '#EC4899', // 粉色强调
|
||||
},
|
||||
fontFamily: {
|
||||
minecraft: ['"Minecraft"', 'sans-serif'],
|
||||
inter: ['Inter', 'sans-serif'],
|
||||
wa: ['"Segoe UI"', 'Roboto', 'sans-serif'], // Added fallback fonts
|
||||
},
|
||||
animation: {
|
||||
'float': 'float 3s ease-in-out infinite',
|
||||
'pulse-slow': 'pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
||||
'bounce-slow': 'bounce 3s infinite',
|
||||
},
|
||||
keyframes: {
|
||||
float: {
|
||||
'0%, 100%': { transform: 'translateY(0)' },
|
||||
'50%': { transform: 'translateY(-10px)' },
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style type="text/tailwindcss">
|
||||
@layer utilities {
|
||||
.content-auto {
|
||||
content-visibility: auto;
|
||||
}
|
||||
.text-shadow {
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.text-shadow-lg {
|
||||
text-shadow: 3px 3px 6px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
.bg-grid {
|
||||
background-image:
|
||||
linear-gradient(rgba(139, 92, 246, 0.05) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(139, 92, 246, 0.05) 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
.wa-title {
|
||||
@apply font-wa text-wa text-shadow-lg tracking-wide;
|
||||
}
|
||||
.wa-card {
|
||||
@apply bg-dark/60 backdrop-blur-sm rounded-xl border border-wa/20 hover:border-wa/40 transition-all duration-500 hover:shadow-lg hover:shadow-wa/20;
|
||||
}
|
||||
.rank-badge {
|
||||
@apply inline-block px-3 py-1 rounded-full text-xs font-bold uppercase;
|
||||
}
|
||||
.nav-link {
|
||||
@apply relative text-white/80 hover:text-white transition-colors duration-300 after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-0 after:bg-wa after:transition-all after:duration-300 hover:after:w-full;
|
||||
}
|
||||
.nav-link.active {
|
||||
@apply text-white after:w-full;
|
||||
}
|
||||
.btn-primary {
|
||||
@apply bg-wa hover:bg-wa-dark text-white font-bold py-3 px-8 rounded-lg transition-all duration-300 transform hover:-translate-y-1 hover:shadow-lg hover:shadow-wa/30;
|
||||
}
|
||||
.btn-secondary {
|
||||
@apply bg-dark/50 hover:bg-dark/70 text-white font-bold py-3 px-8 rounded-lg transition-all duration-300 transform hover:-translate-y-1 hover:shadow-lg;
|
||||
}
|
||||
.scrollbar-hidden::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.scrollbar-hidden {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.animate-float {
|
||||
animation: float 3s ease-in-out infinite;
|
||||
}
|
||||
.animate-pulse-slow {
|
||||
animation: pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body class="font-inter bg-dark bg-grid text-light overflow-x-hidden">
|
||||
<!-- 导航栏 -->
|
||||
<nav class="fixed top-0 left-0 right-0 z-50 transition-all duration-300" id="mainNav">
|
||||
<div class="container mx-auto px-4 py-4 flex justify-between items-center">
|
||||
<div class="flex items-center">
|
||||
<i class="fa fa-cube text-wa text-2xl mr-3 animate-pulse"></i>
|
||||
<h1 class="text-xl font-bold text-white">Hare's<span class="text-wa"> World</span></h1>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 页面内容 -->
|
||||
<main>
|
||||
<!-- 首页 -->
|
||||
<section id="home" class="min-h-screen flex items-center justify-center relative overflow-hidden">
|
||||
<div class="absolute inset-0 bg-[url('https://picsum.photos/id/142/1920/1080')] bg-cover bg-center opacity-20"></div>
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-dark via-dark/80 to-dark"></div>
|
||||
|
||||
<div class="container mx-auto px-4 pt-24 pb-16 relative z-10">
|
||||
<div class="max-w-4xl mx-auto text-center">
|
||||
<div class="mb-8 animate-pulse">
|
||||
<i class="fa fa-cube text-wa text-5xl"></i>
|
||||
</div>
|
||||
<h1 class="text-[clamp(2.5rem,8vw,5rem)] font-bold text-white mb-6 text-shadow-lg">
|
||||
Hare's<span class="wa-title"> World</span>
|
||||
</h1>
|
||||
<p class="text-[clamp(1.2rem,3vw,1.8rem)] text-white/90 mb-12 max-w-3xl mx-auto text-shadow">
|
||||
探索无限可能
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row justify-center gap-6 mb-16">
|
||||
<div class="flex items-center bg-dark/50 backdrop-blur-sm p-4 rounded-xl border border-wa/20">
|
||||
<span class="inline-block w-3 h-3 rounded-full bg-green-500 animate-pulse mr-3"></span>
|
||||
<div>
|
||||
<div class="text-white/60 text-sm">当前在线</div>
|
||||
<div class="text-white text-xl font-bold current-players">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center bg-dark/50 backdrop-blur-sm p-4 rounded-xl border border-wa/20">
|
||||
<i class="fa fa-clock-o text-wa mr-3"></i>
|
||||
<div>
|
||||
<div class="text-white/60 text-sm">服务器运行时间</div>
|
||||
<div class="text-white text-xl font-bold">24/7 不间断</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap justify-center gap-4">
|
||||
<a href="#features" class="btn-primary">
|
||||
<i class="fa fa-rocket mr-2"></i> 探索特色
|
||||
</a>
|
||||
<a href="#join" class="btn-secondary">
|
||||
<i class="fa fa-gamepad mr-2"></i> 立即加入
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="absolute bottom-8 left-0 right-0 flex justify-center animate-bounce">
|
||||
<a href="#features" class="text-white/70 hover:text-white transition-colors">
|
||||
<i class="fa fa-angle-down text-3xl"></i>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 特色系统 -->
|
||||
<section id="features" class="min-h-screen flex items-center justify-center relative py-24">
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-dark to-wa-dark/10"></div>
|
||||
<div class="container mx-auto px-4 relative z-10">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-[clamp(2rem,6vw,4rem)] font-bold text-white mb-4 text-shadow">
|
||||
<span class="wa-title">服务器</span> 特色系统
|
||||
</h2>
|
||||
<p class="text-white/70 max-w-2xl mx-auto text-lg">
|
||||
我们的服务器整合了多种独特功能,打造沉浸式游戏体验
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
<!-- 特色卡片1 -->
|
||||
<div class="wa-card p-8 group">
|
||||
<div class="w-16 h-16 bg-wa/20 rounded-full flex items-center justify-center mb-6 mx-auto transform group-hover:scale-110 transition-transform">
|
||||
<i class="fa fa-user-circle text-2xl text-wa"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-white mb-4 group-hover:text-wa transition-colors">角色称号系统</h3>
|
||||
<p class="text-white/70">独特称号,从"兔耳新生"到"联邦学生会",展现你的游戏成就</p>
|
||||
</div>
|
||||
|
||||
<!-- 特色卡片2 -->
|
||||
<div class="wa-card p-8 group">
|
||||
<div class="w-16 h-16 bg-wa/20 rounded-full flex items-center justify-center mb-6 mx-auto transform group-hover:scale-110 transition-transform">
|
||||
<i class="fa fa-trophy text-2xl text-wa"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-white mb-4 group-hover:text-wa transition-colors">RTP插件</h3>
|
||||
<p class="text-white/70">随机传送,在主世界正负20000格传送,让您的每个基地都独一无二</p>
|
||||
</div>
|
||||
|
||||
<!-- 特色卡片3 -->
|
||||
<div class="wa-card p-8 group">
|
||||
<div class="w-16 h-16 bg-wa/20 rounded-full flex items-center justify-center mb-6 mx-auto transform group-hover:scale-110 transition-transform">
|
||||
<i class="fa fa-university text-2xl text-wa"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-white mb-4 group-hover:text-wa transition-colors">生电优化</h3>
|
||||
<p class="text-white/70">leaves服务核心,轻度生电,养老毫无问题</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 称号与等级 -->
|
||||
<section id="ranks" class="min-h-screen flex items-center justify-center relative py-24">
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-wa-dark/10 to-wa-dark/20"></div>
|
||||
<div class="container mx-auto px-4 relative z-10">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-[clamp(2rem,6vw,4rem)] font-bold text-white mb-4 text-shadow">
|
||||
<span class="wa-title">称号系统</span>
|
||||
</h2>
|
||||
<p class="text-white/70 max-w-2xl mx-auto text-lg">
|
||||
通过游戏成就解锁独特称号,展现你的实力与地位
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
<!-- 等级1 -->
|
||||
<div class="wa-card p-8 overflow-hidden relative group">
|
||||
<div class="absolute -right-4 -top-4 w-24 h-24 bg-wa/20 rounded-full"></div>
|
||||
<div class="relative">
|
||||
<div class="flex justify-between items-start mb-6">
|
||||
<div class="rank-badge bg-wa-dark text-white">🐇兔耳新生</div>
|
||||
<div class="text-3xl font-bold text-wa">Player</div>
|
||||
</div>
|
||||
|
||||
<div class="h-1 bg-wa/30 rounded-full mb-6">
|
||||
<div class="h-full bg-wa rounded-full" style="width: 10%"></div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-bold text-white mb-4">“新生,欢迎来到基沃托斯”</h3>
|
||||
<p class="text-white/70 mb-6">
|
||||
作为学院的新生,你将接受基础训练,学习基本技能和游戏机制。
|
||||
</p>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center">
|
||||
<i class="fa fa-check text-green-400 mr-2"></i>
|
||||
<span class="text-white/80">校园探索权限</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<i class="fa fa-check text-green-400 mr-2"></i>
|
||||
<span class="text-white/80">基础奖励包</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 pt-6 border-t border-wa/10">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<div class="text-white/60 text-sm">称号达成条件:</div>
|
||||
<div class="text-white font-bold">进入服务器自动获取</div>
|
||||
</div>
|
||||
<button class="text-wa hover:text-wa-light transition-colors">
|
||||
<i class="fa fa-arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 等级2 -->
|
||||
<div class="wa-card p-8 overflow-hidden relative group transform hover:-translate-y-2 transition-transform">
|
||||
<div class="absolute -right-4 -top-4 w-24 h-24 bg-wa/30 rounded-full"></div>
|
||||
<div class="relative">
|
||||
<div class="flex justify-between items-start mb-6">
|
||||
<div class="rank-badge bg-wa text-white">🥕胡萝卜特攻</div>
|
||||
<div class="text-3xl font-bold text-wa">Player</div>
|
||||
</div>
|
||||
|
||||
<div class="h-1 bg-wa/30 rounded-full mb-6">
|
||||
<div class="h-full bg-wa rounded-full" style="width: 50%"></div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-bold text-white mb-4">“WOW,你升级了!!!”</h3>
|
||||
<p class="text-white/70 mb-6">
|
||||
作为学院的高年级学生,你将接受更好的学院资助,探索世界。
|
||||
</p>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center">
|
||||
<i class="fa fa-check text-green-400 mr-2"></i>
|
||||
<span class="text-white/80">高级奖励解锁</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<i class="fa fa-check text-green-400 mr-2"></i>
|
||||
<span class="text-white/80">特殊能力解锁</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 pt-6 border-t border-wa/10">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<div class="text-white/60 text-sm">称号达成条件:</div>
|
||||
<div class="text-white font-bold">5*金胡萝卜</div>
|
||||
</div>
|
||||
<button class="text-wa hover:text-wa-light transition-colors">
|
||||
<i class="fa fa-arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 等级3 -->
|
||||
<div class="wa-card p-8 overflow-hidden relative group">
|
||||
<div class="absolute -right-4 -top-4 w-24 h-24 bg-wa-light/30 rounded-full"></div>
|
||||
<div class="relative">
|
||||
<div class="flex justify-between items-start mb-6">
|
||||
<div class="rank-badge bg-wa-light text-dark">⚔战术指挥部\★联邦学生会</div>
|
||||
<div class="text-3xl font-bold text-wa-light">Admin</div>
|
||||
</div>
|
||||
|
||||
<div class="h-1 bg-wa/30 rounded-full mb-6">
|
||||
<div class="h-full bg-wa-light rounded-full" style="width: 100%"></div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-bold text-white mb-4">“看,那是学生会成员,可不要在他们眼前干坏事(⊙o⊙)”</h3>
|
||||
<p class="text-white/70 mb-6">
|
||||
达到学院最高等级,掌握终极权限,维护基沃托斯的安全稳定。
|
||||
</p>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center">
|
||||
<i class="fa fa-check text-green-400 mr-2"></i>
|
||||
<span class="text-white/80">终极管理员权限解锁</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<i class="fa fa-check text-green-400 mr-2"></i>
|
||||
<span class="text-white/80">专属指挥官装备礼包</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 pt-6 border-t border-wa/10">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<div class="text-white/60 text-sm">称号达成条件:</div>
|
||||
<div class="text-white font-bold">咨询任意:"联邦学生会"成员申请</div>
|
||||
</div>
|
||||
<button class="text-wa-light hover:text-white transition-colors">
|
||||
<i class="fa fa-star"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 原本称号展示部分 -->
|
||||
<div class="mt-16 text-center">
|
||||
<h3 class="text-2xl font-bold text-white mb-8">进入服务器步骤</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 max-w-4xl mx-auto">
|
||||
<!-- 步骤 1 -->
|
||||
<div class="wa-card p-4 text-center">
|
||||
<div class="text-wa text-4xl mb-2">
|
||||
<i class="fa fa-download"></i>
|
||||
</div>
|
||||
<div class="rank-badge bg-wa-dark text-white mb-2">步骤 1</div>
|
||||
<p class="text-white/60 text-sm">下载1.18-1.21任意版本的 Minecraft 游戏客户端。</p>
|
||||
</div>
|
||||
|
||||
<!-- 步骤 2 -->
|
||||
<div class="wa-card p-4 text-center">
|
||||
<div class="text-wa text-4xl mb-2">
|
||||
<i class="fa fa-cog"></i>
|
||||
</div>
|
||||
<div class="rank-badge bg-wa text-white mb-2">步骤 2</div>
|
||||
<p class="text-white/60 text-sm">安装游戏客户端,确保网络连接正常。</p>
|
||||
</div>
|
||||
|
||||
<!-- 步骤 3 -->
|
||||
<div class="wa-card p-4 text-center">
|
||||
<div class="text-wa text-4xl mb-2">
|
||||
<i class="fa fa-plus"></i>
|
||||
</div>
|
||||
<div class="rank-badge bg-wa-dark text-white mb-2">步骤 3</div>
|
||||
<p class="text-white/60 text-sm">在游戏客户端中添加服务器地址:play.mc.ccsnetwork.cn。</p>
|
||||
</div>
|
||||
|
||||
<!-- 步骤 4 -->
|
||||
<div class="wa-card p-4 text-center">
|
||||
<div class="text-wa text-4xl mb-2">
|
||||
<i class="fa fa-play"></i>
|
||||
</div>
|
||||
<div class="rank-badge bg-wa-light text-dark mb-2">步骤 4</div>
|
||||
<p class="text-white/60 text-sm">点击连接服务器,开始游戏。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
<footer class="bg-dark/90 backdrop-blur-md py-12">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 mb-8">
|
||||
<div>
|
||||
<div class="flex items-center space-x-2 mb-4">
|
||||
<span class="text-xl font-bold text-white">圆周云境</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm" >一切,为了梦,为了你</p>
|
||||
<p class="text-gray-400 text-sm">本站由:<a href="rainyun.com" class="text-gray-400 text-sm"> 雨云 提供技术支持</a></p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-white font-bold mb-4">使用协议</h4>
|
||||
<ul class="space-y-2">
|
||||
<li><a href="./console/cookie.html" class="text-gray-400 hover:text-primary transition-colors">cookies政策</a></li>
|
||||
<li><a href="./console/terms.html" class="text-gray-400 hover:text-primary transition-colors">隐私政策</a></li>
|
||||
<li><a href="https://gitea.ccsnetwork.cn/CCSIT/ccswebsite/commits/branch/main" class="text-gray-400 hover:text-primary transition-colors">更新日志</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-white font-bold mb-4">社区</h4>
|
||||
<ul class="space-y-2">
|
||||
<li><a href="https://qm.qq.com/cgi-bin/qm/qr?k=GFwl1pDbLeeUGOh3kuVH0664EIdI-N5w&jump_from=webapi&authKey=UzTFWTWasv6xxgl7aU+kHAb80/Ps/gA4AWoJ3v5r4hfj07iDIXBl/sMfY+teTnf3" class="text-white/60 hover:text-blue-400 transition-colors">QQ群</a></li>
|
||||
<li><a href="https://space.bilibili.com/1382004137?spm_id_from=333.1007.0.0" class="text-white/60 hover:text-blue-400 transition-colors">Bilibili</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-8 border-t border-primary/10 text-center text-gray-500 text-sm">
|
||||
Copyright © 2023-2025 CCSIT Network.All rights reserved.
|
||||
© 圆周云境信息技术 保留所有权利。| 版本:2.8.9(submitID:NULL(未开源))
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
// 动态加载服务器数据
|
||||
async function fetchServerData() {
|
||||
try {
|
||||
// 调用PHP后端接口
|
||||
const response = await fetch("get_server_status.php");
|
||||
const data = await response.json();
|
||||
|
||||
// 检查是否有错误
|
||||
if (data.error) {
|
||||
console.error("获取服务器数据失败:", data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新在线玩家数量
|
||||
document.querySelector(".current-players").textContent = `${data.online}/${data.max} 玩家`;
|
||||
|
||||
// 更新服务器版本
|
||||
document.querySelector(".server-version").textContent = data.version;
|
||||
|
||||
// 更新服务器状态
|
||||
const serverStatus = document.querySelector(".server-status");
|
||||
if (data.status === "online") {
|
||||
serverStatus.classList.remove("text-red-500");
|
||||
serverStatus.classList.add("text-green-500");
|
||||
serverStatus.textContent = "在线";
|
||||
} else {
|
||||
serverStatus.classList.remove("text-green-500");
|
||||
serverStatus.classList.add("text-red-500");
|
||||
serverStatus.textContent = "离线";
|
||||
}
|
||||
|
||||
// 更新服务器延迟
|
||||
document.querySelector(".server-delay").textContent = `${data.delay}ms`;
|
||||
|
||||
// 更新服务器广播内容 (MOTD)
|
||||
document.querySelector(".server-motd").textContent = data.motd;
|
||||
|
||||
// 更新游戏模式
|
||||
document.querySelector(".server-gamemode").textContent = data.gamemode;
|
||||
|
||||
// 更新协议版本
|
||||
document.querySelector(".server-agreement").textContent = data.agreement;
|
||||
|
||||
// 如果需要,更新服务器图标
|
||||
const serverIcon = document.querySelector(".server-icon");
|
||||
if (data.icon) {
|
||||
serverIcon.src = data.icon;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取服务器数据失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后调用
|
||||
window.onload = fetchServerData;
|
||||
|
||||
// 每30秒刷新一次数据
|
||||
setInterval(fetchServerData, 30000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
BIN
img/Hare'sWorld.png
Executable file
After Width: | Height: | Size: 3.8 MiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 2.1 MiB |
BIN
img/web.png
Executable file
After Width: | Height: | Size: 1.1 MiB |
1427
index.html
9
index-true.html → index.html.old
Executable file → Normal file
@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>圆周云境</title>
|
||||
<link rel="icon" href="./logo.png" type="image/png">
|
||||
<link rel="icon" href="./logo-sm.png" type="image/png">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
<script>
|
||||
@ -71,7 +71,7 @@
|
||||
<header class="navbar fixed top-0 left-0 w-full bg-white/90 shadow-md z-40 transition-all duration-300">
|
||||
<div class="container mx-auto px-4 py-3 flex justify-between items-center">
|
||||
<a href="#home" class="flex items-center text-lg font-bold text-primary no-underline hover:no-underline">
|
||||
<img src="./logo.png" alt="Logo" class="w-6 h-6 mr-2">
|
||||
<img src="./logo-sm.png" alt="Logo" class="w-6 h-6 mr-2">
|
||||
<h2 class="text-lg font-semibold">圆周云境</h2>
|
||||
</a>
|
||||
</div>
|
||||
@ -207,14 +207,15 @@
|
||||
<div class="flex flex-col md:flex-row justify-between items-center">
|
||||
<div class="mb-4 md:mb-0">
|
||||
<div class="flex items-center space-x-2">
|
||||
<i class="fa fa-server"></i>
|
||||
<img src="./logo-sm.png" alt="Logo" class="w-6 h-6 mr-2">
|
||||
<span class="font-bold">CCS圆周云境</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm mt-1">背景图片来源<a href="https://bluearchive-cn.com/">《蔚蓝档案》</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6 pt-6 border-t border-gray-700 text-center text-gray-400 text-sm">
|
||||
© 2025 圆周云境 | 版本 2.4.1(Alpha)
|
||||
© 2025 圆周云境 | 版本 2.4.1(ID:1d71f86367)
|
||||
<br />ICP备案/许可证号:<a href="https://beian.miit.gov.cn/" target="_blank">赣ICP备2025065357号</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
BIN
logo-sm.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
411
stop.html
Executable file
@ -0,0 +1,411 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>维护通知</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<link rel="stylesheet" href="https://fonts.loli.net/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Noto Sans SC', sans-serif;
|
||||
background: linear-gradient(135deg, #1a202c 0%, #2d3748 100%);
|
||||
color: #fff;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
.card {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||
overflow: hidden;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.service-icon {
|
||||
position: relative;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 2rem;
|
||||
box-shadow: 0 0 30px rgba(255, 59, 48, 0.5);
|
||||
}
|
||||
.service-icon::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(255, 59, 48, 0.8);
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(0.95);
|
||||
opacity: 0.8;
|
||||
}
|
||||
70% {
|
||||
transform: scale(1.1);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.95);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.slide-up {
|
||||
animation: slideUp 0.8s ease forwards;
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
@keyframes slideUp {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.particle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(135deg, rgba(255,255,255,0.8) 0%, rgba(255,255,255,0.1) 100%);
|
||||
}
|
||||
.shake {
|
||||
animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both;
|
||||
}
|
||||
@keyframes shake {
|
||||
10%, 90% {transform: translateX(-1px);}
|
||||
20%, 80% {transform: translateX(2px);}
|
||||
30%, 50%, 70% {transform: translateX(-4px);}
|
||||
40%, 60% {transform: translateX(4px);}
|
||||
}
|
||||
.animate-pulse {
|
||||
animation: pulse-fade 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
@keyframes pulse-fade {
|
||||
0%, 100% {opacity: 1;}
|
||||
50% {opacity: .5;}
|
||||
}
|
||||
.badge {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 1.5rem;
|
||||
background: rgba(255, 59, 48, 0.2);
|
||||
border: 1px solid rgba(255, 59, 48, 0.5);
|
||||
color: #ff3b30;
|
||||
}
|
||||
.timeline {
|
||||
position: relative;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
.timeline::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 6px;
|
||||
height: calc(100% - 12px);
|
||||
width: 2px;
|
||||
background: rgba(156, 163, 175, 0.5);
|
||||
}
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
.timeline-item:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.timeline-dot {
|
||||
position: absolute;
|
||||
left: -2rem;
|
||||
top: 0.25rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: 50%;
|
||||
background: #4f46e5;
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.3);
|
||||
}
|
||||
.service-status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 9999px;
|
||||
background: rgba(255, 59, 48, 0.2);
|
||||
border: 1px solid rgba(255, 59, 48, 0.5);
|
||||
color: #ff3b30;
|
||||
font-weight: 500;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.status-pulse {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: #ff3b30;
|
||||
border-radius: 50%;
|
||||
margin-right: 0.5rem;
|
||||
position: relative;
|
||||
}
|
||||
.status-pulse::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(255, 59, 48, 0.5);
|
||||
animation: status-ping 1.5s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||
}
|
||||
@keyframes status-ping {
|
||||
75%, 100% {
|
||||
transform: scale(2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.service-feature {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.feature-icon {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 50%;
|
||||
background: rgba(79, 70, 229, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 1rem;
|
||||
flex-shrink: 0;
|
||||
color: #818cf8;
|
||||
}
|
||||
|
||||
/* 音乐控制样式 */
|
||||
.music-control {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
z-index: 1000;
|
||||
background: rgba(255,255,255,0.1);
|
||||
backdrop-filter: blur(5px);
|
||||
border: 1px solid rgba(255,255,255,0.3);
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.music-control:hover {
|
||||
background: rgba(255,255,255,0.2);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.music-control.playing {
|
||||
animation: rotate 2s linear infinite;
|
||||
}
|
||||
.music-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
fill: #fff;
|
||||
}
|
||||
@keyframes rotate {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<audio id="bgMusic" src="./Music.mp3" autoplay loop volume="0.3" preload="auto"></audio>
|
||||
<div class="music-control" id="musicControl" onclick="toggleMusic()">
|
||||
<svg class="music-icon" viewBox="0 0 24 24">
|
||||
<path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="card p-6 md:p-8 slide-up" style="animation-delay: 0.1s;">
|
||||
<div class="text-center">
|
||||
<h1 class="text-3xl md:text-4xl font-bold mb-2 bg-gradient-to-r from-red-400 to-pink-600 bg-clip-text text-transparent">服务正在维护</h1>
|
||||
<p class="text-gray-300 mb-6">请耐心等待恢复</p>
|
||||
|
||||
<div class="service-status-badge">
|
||||
<span class="status-pulse"></span>
|
||||
服务维护中
|
||||
</div>
|
||||
|
||||
<div class="service-icon">
|
||||
<i class="fas fa-power-off text-5xl text-red-500"></i>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-800 bg-opacity-50 rounded-lg p-5 mb-6 max-w-2xl mx-auto">
|
||||
<h2 class="text-xl font-bold mb-3 text-yellow-300">
|
||||
<i class="fas fa-exclamation-triangle mr-2"></i>通知
|
||||
</h2>
|
||||
<p class="text-gray-300 mb-4">
|
||||
我们非常遗憾地通知您,我们已于 <span class="font-semibold text-red-400">2025年06月14日</span> 停止服务。
|
||||
</p>
|
||||
<p class="text-gray-300">
|
||||
请注意,所有账户数据将继续储存在我们的数据库中,<span class="font-semibold text-yellow-300">不会丢失数据</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="timeline mx-auto text-left max-w-lg mb-6">
|
||||
<h3 class="font-bold text-xl mb-4 text-center text-purple-300">服务维护时间线</h3>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-dot"></div>
|
||||
<div class="font-medium text-purple-300">2025-05-30</div>
|
||||
<div class="text-gray-300">提交备案申请完毕</div>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-dot"></div>
|
||||
<div class="font-medium text-purple-300">2025-06-14</div>
|
||||
<div class="text-gray-300">服务停止,运行备案流程</div>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-dot"></div>
|
||||
<div class="font-medium text-purple-300">2025-06-XX</div>
|
||||
<div class="text-gray-300">等待备案下发</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card p-6 md:p-8 mt-6 slide-up" style="animation-delay: 0.3s;">
|
||||
<h2 class="text-xl font-bold mb-4 flex items-center">
|
||||
<i class="fas fa-question-circle mr-2 text-yellow-400"></i>
|
||||
常见问题
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
<div class="p-4 bg-gray-800 bg-opacity-50 rounded-lg">
|
||||
<h3 class="font-medium text-yellow-300 mb-2">为什么维护?</h3>
|
||||
<p class="text-gray-300">国内云产品使用带域名的 HTTP/HTTPS 服务或使用80,443端口时,需确保域名完成备案才能正常使用,备案期间完全无法使用服务,<span class="font-semibold text-red-400">此外,服务器项目源文件因失误遭到损坏,正在迁移备份!</span></p>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-800 bg-opacity-50 rounded-lg">
|
||||
<h3 class="font-medium text-yellow-300 mb-2">为什么之前能访问?</h3>
|
||||
<p class="text-gray-300">之前本工作室采用香港计算节点运行网站服务,现迁移至境内服务器,并注册新域名ccsnetwork.cn用于备案。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="mt-8 text-center text-gray-400 text-sm slide-up" style="animation-delay: 0.6s;">
|
||||
<div class="flex justify-center space-x-4 mb-4">
|
||||
<a href="http://gitea.none.pw/CCSIT/CCSWebsite" class="hover:text-white transition-colors" title="Gitea">
|
||||
<img
|
||||
src="https://cdn.jsdelivr.net/npm/simple-icons@v11/icons/gitea.svg"
|
||||
class="w-5 h-5 inline-block"
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
<p>© 2025 CCSIT圆周云境技术部门 | 版本提交ID:7bccec9a12 | <a href="http://gitea.none.pw/CCSIT/CCSWebsite/commits/branch/main">查看所有版本</a>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 背景音乐控制
|
||||
const audio = document.getElementById('bgMusic');
|
||||
const musicControl = document.getElementById('musicControl');
|
||||
let isPlaying = true;
|
||||
|
||||
function toggleMusic() {
|
||||
if (isPlaying) {
|
||||
audio.pause();
|
||||
musicControl.classList.remove('playing');
|
||||
} else {
|
||||
audio.play().catch(err => {
|
||||
console.log('音乐播放失败,可能需要用户交互后播放:', err);
|
||||
});
|
||||
musicControl.classList.add('playing');
|
||||
}
|
||||
isPlaying = !isPlaying;
|
||||
}
|
||||
|
||||
// 粒子效果
|
||||
function createParticles() {
|
||||
const container = document.body;
|
||||
const particleCount = 50;
|
||||
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
const size = Math.random() * 5 + 1;
|
||||
const particle = document.createElement('div');
|
||||
|
||||
particle.classList.add('particle');
|
||||
particle.style.width = `${size}px`;
|
||||
particle.style.height = `${size}px`;
|
||||
particle.style.opacity = Math.random() * 0.5 + 0.1;
|
||||
|
||||
// 随机位置
|
||||
particle.style.left = `${Math.random() * 100}vw`;
|
||||
particle.style.top = `${Math.random() * 100}vh`;
|
||||
|
||||
// 添加动画
|
||||
particle.style.animation = `moveParticle ${Math.random() * 60 + 30}s linear infinite`;
|
||||
|
||||
// 随机漂浮动画
|
||||
const keyframes = `
|
||||
@keyframes moveParticle {
|
||||
0% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
25% {
|
||||
transform: translate(${Math.random() * 100 - 50}px, ${Math.random() * 100 - 50}px);
|
||||
}
|
||||
50% {
|
||||
transform: translate(${Math.random() * 100 - 50}px, ${Math.random() * 100 - 50}px);
|
||||
}
|
||||
75% {
|
||||
transform: translate(${Math.random() * 100 - 50}px, ${Math.random() * 100 - 50}px);
|
||||
}
|
||||
100% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const styleSheet = document.createElement('style');
|
||||
styleSheet.textContent = keyframes;
|
||||
document.head.appendChild(styleSheet);
|
||||
|
||||
container.appendChild(particle);
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后执行
|
||||
window.addEventListener('load', () => {
|
||||
createParticles();
|
||||
document.addEventListener('click', () => {
|
||||
if (!isPlaying) {
|
||||
audio.play().then(() => {
|
||||
musicControl.classList.add('playing');
|
||||
isPlaying = true;
|
||||
});
|
||||
}
|
||||
}, { once: true });
|
||||
|
||||
// 添加滑动动画
|
||||
const elements = document.querySelectorAll('.slide-up');
|
||||
elements.forEach(element => {
|
||||
element.style.animationPlayState = 'running';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
87
temp.html
Executable file
@ -0,0 +1,87 @@
|
||||
<!-- 活动日历 -->
|
||||
<section id="events" class="min-h-screen flex items-center justify-center relative py-24">
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-wa-dark/30 to-wa-dark/40"></div>
|
||||
<div class="container mx-auto px-4 relative z-10">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-[clamp(2rem,6vw,4rem)] font-bold text-white mb-4 text-shadow">
|
||||
服务器<span class="wa-title">活动日历</span>
|
||||
</h2>
|
||||
<p class="text-white/70 max-w-2xl mx-auto text-lg">
|
||||
查看即将到来的服务器活动和特殊事件,参与赢取丰厚奖励
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="wa-card p-8 max-w-6xl mx-auto">
|
||||
<div class="flex flex-col md:flex-row gap-8">
|
||||
<!-- 日历视图 -->
|
||||
<div class="md:w-3/5">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 class="text-xl font-bold text-white">2025年6月</h3>
|
||||
<div class="flex space-x-2">
|
||||
<button class="w-8 h-8 bg-dark/50 hover:bg-dark text-white rounded-full flex items-center justify-center">
|
||||
<i class="fa fa-chevron-left"></i>
|
||||
</button>
|
||||
<button class="w-8 h-8 bg-dark/50 hover:bg-dark text-white rounded-full flex items-center justify-center">
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-7 gap-2 mb-2">
|
||||
<div class="text-center text-white/60 text-sm">日</div>
|
||||
<div class="text-center text-white/60 text-sm">一</div>
|
||||
<div class="text-center text-white/60 text-sm">二</div>
|
||||
<div class="text-center text-white/60 text-sm">三</div>
|
||||
<div class="text-center text-white/60 text-sm">四</div>
|
||||
<div class="text-center text-white/60 text-sm">五</div>
|
||||
<div class="text-center text-white/60 text-sm">六</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-7 gap-2">
|
||||
<!-- 上个月 -->
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white/40">28</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white/40">29</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white/40">30</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white/40">31</div>
|
||||
|
||||
<!-- 当月 -->
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">1</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">2</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">3</div>
|
||||
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">4</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">5</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">6</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">7</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">8</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">9</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">10</div>
|
||||
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">11</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">12</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">13</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">14</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">15</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">16</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">17</div>
|
||||
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">18</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">19</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">20</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">21</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">22</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">23</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">24</div>
|
||||
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">25</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">26</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">27</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">28</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">29</div>
|
||||
<div class="h-16 bg-dark/30 rounded-lg flex flex-col items-center justify-center text-white hover:bg-dark/50 transition-colors cursor-pointer">30</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|