You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
star/httpserver/template.html

479 lines
16 KiB
HTML

3 months ago
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<title>B612 Http Server {{ .IdxTitle }}</title>
<style>
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
body {
background-color: #f5f5f5;
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
-moz-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
height: 100vh;
position: relative; /* 为了使背景图片固定 */
}
.background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
{{ .Photo }} /* 这里的背景图片 URL 是动态插入的 */
background-size: cover;
opacity: 0.5; /* 调整透明度 */
}
@media screen and (max-width: 768px) {
.background {
{{ .MobilePhoto }}
background-size: cover;
}
}
.container {
-webkit-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
flex: 1;
width: 50%;
max-width: 1920px;
margin: 0 auto;
padding: 24px;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
-moz-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
background: rgba(245, 245, 245, 0.5); /* 添加一个半透明背景层 */
}
h1 {
text-align: center;
margin-bottom: 24px;
}
.table-container {
-webkit-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
flex: 1;
overflow-y: auto;
overflow-x: hidden;
position: relative;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 24px;
table-layout: fixed; /* 确保表格单元格宽度固定 */
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
white-space: normal; /* 允许换行 */
background: rgba(245, 245, 245, 0.5); /* 设置表格单元格背景为半透明 */
}
th[data-sort]:after {
content: "▲";
display: inline-block;
height: 20px;
width: 20px;
margin-left: 10px;
vertical-align: middle;
opacity: 0.3;
-webkit-transition: all 0.2s ease-in-out;
-moz-transition: all 0.2s ease-in-out;
-ms-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
}
th.asc:after {
content: "▲";
opacity: 1;
}
th.desc:after {
content: "▼";
opacity: 1;
}
th:hover:after {
opacity: 1;
}
.filename {
color: #007bff;
text-decoration: underline;
display: block;
max-width: 600px; /* 调整你想要的最大宽度 */
word-wrap: break-word; /* 确保文件名能够换行 */
}
.filename:hover {
color: #0056b3;
}
tr:hover {
background-color: rgba(241, 241, 241, 0.5); /* 设置鼠标悬停效果的背景为半透明 */
}
.filetype {
text-transform: uppercase;
}
@media screen and (max-width: 600px) {
table {
font-size: 14px;
}
}
@media (orientation: portrait) and (max-width: 1024px) {
.container {
width: 60%;
}
}
@media (max-width: 400px) {
.container {
width: 80%;
}
}
thead th {
position: -webkit-sticky;
position: -moz-sticky;
position: sticky;
top: 0;
z-index: 1;
background: rgba(245, 245, 245, 0.5); /* 设置表头背景为半透明 */
}
tbody td:first-child,
thead th:first-child {
position: -webkit-sticky;
position: -moz-sticky;
position: sticky;
left: 0;
z-index: 1;
background: rgba(245, 245, 245, 0.5); /* 设置第一列背景为半透明 */
}
hr {
width: 100%;
}
.search-container {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-justify-content: center;
-moz-justify-content: center;
-ms-justify-content: center;
justify-content: center;
margin-bottom: 20px;
}
.search-container input {
padding: 10px;
width: 80%;
max-width: 400px;
font-size: 16px;
border: 1px solid #ddd;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="background"></div>
<div class="container">
<h1>B612 Http Server - {{ .Version }}</h1>
<hr />
<h2>{{ .Idx }}</h2>
{{ .Upload }}
<div class="search-container">
<input type="text" id="search-box" placeholder="Search for a file..." />
</div>
<div class="table-container" id="table-container">
<table>
<thead>
<tr>
<th data-sort="name">Name</th>
<th data-sort="modified">Modified</th>
<th data-sort="size">Size</th>
<th data-sort="type">Type</th>
</tr>
</thead>
<tbody id="table-content"><!-- table content is dynamically filled by JavaScript -->
</tbody>
</table>
</div>
<hr />
<h2 style="text-align: center;">B612.Me © Apache 2.0 License</h2>
</div>
<!-- Context menu for copying file details -->
<div id="context-menu" style="display:none; position: absolute; z-index: 1000; background: white; border: 1px solid #ccc; padding: 5px;">
<ul style="list-style: none; margin: 0; padding: 0;">
<li id="copy-filename" style="padding: 5px; cursor: pointer;">复制文件名</li>
<li id="copy-link" style="padding: 5px; cursor: pointer;">复制文件链接地址</li>
<li id="copy-size-bytes" style="padding: 5px; cursor: pointer;">复制文件大小(按字节)</li>
<li id="copy-size-display" style="padding: 5px; cursor: pointer;">复制文件大小(按显示)</li>
</ul>
</div>
<script>
// 初始化内容
var dataRows = {{ .Data }};
function loadTableContent(dataRows) {
var tableContent = document.getElementById('table-content');
if (!tableContent) return;
var html = '';
dataRows.forEach(function(row) {
html += '<tr>';
html += '<td><a class="filename" href="' + row.attr + '">' + row.name + '</a></td>';
html += '<td>' + row.modified + '</td>';
html += '<td title="' + row.size + ' bytes">' + formatSize(row.size) + '</td>';
html += '<td class="filetype">' + row.type + '</td>';
html += '</tr>';
});
tableContent.innerHTML = html;
}
function renderRows(rows) {
var tableContent = document.getElementById('table-content');
var fragment = document.createDocumentFragment();
rows.forEach(function(row) {
var tr = document.createElement('tr');
tr.innerHTML += '<td><a class="filename" href="' + row.attr + '">' + row.name + '</a></td>';
tr.innerHTML += '<td>' + row.modified + '</td>';
var formattedSize = formatSize(row.size);
tr.innerHTML += '<td title="' + row.size + ' bytes">' + formattedSize + '</td>';
tr.innerHTML += '<td class="filetype">' + row.type + '</td>';
fragment.appendChild(tr);
});
tableContent.innerHTML = ''; // 清空现有内容
tableContent.appendChild(fragment);
}
function chunkedRenderRows() {
var chunkSize = 50;
var currentIndex = 0;
function renderChunk() {
var fragment = document.createDocumentFragment();
for (var i = currentIndex; i < currentIndex + chunkSize && i < dataRows.length; i++) {
var row = dataRows[i];
if (row.name === '..' && i !== 0) continue;
var tr = document.createElement('tr');
tr.innerHTML += '<td><a class="filename" href="' + row.attr + '">' + row.name + '</a></td>';
tr.innerHTML += '<td>' + row.modified + '</td>';
var formattedSize = formatSize(row.size);
tr.innerHTML += '<td title="' + row.size + ' bytes">' + formattedSize + '</td>';
tr.innerHTML += '<td class="filetype">' + row.type + '</td>';
fragment.appendChild(tr);
}
document.getElementById('table-content').appendChild(fragment);
currentIndex += chunkSize;
if (currentIndex < dataRows.length) {
requestAnimationFrame(renderChunk);
}
}
// 清空现有内容
document.getElementById('table-content').innerHTML = '';
// 开始分块渲染
requestAnimationFrame(renderChunk);
}
function parseSize(size) {
var units = { 'KB': 1024, 'MB': 1024 * 1024, 'GB': 1024 * 1024 * 1024 };
var match = size.match(/(\d+\.?\d*)\s*(KB|MB|GB)/i);
if (match) {
return parseFloat(match[1]) * (units[match[2].toUpperCase()] || 1);
}
return parseInt(size, 10);
}
function formatSize(size) {
if (size < 0) return "-";
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 sortTable(th, n, initial) {
var direction = th.classList.contains('asc') && !initial ? 'desc' : 'asc';
dataRows.sort(function(a, b) {
// 检查 'name' 字段以确保 '..' 始终在第一位
if (a.name === '..') return -1;
if (b.name === '..') return 1;
var x = Object.values(a)[n];
var y = Object.values(b)[n];
if (n === 1) { // modified column
// 解析日期字符串
x = new Date(a.modified);
y = new Date(b.modified);
} else if (n === 2) { // size column
x = a.size;
y = b.size;
}
return direction === 'asc' ?
(x < y ? -1 : x > y ? 1 : 0) :
(x > y ? -1 : x < y ? 1 : 0);
});
th.classList.toggle('asc', direction === 'asc' && !initial);
th.classList.toggle('desc', direction === 'desc' && !initial);
updateSortIcons(th);
renderRows(dataRows);
}
function updateSortIcons(th) {
var ths = document.querySelectorAll('thead th[data-sort]');
ths.forEach(function(header) {
if (header !== th) {
header.classList.remove('asc');
header.classList.remove('desc');
}
});
}
// 初次加载时按 Name 列进行升序排序
function initialSort() {
var nameHeader = document.querySelector('th[data-sort="name"]');
if (nameHeader) {
nameHeader.classList.add('asc'); // 直接设置为升序状态
sortTable(nameHeader, 0, true); // 传递一个参数表示这是初始排序
}
}
function isIE() {
return window.navigator.userAgent.indexOf("MSIE ") > -1 || navigator.userAgent.indexOf("Trident/") > -1;
}
if (!isIE()) {
document.addEventListener('DOMContentLoaded', function(event) {
var ths = document.querySelectorAll('thead th[data-sort]');
ths.forEach(function(th, i) {
th.addEventListener('click', function() {
sortTable(th, i);
});
});
// 使用 chunkedRenderRows 进行初始渲染, 然后进行默认排序
renderRows(dataRows);
// 初次加载时按 Name 列进行升序排序
initialSort();
// 初始化右键菜单
initializeContextMenu();
});
// 处理搜索框输入事件
document.getElementById('search-box').addEventListener('input', function (e) {
var searchText = e.target.value.toLowerCase();
var filteredRows = dataRows.filter(function (row) {
return row.name.toLowerCase().includes(searchText);
});
renderRows(filteredRows);
});
}else{
loadTableContent(dataRows);
// 禁用搜索框
var searchBox = document.getElementById('search-box');
if (searchBox) {
searchBox.disabled = true;
searchBox.placeholder = "IE浏览器不受支持大部分功能受限";
}
}
function initializeContextMenu() {
var contextMenu = document.getElementById('context-menu');
document.addEventListener('contextmenu', function(e) {
if (e.target.classList.contains('filetype')) {
e.preventDefault();
var row = e.target.closest('tr');
var fileNameToCopy = row.querySelector('.filename').textContent;
var linkToCopy = row.querySelector('.filename').href;
var byteSizeToCopy = row.querySelector('td[title]') ? row.querySelector('td[title]').getAttribute('title') : '';
var displaySizeToCopy = row.querySelector('td[title]') ? row.querySelector('td[title]').textContent : '';
contextMenu.style.display = 'block';
contextMenu.style.top = e.pageY + 'px';
contextMenu.style.left = e.pageX + 'px';
contextMenu.setAttribute('data-filename', fileNameToCopy);
contextMenu.setAttribute('data-link', linkToCopy);
contextMenu.setAttribute('data-size-bytes', byteSizeToCopy);
contextMenu.setAttribute('data-size-display', displaySizeToCopy);
} else {
contextMenu.style.display = 'none';
}
});
document.addEventListener('click', function() {
contextMenu.style.display = 'none';
});
document.getElementById('copy-filename').addEventListener('click', function() {
var fileName = contextMenu.getAttribute('data-filename');
if (fileName) {
copyToClipboard(fileName);
}
contextMenu.style.display = 'none';
});
document.getElementById('copy-link').addEventListener('click', function() {
var fileLink = contextMenu.getAttribute('data-link');
if (fileLink) {
copyToClipboard(fileLink);
}
contextMenu.style.display = 'none';
});
document.getElementById('copy-size-bytes').addEventListener('click', function() {
var fileSizeBytes = contextMenu.getAttribute('data-size-bytes');
if (fileSizeBytes) {
copyToClipboard(fileSizeBytes);
}
contextMenu.style.display = 'none';
});
document.getElementById('copy-size-display').addEventListener('click', function() {
var fileSizeDisplay = contextMenu.getAttribute('data-size-display');
if (fileSizeDisplay) {
copyToClipboard(fileSizeDisplay);
}
contextMenu.style.display = 'none';
});
}
function copyToClipboard(text) {
var textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
</script>
</body>
</html>