diff --git a/PREVIEW_FEATURE.md b/PREVIEW_FEATURE.md new file mode 100644 index 0000000..f75c298 --- /dev/null +++ b/PREVIEW_FEATURE.md @@ -0,0 +1,123 @@ +# 文件预览功能说明 + +## 功能概述 + +文件预览功能已更新,现在支持多种文件类型的在线预览,并对不支持预览的文件类型提供友好的提示信息。 + +## 支持的文件类型 + +### 1. 图片文件 + +支持的格式:`jpg`, `jpeg`, `png`, `gif`, `bmp`, `webp`, `svg`, `ico` + +**预览方式**:在模态框中直接显示图片,支持缩放适配。 + +### 2. 视频文件 + +支持的格式:`mp4`, `webm`, `ogg`, `mov`, `avi`, `mkv`, `m4v` + +**预览方式**:使用 HTML5 视频播放器,支持播放控制、音量调节、全屏等功能。 + +### 3. 音频文件 ⭐ 新增 + +支持的格式:`mp3`, `wav`, `ogg`, `m4a`, `aac`, `flac`, `opus`, `weba` + +**预览方式**:显示音乐图标和 HTML5 音频播放器,支持播放控制、进度条、音量调节。 + +### 4. PDF 文件 ⭐ 新增 + +支持的格式:`pdf` + +**预览方式**:使用 iframe 内嵌 PDF 查看器,支持页面导航和缩放(取决于浏览器)。 + +### 5. 文本文件 ⭐ 新增 + +支持的格式: + +- 纯文本:`txt`, `log`, `md` +- 数据格式:`json`, `xml`, `csv`, `yaml`, `yml`, `toml`, `ini`, `conf` +- 代码文件:`js`, `css`, `html`, `py`, `java`, `c`, `cpp`, `h`, `hpp`, `sh`, `bat` + +**预览方式**:以等宽字体显示文本内容,保留格式和换行,支持滚动查看。 + +## 不支持预览的文件 + +对于不在上述类型中的文件(如:zip、exe、docx、xlsx 等),系统会显示一个友好的提示界面,包含: + +- 文件图标 +- "该文件不支持预览" 提示信息 +- 文件名显示 +- **下载文件** 按钮 - 直接下载文件到本地 +- **新窗口打开** 按钮 - 在新标签页中尝试打开文件 + +## 用户体验改进 + +1. **统一的预览界面**:所有支持预览的文件都在同一个模态框中显示 +2. **加载提示**:预览加载时显示 "加载中..." 提示 +3. **错误处理**:加载失败时显示友好的错误信息 +4. **快捷操作**: + - 点击预览窗口外部区域可关闭预览 + - 按 `Esc` 键关闭预览 + - 预览窗口内提供下载和关闭按钮 +5. **响应式设计**:在移动设备上自动调整预览窗口大小 +6. **主题适配**:文本预览支持深色/浅色主题切换 + +## 技术实现 + +### JavaScript 更新 + +- `getFileType(filename)` 函数:扩展了文件类型识别,支持 5 大类文件 +- `openPreview(url, filename)` 函数: + - 根据文件类型动态创建相应的预览元素 + - 为不支持的文件显示操作提示界面 + - 处理加载成功和失败的各种情况 + +### CSS 样式 + +新增样式类: + +- `.preview-audio-wrapper` - 音频预览容器 +- `.preview-audio` - 音频播放器 +- `.preview-text` - 文本内容显示 +- `.preview-unsupported` - 不支持预览的提示界面 + +## 使用示例 + +### 预览音频文件 + +``` +点击任意 .mp3, .wav 等音频文件的 "预览" 按钮 +→ 显示音乐图标和播放控制器 +→ 自动开始播放 +``` + +### 预览文本文件 + +``` +点击 .txt, .json, .py 等文本文件的 "预览" 按钮 +→ 加载并显示文件内容 +→ 使用等宽字体,便于阅读代码 +``` + +### 处理不支持的文件 + +``` +点击 .zip, .exe 等文件的 "预览" 按钮 +→ 显示提示界面 +→ 提供下载或新窗口打开选项 +``` + +## 浏览器兼容性 + +- **现代浏览器**:完全支持(Chrome, Firefox, Safari, Edge) +- **音频格式**:取决于浏览器对各种音频编码的支持 +- **PDF 预览**:取决于浏览器内置 PDF 查看器 +- **视频格式**:建议使用 mp4 (H.264) 以获得最佳兼容性 + +## 未来改进方向 + +- [ ] 添加代码语法高亮 +- [ ] 支持 Office 文档预览(需要第三方服务) +- [ ] 添加图片旋转、缩放控制 +- [ ] 支持预览窗口内文件导航(上一个/下一个) +- [ ] 添加预览历史记录 diff --git a/README.md b/README.md index 9a93280..e5d00cc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Cloud-Index 一个支持多种云存储后端的文件管理、索引和浏览服务。 +更多详细信息请访问 [项目文档](https://docs.cloud-index.rhen.cloud) ## 特性 @@ -70,20 +71,18 @@ cp .env.example .env python app.py ``` +## 部署 + +### Vercel 部署 + +项目包含 `vercel.json` 配置文件,可直接部署到 Vercel: + +1. 在 Vercel 中导入项目 +2. 在 Vercel 项目设置中配置环境变量 +3. 部署 + ## 配置说明 -### 选择存储类型 - -在 `.env` 文件中设置 `STORAGE_TYPE` 来选择存储后端: - -```env -# 使用 Cloudflare R2 -STORAGE_TYPE=r2 - -# 或使用腾讯云 cnb.cool -STORAGE_TYPE=cnbcool -``` - ### Cloudflare R2 配置 ```env @@ -126,7 +125,7 @@ GITHUB_BRANCH=main # GitHub Raw 文件反向代理 URL(可选,用于加速访问) # 常用反向代理: # - https://raw.fastgit.org (推荐,速度快) -# - https://ghproxy.com/https://raw.githubusercontent.com (需要拼接路径) +# - https://ghproxy.com # - https://raw.kgithub.com # 留空则使用官方 raw.githubusercontent.com(国内可能较慢) GITHUB_RAW_PROXY_URL=https://raw.fastgit.org @@ -144,7 +143,7 @@ cloud-index/ │ ├── base.py # 基础存储类(抽象类) │ ├── factory.py # 存储工厂类 │ ├── r2.py # Cloudflare R2 实现 -│ └── cnbcool.py # 腾讯云 cnb.cool 实现 +│ └── github.py # GitHub Repository 实现 ├── templates/ # HTML 模板 │ ├── index.html │ └── footer.html @@ -154,21 +153,6 @@ cloud-index/ └── requirements.txt # Python 依赖 ``` -## 贡献 - -项目采用策略模式和工厂模式,使得添加新的存储后端变得简单: - -1. **BaseStorage** - 定义存储后端的统一接口 -2. **具体实现** (R2Storage, CnbCoolStorage) - 实现具体的存储逻辑 -3. **StorageFactory** - 根据配置创建对应的存储实例 - -### 添加新的存储后端 - -1. 在 `storages/` 目录下创建新的存储实现文件 -2. 继承 `BaseStorage` 并实现所有抽象方法 -3. 在 `StorageFactory` 中添加对应的创建逻辑 -4. 更新 `.env.example` 添加新的配置项 - ## API 路由 - `GET /` - 浏览根目录 @@ -186,16 +170,6 @@ cloud-index/ 详细 API 文档:[API 文档](docs/api.md) -## 部署 - -### Vercel 部署 - -项目包含 `vercel.json` 配置文件,可直接部署到 Vercel: - -1. 在 Vercel 中导入项目 -2. 在 Vercel 项目设置中配置环境变量 -3. 部署 - ### 本地开发 ```bash @@ -265,7 +239,7 @@ A: 当前支持: - Cloudflare R2(推荐) - Amazon S3 -- GitHub Repository(通过 cnb.cool) +- GitHub Repository ### Q: 如何添加新的存储后端? @@ -273,6 +247,18 @@ A: 参考项目结构中的"添加新的存储后端"部分,继承 `BaseStorag ## 贡献指南 +项目采用策略模式和工厂模式,使得添加新的存储后端变得简单: + +1. **BaseStorage** - 定义存储后端的统一接口 +2. **具体实现** (R2Storage, GithubStorage) - 实现具体的存储逻辑 +3. **StorageFactory** - 根据配置创建对应的存储实例 + +### 添加新的存储后端 + +1. 在 `storages/` 目录下创建新的存储实现文件 +2. 继承 `BaseStorage` 并实现所有抽象方法 +3. 在 `StorageFactory` 中添加对应的创建逻辑 +4. 更新 `.env.example` 添加新的配置项 欢迎提交 Issue 和 Pull Request! 1. Fork 项目 diff --git a/static/css/main.css b/static/css/main.css index aaa80e8..07ac27d 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -534,6 +534,74 @@ video.preview-content { background-color: #000; } +/* 音频预览样式 */ +.preview-audio-wrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 40px; + background-color: rgba(0, 0, 0, 0.5); + border-radius: 12px; + min-width: 400px; +} + +.preview-audio { + width: 100%; + max-width: 500px; + margin-top: 20px; +} + +/* 文本预览样式 */ +.preview-text { + background-color: rgba(255, 255, 255, 0.95); + color: #2c3e50; + padding: 20px; + border-radius: 8px; + max-width: 90vw; + max-height: 85vh; + overflow: auto; + font-family: "Courier New", Courier, monospace; + font-size: 14px; + line-height: 1.6; + white-space: pre-wrap; + word-wrap: break-word; +} + +[data-theme="dark"] .preview-text { + background-color: rgba(45, 45, 45, 0.95); + color: #e1e1e1; +} + +/* 不支持预览的文件样式 */ +.preview-unsupported { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 60px 40px; + background-color: rgba(0, 0, 0, 0.7); + border-radius: 12px; + color: white; + text-align: center; + min-width: 400px; +} + +.preview-unsupported p { + margin: 0; +} + +.preview-unsupported .action-link { + background-color: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.3); + color: white; +} + +.preview-unsupported .action-link:hover { + background-color: rgba(255, 255, 255, 0.2); + border-color: rgba(255, 255, 255, 0.5); +} + @media (max-width: 768px) { .preview-container { max-width: 95%; @@ -554,6 +622,21 @@ video.preview-content { font-size: 0.85em; padding: 8px 15px; } + + .preview-audio-wrapper { + min-width: 300px; + padding: 30px 20px; + } + + .preview-unsupported { + min-width: 300px; + padding: 40px 20px; + } + + .preview-text { + font-size: 12px; + padding: 15px; + } } /* 上传进度提示 */ diff --git a/static/js/main.js b/static/js/main.js index 6e4f276..8f65c35 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -843,6 +843,32 @@ const extension = filename.toLowerCase().split(".").pop(); const imageExtensions = ["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg", "ico"]; const videoExtensions = ["mp4", "webm", "ogg", "mov", "avi", "mkv", "m4v"]; + const audioExtensions = ["mp3", "wav", "ogg", "m4a", "aac", "flac", "opus", "weba"]; + const pdfExtensions = ["pdf"]; + const textExtensions = [ + "txt", + "log", + "md", + "json", + "xml", + "csv", + "js", + "css", + "html", + "py", + "java", + "c", + "cpp", + "h", + "hpp", + "sh", + "bat", + "yaml", + "yml", + "toml", + "ini", + "conf", + ]; if (imageExtensions.includes(extension)) { return "image"; @@ -850,7 +876,16 @@ if (videoExtensions.includes(extension)) { return "video"; } - return "unknown"; + if (audioExtensions.includes(extension)) { + return "audio"; + } + if (pdfExtensions.includes(extension)) { + return "pdf"; + } + if (textExtensions.includes(extension)) { + return "text"; + } + return "unsupported"; } function openPreview(url, filename) { @@ -865,8 +900,26 @@ const fileType = getFileType(filename); - if (fileType === "unknown") { - window.open(url, "_blank"); + // 对于不支持预览的文件类型,显示提示信息 + if (fileType === "unsupported") { + container.innerHTML = ` +
+ +

该文件不支持预览

+

文件名: ${filename}

+
+ + +
+
+ `; + info.textContent = filename; + modal.classList.add("show"); + document.body.style.overflow = "hidden"; return; } @@ -905,6 +958,46 @@ video.onerror = () => { container.innerHTML = '
视频加载失败
'; }; + } else if (fileType === "audio") { + const audioWrapper = document.createElement("div"); + audioWrapper.className = "preview-audio-wrapper"; + audioWrapper.innerHTML = ` + + + `; + container.innerHTML = ""; + container.appendChild(audioWrapper); + } else if (fileType === "pdf") { + const iframe = document.createElement("iframe"); + iframe.className = "preview-content"; + iframe.src = url; + iframe.style.width = "100%"; + iframe.style.height = "100%"; + iframe.style.border = "none"; + + container.innerHTML = ""; + container.appendChild(iframe); + } else if (fileType === "text") { + container.innerHTML = '
加载文本内容...
'; + + fetch(url) + .then((response) => { + if (!response.ok) throw new Error("加载失败"); + return response.text(); + }) + .then((text) => { + const pre = document.createElement("pre"); + pre.className = "preview-text"; + pre.textContent = text; + container.innerHTML = ""; + container.appendChild(pre); + }) + .catch((error) => { + container.innerHTML = '
文本加载失败: ' + error.message + "
"; + }); } }, 100); } diff --git a/storages/__init__.py b/storages/__init__.py index daf1893..5530cfb 100644 --- a/storages/__init__.py +++ b/storages/__init__.py @@ -3,4 +3,4 @@ from .factory import StorageFactory from .github import GitHubStorage from .r2 import R2Storage -__all__ = ["BaseStorage", "R2Storage", "CnbCoolStorage", "StorageFactory"] +__all__ = ["BaseStorage", "R2Storage", "GitHubStorage", "StorageFactory"] diff --git a/storages/factory.py b/storages/factory.py index cf96792..b992859 100644 --- a/storages/factory.py +++ b/storages/factory.py @@ -4,8 +4,6 @@ from typing import Optional import dotenv from .base import BaseStorage - -# from .cnbcool import CnbCoolStorage from .github import GitHubStorage from .r2 import R2Storage @@ -39,12 +37,9 @@ class StorageFactory: cls._instance = R2Storage() elif storage_type == "github": cls._instance = GitHubStorage() - # elif storage_type == "cnbcool": - # cls._instance = CnbCoolStorage() else: raise RuntimeError( - f"Unsupported storage type: {storage_type}. " - f"Supported types: r2, github, cnbcool" + f"Unsupported storage type: {storage_type}. Supported types: r2, github" ) return cls._instance