<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>服务器图例管理系统</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
:root {
--primary: #4361ee;
--primary-dark: #3a56d4;
--secondary: #3f37c9;
--accent: #4895ef;
--light: #f8f9fa;
--dark: #212529;
--gray: #6c757d;
--light-gray: #e9ecef;
--success: #4cc9f0;
--warning: #f72585;
--border-radius: 8px;
--shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
--transition: all 0.3s ease;
}
body {
background: linear-gradient(135deg, #f0f4f8 0%, #e2e8f0 100%);
color: var(--dark);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
/* 头部样式 */
header {
background: white;
border-radius: var(--border-radius);
padding: 15px 25px;
margin-bottom: 25px;
box-shadow: var(--shadow);
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 15px;
}
.logo {
display: flex;
align-items: center;
font-size: 1.8rem;
font-weight: bold;
color: var(--primary);
}
.logo i {
margin-right: 15px;
font-size: 2.2rem;
}
.search-container {
flex: 1;
min-width: 300px;
position: relative;
}
.search-container input {
width: 100%;
padding: 12px 15px 12px 45px;
border-radius: 30px;
border: 2px solid var(--light-gray);
background: white;
color: var(--dark);
font-size: 1rem;
transition: var(--transition);
}
.search-container input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.2);
}
.search-container i {
position: absolute;
left: 18px;
top: 50%;
transform: translateY(-50%);
color: var(--gray);
font-size: 1.2rem;
}
.header-controls {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
.btn {
padding: 10px 20px;
border-radius: 30px;
border: none;
background: var(--light);
color: var(--dark);
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: var(--transition);
box-shadow: var(--shadow);
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.1);
}
.btn-primary {
background: var(--primary);
color: white;
}
.btn-primary:hover {
background: var(--primary-dark);
}
/* 目录生成区 */
.directory-section {
background: white;
border-radius: var(--border-radius);
padding: 25px;
margin-bottom: 25px;
box-shadow: var(--shadow);
}
.directory-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 15px;
}
.directory-header h2 {
color: var(--dark);
font-weight: 600;
}
.directory-controls {
display: flex;
gap: 15px;
}
.folder-info {
display: flex;
align-items: center;
gap: 15px;
background: var(--light);
padding: 15px;
border-radius: var(--border-radius);
margin-bottom: 20px;
}
.folder-info i {
color: var(--primary);
font-size: 1.5rem;
}
.folder-details {
flex: 1;
}
.folder-details h3 {
margin-bottom: 5px;
}
.folder-details p {
color: var(--gray);
font-size: 0.9rem;
}
.directory-tree {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 15px;
}
.tree-category {
background: var(--light);
border-radius: var(--border-radius);
padding: 15px;
transition: var(--transition);
cursor: pointer;
}
.tree-category:hover {
background: rgba(67, 97, 238, 0.1);
}
.tree-category h3 {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
color: var(--primary);
}
.tree-category h3 i {
font-size: 1.2rem;
}
.tree-items {
padding-left: 25px;
}
.tree-item {
padding: 8px 0;
display: flex;
align-items: center;
gap: 8px;
color: var(--gray);
}
.tree-item i {
font-size: 0.9rem;
color: var(--accent);
}
/* 图例展示区 */
.legends-section {
background: white;
border-radius: var(--border-radius);
padding: 25px;
box-shadow: var(--shadow);
}
.legends-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 15px;
}
.view-controls {
display: flex;
gap: 12px;
}
.view-btn {
width: 40px;
height: 40px;
border-radius: 50%;
border: 1px solid var(--light-gray);
background: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: var(--transition);
}
.view-btn:hover, .view-btn.active {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.legend-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 25px;
}
.legend-card {
background: white;
border-radius: var(--border-radius);
overflow: hidden;
box-shadow: var(--shadow);
transition: var(--transition);
cursor: pointer;
display: flex;
flex-direction: column;
border: 1px solid var(--light-gray);
}
.legend-card:hover {
transform: translateY(-8px);
box-shadow: 0 12px 20px rgba(0, 0, 0, 0.15);
border-color: var(--accent);
}
.card-img {
height: 180px;
background: var(--light-gray);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
}
.card-img img {
width: 100%;
height: 100%;
object-fit: cover;
transition: var(--transition);
}
.legend-card:hover .card-img img {
transform: scale(1.05);
}
.zoom-btn {
position: absolute;
bottom: 12px;
right: 12px;
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.9);
color: var(--dark);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: var(--transition);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.zoom-btn:hover {
background: var(--primary);
color: white;
transform: scale(1.1);
}
.card-content {
padding: 18px;
flex: 1;
display: flex;
flex-direction: column;
}
.card-content h4 {
margin-bottom: 8px;
font-weight: 600;
color: var(--dark);
}
.card-content p {
font-size: 0.9rem;
color: var(--gray);
line-height: 1.5;
flex: 1;
}
.file-info {
display: flex;
justify-content: space-between;
margin-top: 12px;
font-size: 0.8rem;
color: var(--gray);
}
/* 响应式设计 */
@media (max-width: 992px) {
header {
flex-direction: column;
gap: 20px;
}
.search-container {
min-width: 100%;
margin: 0;
}
.header-controls {
width: 100%;
justify-content: center;
}
.directory-tree {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
}
@media (max-width: 768px) {
.legend-grid {
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
}
.directory-tree {
grid-template-columns: 1fr;
}
}
@media (max-width: 480px) {
.legend-grid {
grid-template-columns: 1fr;
}
.header-controls {
flex-direction: column;
}
.btn {
width: 100%;
justify-content: center;
}
.directory-controls {
flex-direction: column;
}
}
/* 大图模态框 */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal-content {
max-width: 90%;
max-height: 90%;
position: relative;
}
.modal-content img {
max-width: 100%;
max-height: 80vh;
border: 3px solid white;
border-radius: 8px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
.close-modal {
position: absolute;
top: 20px;
right: 30px;
color: white;
font-size: 40px;
cursor: pointer;
transition: var(--transition);
}
.close-modal:hover {
color: var(--accent);
}
.image-info {
color: white;
text-align: center;
margin-top: 15px;
font-size: 1.2rem;
}
/* 加载动画 */
.loader {
display: flex;
justify-content: center;
padding: 40px 0;
}
.loader .spinner {
width: 50px;
height: 50px;
border: 5px solid var(--light-gray);
border-top: 5px solid var(--primary);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 40px 0;
color: var(--gray);
grid-column: 1 / -1;
}
.empty-state i {
font-size: 3rem;
margin-bottom: 20px;
opacity: 0.5;
}
.notification {
position: fixed;
bottom: 20px;
right: 20px;
background: var(--primary);
color: white;
padding: 15px 25px;
border-radius: 8px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
z-index: 1000;
animation: fadeInOut 3s ease-in-out;
display: none;
}
@keyframes fadeInOut {
0% { opacity: 0; transform: translateY(20px); }
10% { opacity: 1; transform: translateY(0); }
90% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(20px); }
}
</style>
</head>
<body>
<div class="container">
<!-- 头部区域 -->
<header>
<div class="logo">
<i class="fas fa-map-marked-alt"></i>
<span>服务器图例管理系统</span>
</div>
<div class="search-container">
<i class="fas fa-search"></i>
<input type="text" id="searchInput" placeholder="搜索图例...">
</div>
<div class="header-controls">
<button class="btn" id="refreshBtn">
<i class="fas fa-sync-alt"></i> 刷新图例
</button>
<button class="btn btn-primary" id="generateCatalogBtn">
<i class="fas fa-folder-plus"></i> 生成目录
</button>
</div>
</header>
<!-- 目录生成区 -->
<section class="directory-section">
<div class="directory-header">
<h2><i class="fas fa-folder-tree"></i> 服务器图例目录</h2>
<div class="directory-controls">
<div class="btn" id="expandAllBtn">
<i class="fas fa-expand"></i> 展开所有
</div>
<div class="btn" id="collapseAllBtn">
<i class="fas fa-compress"></i> 折叠所有
</div>
</div>
</div>
<div class="folder-info">
<i class="fas fa-folder-open"></i>
<div class="folder-details">
<h3>服务器图片目录</h3>
<p>路径: <span id="folderPath">./img</span> | 最后更新: <span id="lastUpdate">正在获取...</span></p>
</div>
</div>
<div class="directory-tree" id="directoryTree">
<div class="loader">
<div class="spinner"></div>
</div>
</div>
</section>
<!-- 图例展示区 -->
<section class="legends-section">
<div class="legends-header">
<h2><i class="fas fa-images"></i> 图例展示 (<span id="imageCount">0</span> 个图例)</h2>
<div class="view-controls">
<div class="view-btn active" title="网格视图">
<i class="fas fa-th"></i>
</div>
<div class="view-btn" title="列表视图">
<i class="fas fa-list"></i>
</div>
<div class="view-btn" title="放大">
<i class="fas fa-search-plus"></i>
</div>
<div class="view-btn" title="缩小">
<i class="fas fa-search-minus"></i>
</div>
</div>
</div>
<div class="legend-grid" id="legendGrid">
<div class="loader">
<div class="spinner"></div>
</div>
</div>
</section>
</div>
<!-- 大图模态框 -->
<div class="modal" id="imageModal">
<span class="close-modal" id="closeModal">×</span>
<div class="modal-content">
<img id="modalImage" src="" alt="大图预览">
<div class="image-info" id="imageInfo"></div>
</div>
</div>
<!-- 通知区域 -->
<div class="notification" id="notification"></div>
<script>
// 获取服务器图片的函数
async function getServerImages() {
try {
// 显示加载状态
document.getElementById('legendGrid').innerHTML = `
<div class="loader">
<div class="spinner"></div>
</div>
`;
// 调用后端API获取图片数据
const response = await fetch('get_images.php');
if (!response.ok) {
throw new Error('获取图片数据失败');
}
const data = await response.json();
// 更新最后更新时间
document.getElementById('lastUpdate').textContent = data.last_updated;
// 更新图片数量
document.getElementById('imageCount').textContent = data.images.length;
// 渲染图片到页面
renderImages(data.images);
// 生成目录树
generateDirectoryTree(data.categories);
// 显示文件夹路径
document.getElementById('folderPath').textContent = data.folder_path;
} catch (error) {
console.error('获取图片数据时出错:', error);
document.getElementById('legendGrid').innerHTML = `
<div class="empty-state">
<i class="fas fa-exclamation-triangle"></i>
<h3>无法加载图片</h3>
<p>${error.message}</p>
<button class="btn" id="retryBtn" style="margin-top: 15px;">
<i class="fas fa-redo"></i> 重试
</button>
</div>
`;
document.getElementById('retryBtn').addEventListener('click', getServerImages);
}
}
// 渲染图片到页面
function renderImages(images) {
const grid = document.getElementById('legendGrid');
if (images.length === 0) {
grid.innerHTML = `
<div class="empty-state">
<i class="fas fa-folder-open"></i>
<h3>没有找到图片</h3>
<p>请检查服务器上的图片文件夹</p>
</div>
`;
return;
}
grid.innerHTML = '';
images.forEach(image => {
const card = document.createElement('div');
card.className = 'legend-card';
card.innerHTML = `
<div class="card-img">
<img src="${image.path}" alt="${image.name}">
<div class="zoom-btn" data-image="${image.path}" data-name="${image.name}">
<i class="fas fa-search-plus"></i>
</div>
</div>
<div class="card-content">
<h4>${image.name}</h4>
<p>${image.description}</p>
<div class="file-info">
<span>${image.size}</span>
<span>${image.date}</span>
</div>
</div>
`;
grid.appendChild(card);
});
// 绑定放大按钮事件
document.querySelectorAll('.zoom-btn').forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
const imageUrl = this.getAttribute('data-image');
const imageName = this.getAttribute('data-name');
showImageModal(imageUrl, imageName);
});
});
}
// 生成目录树
function generateDirectoryTree(categories) {
const treeContainer = document.getElementById('directoryTree');
if (!categories || categories.length === 0) {
treeContainer.innerHTML = `
<div class="empty-state">
<i class="fas fa-exclamation-circle"></i>
<h3>没有目录数据</h3>
<p>点击"生成目录"按钮创建目录</p>
</div>
`;
return;
}
treeContainer.innerHTML = '';
categories.forEach(category => {
const categoryElement = document.createElement('div');
categoryElement.className = 'tree-category';
categoryElement.innerHTML = `
<h3><i class="${category.icon}"></i> ${category.name} (${category.count})</h3>
<div class="tree-items">
${category.items.map(item => `
<div class="tree-item">
<i class="fas fa-file-image"></i> ${item}
</div>
`).join('')}
</div>
`;
treeContainer.appendChild(categoryElement);
});
}
// 显示大图模态框
function showImageModal(imageUrl, imageName) {
const modal = document.getElementById('imageModal');
const modalImg = document.getElementById('modalImage');
const imageInfo = document.getElementById('imageInfo');
modalImg.src = imageUrl;
modalImg.alt = imageName;
imageInfo.textContent = imageName;
modal.style.display = 'flex';
}
// 显示通知
function showNotification(message) {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.style.display = 'block';
setTimeout(() => {
notification.style.display = 'none';
}, 3000);
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
// 加载服务器图片
getServerImages();
// 刷新按钮事件
document.getElementById('refreshBtn').addEventListener('click', getServerImages);
// 生成目录按钮事件
document.getElementById('generateCatalogBtn').addEventListener('click', function() {
// 在实际应用中,这里会调用后端生成目录的API
showNotification('目录已重新生成!');
// 模拟重新加载数据
setTimeout(() => {
getServerImages();
}, 1000);
});
// 关闭模态框
document.getElementById('closeModal').addEventListener('click', function() {
document.getElementById('imageModal').style.display = 'none';
});
// 搜索功能
document.getElementById('searchInput').addEventListener('input', function(e) {
const searchTerm = e.target.value.toLowerCase();
filterLegends(searchTerm);
});
// 展开所有目录
document.getElementById('expandAllBtn').addEventListener('click', function() {
// 实际应用中应展开所有目录项
showNotification('已展开所有目录');
});
// 折叠所有目录
document.getElementById('collapseAllBtn').addEventListener('click', function() {
// 实际应用中应折叠所有目录项
showNotification('已折叠所有目录');
});
});
// 过滤图例
function filterLegends(searchTerm) {
const cards = document.querySelectorAll('.legend-card');
let visibleCount = 0;
cards.forEach(card => {
const title = card.querySelector('h4').textContent.toLowerCase();
const description = card.querySelector('p').textContent.toLowerCase();
if (title.includes(searchTerm) || description.includes(searchTerm)) {
card.style.display = '';
visibleCount++;
} else {
card.style.display = 'none';
}
});
// 更新计数
document.getElementById('imageCount').textContent = visibleCount;
}
</script>
</body>
</html>
<?php
// 设置响应头为JSON格式
header('Content-Type: application/json');
// 图片文件夹路径(相对于此PHP文件的路径)
$imgFolder = './img';
// 获取最后更新时间
$lastUpdated = date("Y-m-d H:i:s", filemtime($imgFolder));
// 获取文件夹路径
$folderPath = realpath($imgFolder);
// 扫描文件夹获取图片文件
$imageFiles = [];
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if (is_dir($imgFolder)) {
$files = scandir($imgFolder);
foreach ($files as $file) {
if ($file != '.' && $file != '..') {
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (in_array($extension, $allowedExtensions)) {
$imageFiles[] = $file;
}
}
}
}
// 构建图片数据
$images = [];
foreach ($imageFiles as $index => $file) {
$filePath = $imgFolder . '/' . $file;
$fileSize = filesize($filePath);
$fileDate = date("Y-m-d", filemtime($filePath));
// 生成友好的文件大小显示
$sizeFormatted = formatFileSize($fileSize);
// 获取不带扩展名的文件名
$fileName = pathinfo($file, PATHINFO_FILENAME);
$images[] = [
'id' => $index + 1,
'name' => $fileName,
'path' => $filePath,
'description' => getDescription($fileName),
'size' => $sizeFormatted,
'date' => $fileDate,
'category' => getCategory($fileName)
];
}
// 生成目录结构
$categories = [
[
'name' => '地形地貌',
'icon' => 'fas fa-mountain',
'count' => count(array_filter($images, function($img) {
return strpos($img['category'], '地形地貌') !== false;
})),
'items' => ['地形等高线', '地貌分类', '高程图']
],
[
'name' => '交通网络',
'icon' => 'fas fa-road',
'count' => count(array_filter($images, function($img) {
return strpos($img['category'], '交通网络') !== false;
})),
'items' => ['主要公路', '铁路网络', '机场位置']
],
[
'name' => '自然地理',
'icon' => 'fas fa-water',
'count' => count(array_filter($images, function($img) {
return strpos($img['category'], '自然地理') !== false;
})),
'items' => ['水系分布', '地质构造', '土壤类型']
],
[
'name' => '植被生态',
'icon' => 'fas fa-tree',
'count' => count(array_filter($images, function($img) {
return strpos($img['category'], '植被生态') !== false;
})),
'items' => ['森林覆盖', '草地分布', '植被类型']
],
[
'name' => '人文地理',
'icon' => 'fas fa-city',
'count' => count(array_filter($images, function($img) {
return strpos($img['category'], '人文地理') !== false;
})),
'items' => ['城市区域', '工业区域', '居民点分布']
]
];
// 构建响应数据
$response = [
'status' => 'success',
'folder_path' => $folderPath,
'last_updated' => $lastUpdated,
'image_count' => count($images),
'images' => $images,
'categories' => $categories
];
// 输出JSON响应
echo json_encode($response);
// 辅助函数:格式化文件大小
function formatFileSize($bytes) {
if ($bytes >= 1048576) {
return round($bytes / 1048576, 1) . ' MB';
} elseif ($bytes >= 1024) {
return round($bytes / 1024, 1) . ' KB';
} else {
return $bytes . ' bytes';
}
}
// 辅助函数:根据文件名生成描述
function getDescription($fileName) {
$descriptions = [
'地形等高线' => '用于表示地形高度的等高线系统,间隔20米',
'主要公路' => '国家级和省级主要公路交通网络',
'水系分布' => '河流、湖泊、水库等水系分布图例',
'森林覆盖' => '森林、灌木林、草地的植被覆盖分类',
'城市区域' => '城市建成区、开发区、居民点分布',
'工业区域' => '工厂、矿山、工业园区等分布',
'降雨量分布' => '年平均降雨量分布图例(毫米)',
'农业用地' => '农田、果园、养殖区等农业用地分类'
];
return $descriptions[$fileName] ?? $fileName . ' 图例,用于地图制作和地理信息系统分析';
}
// 辅助函数:根据文件名分配类别
function getCategory($fileName) {
$categories = [
'地形等高线' => '地形地貌',
'地貌分类' => '地形地貌',
'主要公路' => '交通网络',
'铁路网络' => '交通网络',
'水系分布' => '自然地理',
'森林覆盖' => '植被生态',
'城市区域' => '人文地理',
'工业区域' => '人文地理',
'降雨量分布' => '气候气象',
'农业用地' => '土地利用'
];
return $categories[$fileName] ?? '其他';
}
?>
评论