mirror of
https://github.com/RhenCloud/Cloud-Index.git
synced 2025-12-06 15:26:10 +08:00
358 lines
15 KiB
HTML
358 lines
15 KiB
HTML
{% extends 'base.html' %} {% block title %}Cloud Index{% endblock %} {% block body_attrs %}data-current-prefix="{{
|
|
current_prefix }}"{% endblock %} {% block content %}
|
|
<div class="container">
|
|
<h1>
|
|
Cloud Index
|
|
<div>
|
|
<button
|
|
class="view-toggle"
|
|
id="createFolderButton"
|
|
aria-label="新建文件夹"
|
|
onclick="promptCreateFolder()"
|
|
title="新建文件夹"
|
|
>
|
|
<i class="fas fa-folder-plus"></i>
|
|
</button>
|
|
<button
|
|
class="view-toggle"
|
|
id="uploadButton"
|
|
aria-label="上传文件"
|
|
onclick="document.getElementById('fileInput').click()"
|
|
title="上传文件"
|
|
>
|
|
<i class="fas fa-upload"></i>
|
|
</button>
|
|
<button
|
|
class="view-toggle"
|
|
id="deleteTrigger"
|
|
style="color: #dc3545"
|
|
aria-label="删除文件"
|
|
onclick="deleteSelectedEntries()"
|
|
title="删除文件"
|
|
>
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
<button class="theme-toggle" id="themeToggle" aria-label="切换深色模式" title="切换深色/浅色模式">
|
|
<i class="fas fa-moon"></i>
|
|
</button>
|
|
<button class="view-toggle" id="viewToggle" aria-label="切换视图" title="切换列表/网格视图">
|
|
<i class="fas fa-th-list"></i>
|
|
</button>
|
|
</div>
|
|
</h1>
|
|
|
|
<div class="breadcrumb">
|
|
<a href="/">Home</a>
|
|
{% if crumbs %} / {% for c in crumbs %}
|
|
<a href="/{{ c.prefix.rstrip('/') }}">{{ c.name }}</a>{% if not loop.last %} / {% endif %} {% endfor %} {% endif
|
|
%}
|
|
</div>
|
|
|
|
<div id="uploadStatus" class="upload-status"></div>
|
|
|
|
<input type="file" id="fileInput" class="upload-input" multiple onchange="uploadFiles(this.files)" />
|
|
|
|
{% if entries %}
|
|
<table class="files-table">
|
|
<thead>
|
|
<tr>
|
|
<th class="checkbox-col">
|
|
<input type="checkbox" id="selectAll" aria-label="全选" />
|
|
</th>
|
|
<th class="file-name-col">名称</th>
|
|
<th class="file-size-col">大小</th>
|
|
<th class="last-modified-col">最后修改时间</th>
|
|
<th class="actions-col">操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for entry in entries %}
|
|
<tr>
|
|
<td class="checkbox-col">
|
|
<input
|
|
type="checkbox"
|
|
class="entry-checkbox"
|
|
value="{{ entry.key }}"
|
|
data-type="{{ 'dir' if entry.is_dir else 'file' }}"
|
|
aria-label="选择 {{ entry.name }}"
|
|
/>
|
|
</td>
|
|
<td
|
|
class="file-name-col"
|
|
onclick="{% if entry.is_dir %}window.location.href='/{{ entry.key.rstrip('/') }}'{% else %}window.open('{{ entry.file_url }}', '_blank'){% endif %}"
|
|
style="cursor: pointer"
|
|
>
|
|
{% if entry.is_dir %}
|
|
<i class="file-icon folder fas fa-folder"></i>
|
|
<a href="/{{ entry.key.rstrip('/') }}" onclick="event.stopPropagation();">{{ entry.name }}</a>
|
|
{% else %}
|
|
<i class="file-icon file {{ entry.name|fileicon }}"></i>
|
|
<a href="{{ entry.file_url }}" target="_blank" onclick="event.stopPropagation();"
|
|
>{{ entry.name }}</a
|
|
>
|
|
{% endif %}
|
|
</td>
|
|
<td class="file-size-col file-size">
|
|
{% if not entry.is_dir %} {{ entry.size|filesizeformat }} {% else %} - {% endif %}
|
|
</td>
|
|
<td class="last-modified-col last-modified">
|
|
{% if not entry.is_dir %} {{ entry.last_modified }} {% else %} - {% endif %}
|
|
</td>
|
|
<td class="actions-col">
|
|
{% if entry.is_dir %}
|
|
<button
|
|
class="action-link rename-btn"
|
|
onclick="promptRename('{{ entry.key }}', '{{ entry.name }}', true)"
|
|
>
|
|
<i class="fas fa-edit"></i><span class="action-text"> 重命名</span>
|
|
</button>
|
|
<button class="action-link delete-btn" onclick="deleteFolder('{{ entry.key }}')">
|
|
<i class="fas fa-trash"></i><span class="action-text"> 删除</span>
|
|
</button>
|
|
<button class="action-link copy-btn" onclick="promptCopyOrMove('{{ entry.key }}', true, 'copy')">
|
|
<i class="fas fa-copy"></i><span class="action-text"> 复制</span>
|
|
</button>
|
|
<button class="action-link move-btn" onclick="promptCopyOrMove('{{ entry.key }}', true, 'move')">
|
|
<i class="fas fa-arrows-alt-h"></i><span class="action-text"> 移动</span>
|
|
</button>
|
|
{% else %}
|
|
<button class="action-link" onclick="openPreview('{{ entry.file_url }}', '{{ entry.name }}')">
|
|
<i class="fas fa-eye"></i><span class="action-text"> 预览</span>
|
|
</button>
|
|
<button
|
|
class="action-link download-btn"
|
|
data-download-key="{{ entry.key }}"
|
|
data-download-name="{{ entry.name }}"
|
|
>
|
|
<i class="fas fa-download"></i><span class="action-text"> 下载</span>
|
|
</button>
|
|
<button class="action-link delete-btn" onclick="deleteFile('{{ entry.key }}')">
|
|
<i class="fas fa-trash"></i><span class="action-text"> 删除</span>
|
|
</button>
|
|
<button
|
|
class="action-link rename-btn"
|
|
onclick="promptRename('{{ entry.key }}', '{{ entry.name }}')"
|
|
>
|
|
<i class="fas fa-edit"></i><span class="action-text"> 重命名</span>
|
|
</button>
|
|
<button class="action-link copy-btn" onclick="promptCopyOrMove('{{ entry.key }}', false, 'copy')">
|
|
<i class="fas fa-copy"></i><span class="action-text"> 复制</span>
|
|
</button>
|
|
<button class="action-link move-btn" onclick="promptCopyOrMove('{{ entry.key }}', false, 'move')">
|
|
<i class="fas fa-arrows-alt-h"></i><span class="action-text"> 移动</span>
|
|
</button>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="grid-container" id="gridContainer">
|
|
{% for entry in entries %}
|
|
<div class="grid-card" data-key="{{ entry.key }}" data-type="{{ 'dir' if entry.is_dir else 'file' }}">
|
|
<div class="grid-checkbox">
|
|
<input
|
|
type="checkbox"
|
|
class="entry-checkbox"
|
|
value="{{ entry.key }}"
|
|
data-type="{{ 'dir' if entry.is_dir else 'file' }}"
|
|
aria-label="选择 {{ entry.name }}"
|
|
/>
|
|
</div>
|
|
{% if entry.is_dir %}
|
|
<div class="grid-icon" onclick="window.location.href='/{{ entry.key.rstrip('/') }}'">
|
|
<i class="fas fa-folder" style="color: var(--folder-color)"></i>
|
|
</div>
|
|
<a class="grid-name" href="/{{ entry.key.rstrip('/') }}">{{ entry.name }}</a>
|
|
<div class="grid-actions">
|
|
<button
|
|
class="grid-action-btn rename"
|
|
onclick="promptRename('{{ entry.key }}', '{{ entry.name }}', true)"
|
|
title="重命名"
|
|
>
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button class="grid-action-btn delete" onclick="deleteFolder('{{ entry.key }}')" title="删除">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
<button
|
|
class="grid-action-btn copy"
|
|
onclick="promptCopyOrMove('{{ entry.key }}', true, 'copy')"
|
|
title="复制"
|
|
>
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
<button
|
|
class="grid-action-btn move"
|
|
onclick="promptCopyOrMove('{{ entry.key }}', true, 'move')"
|
|
title="移动"
|
|
>
|
|
<i class="fas fa-arrows-alt-h"></i>
|
|
</button>
|
|
</div>
|
|
{% else %} {% if entry.name|fileicon == 'fas fa-image' %}
|
|
<div class="grid-thumb" onclick="openPreview('{{ entry.file_url }}', '{{ entry.name }}')">
|
|
<img
|
|
style="width: 100%; height: 100%; object-fit: cover; border-radius: 6px"
|
|
src="/thumb/{{ entry.key }}"
|
|
loading="lazy"
|
|
decoding="async"
|
|
fetchpriority="low"
|
|
alt="{{ entry.name }}"
|
|
/>
|
|
</div>
|
|
{% else %}
|
|
<div class="grid-icon" onclick="openPreview('{{ entry.file_url }}', '{{ entry.name }}')">
|
|
<i class="{{ entry.name|fileicon }}" style="color: var(--file-color)"></i>
|
|
</div>
|
|
{% endif %}
|
|
<a
|
|
class="grid-name"
|
|
href="javascript:void(0)"
|
|
onclick="openPreview('{{ entry.file_url }}', '{{ entry.name }}')"
|
|
title="{{ entry.name }}"
|
|
>
|
|
{{ entry.name }}
|
|
</a>
|
|
<div class="file-size">{{ entry.size|filesizeformat }}</div>
|
|
<div class="grid-actions">
|
|
<button
|
|
class="grid-action-btn preview"
|
|
onclick="openPreview('{{ entry.file_url }}', '{{ entry.name }}')"
|
|
title="预览"
|
|
>
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
<button
|
|
class="grid-action-btn download"
|
|
data-download-key="{{ entry.key }}"
|
|
data-download-name="{{ entry.name }}"
|
|
title="下载"
|
|
>
|
|
<i class="fas fa-download"></i>
|
|
</button>
|
|
<button class="grid-action-btn delete" onclick="deleteFile('{{ entry.key }}')" title="删除">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
<button
|
|
class="grid-action-btn rename"
|
|
onclick="promptRename('{{ entry.key }}', '{{ entry.name }}')"
|
|
title="重命名"
|
|
>
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button
|
|
class="grid-action-btn copy"
|
|
onclick="promptCopyOrMove('{{ entry.key }}', false, 'copy')"
|
|
title="复制"
|
|
>
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
<button
|
|
class="grid-action-btn move"
|
|
onclick="promptCopyOrMove('{{ entry.key }}', false, 'move')"
|
|
title="移动"
|
|
>
|
|
<i class="fas fa-arrows-alt-h"></i>
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="empty-message">
|
|
<p>源存储为空或未找到任何文件</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% include 'footer.html' %}
|
|
|
|
<div id="previewModal" class="preview-modal">
|
|
<div class="preview-controls">
|
|
<button class="preview-btn" onclick="downloadPreview()" title="下载">
|
|
<i class="fas fa-download"></i>
|
|
</button>
|
|
<button class="preview-btn" onclick="closePreview()" title="关闭">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<div class="preview-container">
|
|
<div id="previewContainer"></div>
|
|
<div id="previewInfo" class="preview-info"></div>
|
|
</div>
|
|
</div>
|
|
{% endblock %} {% block scripts %}
|
|
<script>
|
|
// 客户端分页:初始只渲染部分条目,点击“加载更多”逐步显示,减少首屏 DOM 与渲染压力
|
|
(function () {
|
|
const PAGE_CHUNK = 200; // 每次显示的条目数(表格与网格分别按该步长展开)
|
|
function hideBeyond(nodeList) {
|
|
let hidden = 0;
|
|
for (let i = PAGE_CHUNK; i < nodeList.length; i++) {
|
|
const el = nodeList[i];
|
|
if (!el.hasAttribute("hidden")) {
|
|
el.setAttribute("hidden", "");
|
|
hidden++;
|
|
}
|
|
}
|
|
return hidden;
|
|
}
|
|
function revealNext(nodeList, count) {
|
|
let revealed = 0;
|
|
for (let i = 0; i < nodeList.length && revealed < count; i++) {
|
|
const el = nodeList[i];
|
|
if (el.hasAttribute("hidden")) {
|
|
el.removeAttribute("hidden");
|
|
revealed++;
|
|
}
|
|
}
|
|
return revealed;
|
|
}
|
|
|
|
function setupLoadMore() {
|
|
const tableRows = document.querySelectorAll("table.files-table tbody tr");
|
|
const gridCards = document.querySelectorAll("#gridContainer .grid-card");
|
|
let hiddenCount = 0;
|
|
hiddenCount += hideBeyond(tableRows);
|
|
hiddenCount += hideBeyond(gridCards);
|
|
|
|
if (hiddenCount === 0) return;
|
|
|
|
// 创建“加载更多”按钮
|
|
const moreWrap = document.createElement("div");
|
|
moreWrap.style.display = "flex";
|
|
moreWrap.style.justifyContent = "center";
|
|
moreWrap.style.margin = "16px 0 24px";
|
|
|
|
const btn = document.createElement("button");
|
|
btn.className = "view-toggle";
|
|
btn.type = "button";
|
|
const info = document.createElement("span");
|
|
info.style.marginLeft = "8px";
|
|
info.textContent = hiddenCount;
|
|
btn.textContent = "加载更多";
|
|
btn.appendChild(info);
|
|
moreWrap.appendChild(btn);
|
|
|
|
const container = document.querySelector(".container");
|
|
container && container.appendChild(moreWrap);
|
|
|
|
btn.addEventListener("click", () => {
|
|
const shown = revealNext(tableRows, PAGE_CHUNK) + revealNext(gridCards, PAGE_CHUNK);
|
|
hiddenCount = Math.max(0, hiddenCount - shown);
|
|
if (hiddenCount <= 0) {
|
|
moreWrap.remove();
|
|
} else {
|
|
info.textContent = hiddenCount;
|
|
}
|
|
});
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", setupLoadMore);
|
|
})();
|
|
</script>
|
|
{% endblock %}
|