Initial commit: DVS Cloud Drive v1.0.0

This commit is contained in:
dvs
2026-08-28 10:22:37 +08:00
commit ff2c63c0ce
18 changed files with 4343 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
# Python
__pycache__/
*.py[cod]
*.egg-info/
dist/
build/
.venv/
venv/
env/
# Database & uploads
*.db
*.sqlite3
uploads/
user_data/
data/
# System files
Thumbs.db
.DS_Store
desktop.ini
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 DVS (dvs-dvsxt)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+108
View File
@@ -0,0 +1,108 @@
# ☁️ DVS Cloud Drive — Web File Management Platform
> A full-featured **web-based file management (cloud drive)** platform built with Flask. Upload, download, preview, share, search, and manage your files — with robust security.
**DVS Cloud Drive** is a self-hosted web file manager. It provides user authentication, file upload/download, folder management, batch operations, in-browser preview (PDF / audio / video), file search, and shareable links — all protected by security controls that block access to system-critical directories.
---
## ✨ Features
| Feature | Description |
|---------|-------------|
| 👤 **User System** | Register / login / logout / change password (hashed with `werkzeug`) |
| 📤 **File Upload** | Upload files with secure filename handling |
| 📁 **Folder Management** | Create / rename / delete folders & files |
| 📥 **Download** | Single & batch download (ZIP archive) |
| 👁️ **In-Browser Preview** | PDF / video / audio preview |
| 🔍 **Search** | Search files across the drive |
| 🔗 **File Sharing** | Generate shareable links (`/s/<share_id>`) |
| 🗑️ **Batch Delete** | Delete multiple items at once |
| 📊 **Storage Usage** | View storage usage via API |
| 🛡️ **Security** | Forbidden system directories (Linux & Windows) blocked |
---
## 🔌 Routes
| Route | Method | Description |
|-------|--------|-------------|
| `/` | GET | Dashboard / file list |
| `/login` | GET/POST | User login |
| `/register` | GET/POST | User registration |
| `/change_password` | GET/POST | Change password |
| `/logout` | GET | Log out |
| `/upload` | POST | Upload files |
| `/create_folder` | POST | Create a folder |
| `/rename` | POST | Rename item |
| `/delete` | POST | Delete item |
| `/download/<path>` | GET | Download file/folder |
| `/preview/<path>` | GET | Preview file (PDF/video/audio) |
| `/search` | GET | Search files |
| `/batch_download` | POST | Batch download as ZIP |
| `/batch_delete` | POST | Batch delete |
| `/share` | POST | Create share link |
| `/unshare` | POST | Remove share |
| `/shared_files` | GET | List shared files |
| `/s/<share_id>` | GET | Access a shared file |
| `/api/storage_usage` | GET | Storage usage (JSON) |
---
## 🚀 Quick Start
### Prerequisites
- Python 3.8+
- `flask`, `flask-cors`, `werkzeug`
### Install & Run
```bash
# Install dependencies
pip install flask flask-cors
# Run the platform
python app.py
```
Then open `http://localhost:5000` in your browser.
### Default Flow
1. **Register** an account
2. **Login**
3. Upload / create folders / share / preview files from the dashboard
---
## 🛡️ Security
- Passwords hashed with `werkzeug.security` (PBKDF2)
- Secure filename normalization via `werkzeug.utils.secure_filename`
- **Forbidden directories** blocked (path traversal protection):
- Linux: `/root`, `/etc`, `/usr`, `/var`, `/proc`, `/sys`, etc.
- Windows: `C:\Windows`, `C:\Program Files`, System32, etc.
- Session-based authentication & CSRF protection
---
## 📁 Project Structure
```
DVS-Cloud-Drive/
├── app.py # Main Flask application
├── templates/ # HTML templates (Jinja2)
│ ├── base.html # Layout
│ ├── index.html # Dashboard / file list
│ ├── login.html / register.html / change_password.html
│ ├── preview_pdf.html / preview_video.html / preview_audio.html
│ ├── search_results.html / shared_files.html
│ ├── 404.html / 500.html
├── favicon.ico # Site icon
└── README.md # This document
```
---
## 📄 License
This project is licensed under the **MIT License**. See [LICENSE](LICENSE) for details.
+1282
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

+16
View File
@@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block content %}
<div class="max-w-3xl mx-auto mt-12 text-center">
<div class="mb-6 text-gray-400">
<i class="fa fa-exclamation-triangle text-9xl"></i>
</div>
<h1 class="text-4xl font-bold text-gray-900 mb-2">404 - 页面未找到</h1>
<p class="text-lg text-gray-600 mb-6">您要访问的页面不存在或已被移动。</p>
<div class="mt-8">
<a href="{{ url_for('index') }}" class="inline-flex items-center px-5 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-primary hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
<i class="fa fa-home mr-2"></i> 返回主页
</a>
</div>
</div>
{% endblock %}
+16
View File
@@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block content %}
<div class="max-w-3xl mx-auto mt-12 text-center">
<div class="mb-6 text-gray-400">
<i class="fa fa-server text-9xl"></i>
</div>
<h1 class="text-4xl font-bold text-gray-900 mb-2">500 - 服务器内部错误</h1>
<p class="text-lg text-gray-600 mb-6">发生了一些意外情况,请稍后再试。</p>
<div class="mt-8">
<a href="{{ url_for('index') }}" class="inline-flex items-center px-5 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-primary hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
<i class="fa fa-home mr-2"></i> 返回主页
</a>
</div>
</div>
{% endblock %}
+742
View File
@@ -0,0 +1,742 @@
<!DOCTYPE html>␍
<html lang="zh">␍
<head>␍
<meta charset="UTF-8">␍
<meta name="viewport" content="width=device-width, initial-scale=1.0">␍
<title>{% block title %}DVS云盘{% endblock %}</title>␍
␍
<!-- 核心样式:基于 css.css -->␍
<link rel="stylesheet" href="{{ url_for('static', filename='css.css') }}">␍
␍
<!-- Tailwind CSS (辅助) -->␍
<script src="https://cdn.tailwindcss.com"></script>␍
<script>␍
tailwind.config = {␍
corePlugins: {␍
preflight: false, // 避免与css.css冲突␍
}␍
}␍
</script>␍
␍
<!-- Font Awesome -->␍
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">␍
␍
<!-- Bootstrap Icons (补充) -->␍
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">␍
␍
<!-- 额外样式覆盖 -->␍
<style>␍
/* 确保css.css变量生效 */␍
:root {␍
--repo-color-primary: #3b82f6;␍
--repo-color-primary-hover: #2563eb;␍
--repo-color-primary-active: #1d4ed8;␍
--repo-color-danger: #ef4444;␍
--repo-color-success: #10b981;␍
--repo-color-warning: #f59e0b;␍
--repo-color-info: #3b82f6;␍
--repo-color-text: #1f2937;␍
--repo-color-text-secondary: #4b5563;␍
--repo-color-text-tertiary: #9ca3af;␍
--repo-color-bg: #ffffff;␍
--repo-color-bg-secondary: #f9fafb;␍
--repo-color-bg-tertiary: #f3f4f6;␍
--repo-color-border: #e5e7eb;␍
--repo-color-border-hover: #d1d5db;␍
--repo-header-height: 60px;␍
--repo-sidebar-width: 260px;␍
--repo-spacing-1: 4px;␍
--repo-spacing-2: 8px;␍
--repo-spacing-3: 12px;␍
--repo-spacing-4: 16px;␍
--repo-spacing-5: 20px;␍
--repo-spacing-6: 24px;␍
--repo-spacing-8: 32px;␍
--repo-radius-sm: 4px;␍
--repo-radius-md: 6px;␍
--repo-radius-lg: 8px;␍
--repo-radius-xl: 12px;␍
--repo-radius-full: 9999px;␍
}␍
␍
/* 全局重置 */␍
* {␍
margin: 0;␍
padding: 0;␍
box-sizing: border-box;␍
}␍
␍
body {␍
font-family: var(--repo-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif);␍
font-size: var(--repo-font-size-base, 14px);␍
line-height: var(--repo-line-height-base, 1.5);␍
color: var(--repo-color-text);␍
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);␍
min-height: 100vh;␍
}␍
␍
/* Mac风格导航栏 - 基于css.css变量 */␍
.mac-nav {␍
background: rgba(255, 255, 255, 0.92);␍
backdrop-filter: blur(12px);␍
-webkit-backdrop-filter: blur(12px);␍
border-bottom: 1px solid var(--repo-color-border);␍
box-shadow: var(--repo-shadow-sm, 0 1px 3px rgba(0,0,0,0.05));␍
padding: 0;␍
position: sticky;␍
top: 0;␍
z-index: var(--repo-z-sticky, 100);␍
}␍
␍
.mac-nav .container {␍
max-width: 1400px;␍
margin: 0 auto;␍
padding: 0 var(--repo-spacing-6);␍
height: var(--repo-header-height);␍
display: flex;␍
align-items: center;␍
justify-content: space-between;␍
}␍
␍
.mac-nav-brand {␍
font-weight: 700;␍
font-size: 20px;␍
color: var(--repo-color-text);␍
display: flex;␍
align-items: center;␍
gap: 10px;␍
text-decoration: none;␍
}␍
␍
.mac-nav-brand i {␍
font-size: 24px;␍
color: var(--repo-color-primary);␍
}␍
␍
.mac-nav-link {␍
color: var(--repo-color-text-secondary);␍
font-weight: 500;␍
padding: 8px 16px;␍
border-radius: var(--repo-radius-md);␍
transition: all 0.2s ease;␍
margin: 0 2px;␍
display: inline-flex;␍
align-items: center;␍
gap: 8px;␍
text-decoration: none;␍
}␍
␍
.mac-nav-link:hover {␍
background: rgba(59, 130, 246, 0.08);␍
color: var(--repo-color-primary);␍
}␍
␍
.mac-nav-link.active {␍
background: var(--repo-color-primary);␍
color: white;␍
}␍
␍
.mac-nav-link.active i {␍
color: white;␍
}␍
␍
/* 按钮样式 - 基于css.css */␍
.mac-btn {␍
display: inline-flex;␍
align-items: center;␍
justify-content: center;␍
gap: 8px;␍
background: var(--repo-color-primary);␍
color: white;␍
border: none;␍
border-radius: var(--repo-radius-full);␍
padding: 10px 24px;␍
font-weight: 500;␍
font-size: 14px;␍
cursor: pointer;␍
transition: all 0.2s ease;␍
box-shadow: var(--repo-shadow-xs);␍
text-decoration: none;␍
}␍
␍
.mac-btn:hover {␍
background: var(--repo-color-primary-hover);␍
transform: translateY(-1px);␍
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);␍
}␍
␍
.mac-btn-sm {␍
padding: 6px 16px;␍
font-size: 13px;␍
}␍
␍
.mac-btn-outline {␍
background: transparent;␍
color: var(--repo-color-primary);␍
border: 1px solid var(--repo-color-border);␍
box-shadow: none;␍
}␍
␍
.mac-btn-outline:hover {␍
background: rgba(59, 130, 246, 0.08);␍
border-color: var(--repo-color-primary);␍
}␍
␍
.mac-btn-secondary {␍
background: var(--repo-color-text-secondary);␍
}␍
␍
.mac-btn-secondary:hover {␍
background: #475569;␍
}␍
␍
.mac-btn-success {␍
background: var(--repo-color-success);␍
}␍
␍
.mac-btn-danger {␍
background: var(--repo-color-danger);␍
}␍
␍
/* 卡片样式 */␍
.mac-card {␍
background: var(--repo-color-bg);␍
border-radius: var(--repo-radius-xl);␍
border: 1px solid var(--repo-color-border);␍
box-shadow: var(--repo-shadow-sm);␍
overflow: hidden;␍
transition: all 0.2s ease;␍
}␍
␍
.mac-card:hover {␍
box-shadow: var(--repo-shadow-lg);␍
transform: translateY(-2px);␍
}␍
␍
.mac-card-header {␍
background: var(--repo-color-bg-secondary);␍
padding: 20px;␍
border-bottom: 1px solid var(--repo-color-border);␍
font-weight: 600;␍
color: var(--repo-color-text);␍
display: flex;␍
align-items: center;␍
justify-content: space-between;␍
}␍
␍
/* 文件列表项 */␍
.mac-file-item {␍
background: var(--repo-color-bg);␍
border: 1px solid var(--repo-color-border);␍
border-radius: var(--repo-radius-lg);␍
padding: 16px;␍
margin-bottom: 8px;␍
display: flex;␍
align-items: center;␍
gap: 16px;␍
transition: all 0.2s ease;␍
}␍
␍
.mac-file-item:hover {␍
background: var(--repo-color-bg-hover, #f8fafc);␍
border-color: var(--repo-color-border-hover);␍
transform: translateX(4px);␍
}␍
␍
.mac-file-icon {␍
width: 48px;␍
height: 48px;␍
border-radius: var(--repo-radius-lg);␍
display: flex;␍
align-items: center;␍
justify-content: center;␍
color: white;␍
font-size: 20px;␍
}␍
␍
.mac-file-icon.folder {␍
background: linear-gradient(135deg, #60a5fa, #3b82f6);␍
}␍
␍
.mac-file-icon.image {␍
background: linear-gradient(135deg, #38bdf8, #0ea5e9);␍
}␍
␍
.mac-file-icon.video {␍
background: linear-gradient(135deg, #8b5cf6, #7c3aed);␍
}␍
␍
.mac-file-icon.audio {␍
background: linear-gradient(135deg, #10b981, #059669);␍
}␍
␍
.mac-file-icon.pdf {␍
background: linear-gradient(135deg, #f87171, #ef4444);␍
}␍
␍
.mac-file-icon.document {␍
background: linear-gradient(135deg, #94a3b8, #64748b);␍
}␍
␍
/* 面包屑 */␍
.mac-breadcrumb {␍
display: flex;␍
align-items: center;␍
gap: 8px;␍
background: var(--repo-color-bg);␍
border: 1px solid var(--repo-color-border);␍
border-radius: var(--repo-radius-lg);␍
padding: 12px 20px;␍
margin-bottom: 24px;␍
}␍
␍
.mac-breadcrumb-item {␍
color: var(--repo-color-text-secondary);␍
font-size: 14px;␍
}␍
␍
.mac-breadcrumb-item a {␍
color: var(--repo-color-primary);␍
text-decoration: none;␍
font-weight: 500;␍
}␍
␍
/* 输入框 */␍
.mac-input {␍
background: var(--repo-color-bg);␍
border: 1px solid var(--repo-color-border);␍
border-radius: var(--repo-radius-lg);␍
padding: 12px 16px;␍
font-size: 14px;␍
color: var(--repo-color-text);␍
transition: all 0.2s ease;␍
outline: none;␍
}␍
␍
.mac-input:focus {␍
border-color: var(--repo-color-primary);␍
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);␍
}␍
␍
/* 徽章 */␍
.mac-badge {␍
display: inline-flex;␍
align-items: center;␍
gap: 4px;␍
background: var(--repo-color-bg-tertiary);␍
color: var(--repo-color-text-secondary);␍
padding: 4px 12px;␍
border-radius: var(--repo-radius-full);␍
font-size: 12px;␍
font-weight: 500;␍
}␍
␍
.mac-badge-primary {␍
background: rgba(59, 130, 246, 0.1);␍
color: var(--repo-color-primary);␍
}␍
␍
.mac-badge-success {␍
background: rgba(16, 185, 129, 0.1);␍
color: var(--repo-color-success);␍
}␍
␍
.mac-badge-warning {␍
background: rgba(245, 158, 11, 0.1);␍
color: var(--repo-color-warning);␍
}␍
␍
.mac-badge-danger {␍
background: rgba(239, 68, 68, 0.1);␍
color: var(--repo-color-danger);␍
}␍
␍
/* 警告框 */␍
.mac-alert {␍
background: var(--repo-color-bg);␍
border: 1px solid;␍
border-radius: var(--repo-radius-lg);␍
padding: 16px;␍
margin: 16px 0;␍
display: flex;␍
align-items: center;␍
gap: 12px;␍
}␍
␍
.mac-alert-success {␍
border-color: var(--repo-color-success);␍
background: rgba(16, 185, 129, 0.05);␍
}␍
␍
.mac-alert-warning {␍
border-color: var(--repo-color-warning);␍
background: rgba(245, 158, 11, 0.05);␍
}␍
␍
.mac-alert-danger {␍
border-color: var(--repo-color-danger);␍
background: rgba(239, 68, 68, 0.05);␍
}␍
␍
.mac-alert-info {␍
border-color: var(--repo-color-primary);␍
background: rgba(59, 130, 246, 0.05);␍
}␍
␍
/* 动作按钮组 */␍
.mac-action-buttons {␍
display: flex;␍
gap: 8px;␍
}␍
␍
.mac-action-btn {␍
width: 36px;␍
height: 36px;␍
display: flex;␍
align-items: center;␍
justify-content: center;␍
background: var(--repo-color-bg);␍
border: 1px solid var(--repo-color-border);␍
border-radius: var(--repo-radius-md);␍
color: var(--repo-color-text-secondary);␍
cursor: pointer;␍
transition: all 0.2s ease;␍
}␍
␍
.mac-action-btn:hover {␍
background: var(--repo-color-bg-hover);␍
border-color: var(--repo-color-border-hover);␍
color: var(--repo-color-primary);␍
}␍
␍
/* 下拉菜单 */␍
.mac-dropdown {␍
background: var(--repo-color-bg);␍
border: 1px solid var(--repo-color-border);␍
border-radius: var(--repo-radius-lg);␍
box-shadow: var(--repo-shadow-lg);␍
padding: 8px;␍
min-width: 200px;␍
}␍
␍
.mac-dropdown-item {␍
padding: 10px 16px;␍
border-radius: var(--repo-radius-md);␍
color: var(--repo-color-text);␍
cursor: pointer;␍
transition: all 0.2s ease;␍
display: flex;␍
align-items: center;␍
gap: 12px;␍
text-decoration: none;␍
}␍
␍
.mac-dropdown-item:hover {␍
background: var(--repo-color-bg-hover);␍
color: var(--repo-color-primary);␍
}␍
␍
/* 表格 */␍
.mac-table {␍
background: var(--repo-color-bg);␍
border: 1px solid var(--repo-color-border);␍
border-radius: var(--repo-radius-lg);␍
overflow: hidden;␍
width: 100%;␍
}␍
␍
.mac-table th {␍
background: var(--repo-color-bg-secondary);␍
padding: 16px;␍
text-align: left;␍
font-weight: 600;␍
color: var(--repo-color-text);␍
border-bottom: 1px solid var(--repo-color-border);␍
}␍
␍
.mac-table td {␍
padding: 16px;␍
border-bottom: 1px solid var(--repo-color-border-light, #f1f5f9);␍
color: var(--repo-color-text-secondary);␍
}␍
␍
.mac-table tr:hover td {␍
background: var(--repo-color-bg-hover);␍
}␍
␍
/* 浮动按钮 */␍
.mac-float-btn {␍
position: fixed;␍
bottom: 24px;␍
right: 24px;␍
width: 56px;␍
height: 56px;␍
border-radius: 50%;␍
background: var(--repo-color-primary);␍
color: white;␍
display: flex;␍
align-items: center;␍
justify-content: center;␍
box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3);␍
cursor: pointer;␍
transition: all 0.2s ease;␍
z-index: 100;␍
border: none;␍
}␍
␍
.mac-float-btn:hover {␍
background: var(--repo-color-primary-hover);␍
transform: translateY(-2px) scale(1.05);␍
}␍
␍
/* 复选框 */␍
.mac-checkbox {␍
width: 18px;␍
height: 18px;␍
border-radius: var(--repo-radius-sm);␍
border: 2px solid var(--repo-color-border);␍
cursor: pointer;␍
}␍
␍
/* 模态框/弹窗 - 保持原有逻辑不变 */␍
.mac-dialog-overlay {␍
position: fixed;␍
top: 0;␍
left: 0;␍
right: 0;␍
bottom: 0;␍
background-color: rgba(0, 0, 0, 0.5);␍
z-index: 1040;␍
}␍
␍
.mac-dialog {␍
position: fixed;␍
top: 50%;␍
left: 50%;␍
transform: translate(-50%, -50%);␍
background: var(--repo-color-bg);␍
border-radius: var(--repo-radius-xl);␍
box-shadow: var(--repo-shadow-xl);␍
z-index: 1050;␍
min-width: 400px;␍
max-width: 500px;␍
width: 90%;␍
}␍
␍
/* 弹窗样式 - 保持原有 */␍
#uploadPopup, #createFolderPopup, #sharePopup, #renamePopup, #deletePopup {␍
display: none !important;␍
}␍
␍
#uploadPopup:not(.hidden), #createFolderPopup:not(.hidden), ␍
#sharePopup:not(.hidden), #renamePopup:not(.hidden), ␍
#deletePopup:not(.hidden) {␍
display: flex !important;␍
}␍
␍
.hidden {␍
display: none !important;␍
}␍
␍
/* 滚动条 */␍
::-webkit-scrollbar {␍
width: 8px;␍
height: 8px;␍
}␍
␍
::-webkit-scrollbar-track {␍
background: var(--repo-color-bg-tertiary);␍
border-radius: var(--repo-radius-full);␍
}␍
␍
::-webkit-scrollbar-thumb {␍
background: var(--repo-color-border);␍
border-radius: var(--repo-radius-full);␍
}␍
␍
::-webkit-scrollbar-thumb:hover {␍
background: var(--repo-color-border-hover);␍
}␍
␍
/* 多彩图标增强 */␍
.text-primary { color: var(--repo-color-primary); }␍
.text-success { color: var(--repo-color-success); }␍
.text-danger { color: var(--repo-color-danger); }␍
.text-warning { color: var(--repo-color-warning); }␍
.text-info { color: var(--repo-color-info); }␍
␍
/* 响应式 */␍
@media (max-width: 768px) {␍
.mac-nav .container {␍
padding: 0 var(--repo-spacing-4);␍
}␍
.mac-nav-link span:not(.hidden-on-mobile) {␍
display: none;␍
}␍
.mac-nav-link {␍
padding: 8px 12px;␍
}␍
.mac-dialog {␍
min-width: 90%;␍
width: 90%;␍
}␍
}␍
</style>␍
␍
{% block extra_css %}{% endblock %}␍
</head>␍
<body>␍
<!-- 导航栏 -->␍
<nav class="mac-nav">␍
<div class="container">␍
<div class="flex items-center space-x-8">␍
<a href="{{ url_for('index') }}" class="mac-nav-brand">␍
<i class="fas fa-cloud text-2xl"></i>␍
<span>DVS云盘</span>␍
</a>␍
␍
<div class="flex items-center space-x-1">␍
<a href="{{ url_for('index') }}" ␍
class="mac-nav-link {{ 'active' if request.endpoint == 'index' }}">␍
<i class="fas fa-home"></i>␍
<span>主页</span>␍
</a>␍
␍
<a href="{{ url_for('shared_files') }}" ␍
class="mac-nav-link {{ 'active' if request.endpoint == 'shared_files' }}">␍
<i class="fas fa-link"></i>␍
<span>已分享</span>␍
</a>␍
</div>␍
</div>␍
␍
<div class="flex items-center">␍
{% if session.get('username') %}␍
<div class="relative ml-4">␍
<button class="mac-nav-link flex items-center" id="userMenuButton">␍
<i class="fas fa-user-circle text-xl mr-2"></i>␍
<span>{{ session.username }}</span>␍
<i class="fas fa-chevron-down ml-2 text-xs"></i>␍
</button>␍
␍
<div class="mac-dropdown absolute right-0 mt-2 hidden z-50" id="userMenuDropdown">␍
<a href="{{ url_for('change_password') }}" class="mac-dropdown-item">␍
<i class="fas fa-key"></i>␍
<span>修改密码</span>␍
</a>␍
<div class="border-t border-gray-100 my-1"></div>␍
<a href="{{ url_for('logout') }}" class="mac-dropdown-item">␍
<i class="fas fa-sign-out-alt"></i>␍
<span>退出登录</span>␍
</a>␍
</div>␍
</div>␍
{% else %}␍
<a href="{{ url_for('login') }}" class="mac-btn mac-btn-sm ml-4">␍
<i class="fas fa-sign-in-alt"></i>␍
<span>登录</span>␍
</a>␍
{% endif %}␍
</div>␍
</div>␍
</nav>␍
␍
<!-- 主内容区域 -->␍
<main class="container mx-auto px-4 py-8">␍
{% block content %}{% endblock %}␍
</main>␍
␍
<!-- 浮动操作按钮 -->␍
<button class="mac-float-btn mac-tooltip" data-tooltip="快速操作">␍
<i class="fas fa-plus"></i>␍
</button>␍
␍
<!-- JavaScript -->␍
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>␍
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>␍
␍
<script>␍
// 下拉菜单切换␍
const userMenuBtn = document.getElementById('userMenuButton');␍
const userMenuDropdown = document.getElementById('userMenuDropdown');␍
if (userMenuBtn && userMenuDropdown) {␍
userMenuBtn.addEventListener('click', function(e) {␍
e.stopPropagation();␍
userMenuDropdown.classList.toggle('hidden');␍
});␍
}␍
␍
// 点击外部关闭下拉菜单␍
document.addEventListener('click', function() {␍
if (userMenuDropdown) userMenuDropdown.classList.add('hidden');␍
});␍
␍
// 全局函数声明␍
window.showUploadPopup = function() {␍
const popup = document.getElementById('uploadPopup');␍
if (popup) {␍
popup.classList.remove('hidden');␍
popup.style.cssText = 'display:flex!important;position:fixed!important;inset:0!important;background-color:rgba(0,0,0,0.5)!important;align-items:center!important;justify-content:center!important;z-index:9999!important;';␍
}␍
};␍
␍
window.showCreateFolderPopup = function() {␍
const popup = document.getElementById('createFolderPopup');␍
if (popup) {␍
popup.classList.remove('hidden');␍
popup.style.cssText = 'display:flex!important;position:fixed!important;inset:0!important;background-color:rgba(0,0,0,0.5)!important;align-items:center!important;justify-content:center!important;z-index:9999!important;';␍
}␍
};␍
␍
window.closePopup = function(id) {␍
const el = document.getElementById(id);␍
if (el) {␍
el.classList.add('hidden');␍
el.style.cssText = '';␍
}␍
};␍
␍
// 工具提示␍
document.querySelectorAll('.mac-tooltip').forEach(element => {␍
element.addEventListener('mouseenter', function() {␍
const tooltip = this.getAttribute('data-tooltip');␍
if (tooltip) {␍
const tooltipEl = document.createElement('div');␍
tooltipEl.className = 'fixed bg-gray-900 text-white text-xs px-2 py-1 rounded shadow-lg z-[9999] pointer-events-none';␍
tooltipEl.textContent = tooltip;␍
const rect = this.getBoundingClientRect();␍
tooltipEl.style.left = rect.left + (rect.width / 2) + 'px';␍
tooltipEl.style.top = rect.top - 30 + 'px';␍
tooltipEl.style.transform = 'translateX(-50%)';␍
document.body.appendChild(tooltipEl);␍
this._tooltipEl = tooltipEl;␍
}␍
});␍
␍
element.addEventListener('mouseleave', function() {␍
if (this._tooltipEl) {␍
this._tooltipEl.remove();␍
this._tooltipEl = null;␍
}␍
});␍
});␍
</script>␍
␍
{% block extra_js %}{% endblock %}␍
␍
<!-- 页脚 -->␍
<footer class="mt-12 py-8 text-center border-t border-gray-200">␍
<div class="container mx-auto px-4">␍
<p class="text-gray-600 mb-2 text-lg">🚀 DVS云盘 · 某个开发者的作品</p>␍
<p class="text-gray-500 text-sm">␍
安全的代码是优美的代码 · ␍
用户友好的设计是简单的设计 ·␍
每一次302重定向都是明了的防护␍
</p>␍
<p class="text-gray-400 text-xs mt-4">␍
&copy; 2026 DVS云盘 保留所有权利␍
</p>␍
</div>␍
</footer>␍
</body>␍
</html>
+86
View File
@@ -0,0 +1,86 @@
{% extends "base.html" %}
{% block title %}修改密码 - DVS云盘{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<div class="max-w-lg mx-auto">
<div class="mac-card">
<div class="mac-card-header">
<div class="flex items-center">
<i class="fas fa-key mr-3"></i>
<h5 class="mb-0">修改密码</h5>
</div>
</div>
<div class="p-6">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="mac-alert mac-alert-{{ 'success' if category == 'success' else 'info' if category == 'info' else 'warning' if category == 'warning' else 'danger' }} mb-6">
<i class="fas fa-{{ 'check-circle' if category == 'success' else 'info-circle' if category == 'info' else 'exclamation-triangle' if category == 'warning' else 'times-circle' }}"></i>
<span>{{ message|safe }}</span>
<button type="button" class="mac-action-btn ml-auto" onclick="this.parentElement.remove()">
<i class="fas fa-times"></i>
</button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST" action="{{ url_for('change_password') }}">
<div class="mb-6">
<label for="current_password" class="block text-gray-700 mb-2 font-medium">
<i class="fas fa-lock mr-2"></i>当前密码
</label>
<input type="password" class="mac-input w-full" id="current_password" name="current_password" required>
</div>
<div class="mb-6">
<label for="new_password" class="block text-gray-700 mb-2 font-medium">
<i class="fas fa-key mr-2"></i>新密码
</label>
<input type="password" class="mac-input w-full" id="new_password" name="new_password" required>
<div class="text-gray-500 text-sm mt-2">密码长度至少6个字符</div>
</div>
<div class="mb-8">
<label for="confirm_password" class="block text-gray-700 mb-2 font-medium">
<i class="fas fa-lock mr-2"></i>确认新密码
</label>
<input type="password" class="mac-input w-full" id="confirm_password" name="confirm_password" required>
<div class="text-gray-500 text-sm mt-2">请再次输入新密码以确保一致性</div>
</div>
<div class="flex justify-between gap-3">
<a href="{{ url_for('index') }}" class="mac-btn mac-btn-secondary">
<i class="fas fa-arrow-left mr-2"></i>返回
</a>
<button type="submit" class="mac-btn mac-btn-primary">
<i class="fas fa-save mr-2"></i>保存更改
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 密码确认验证
const newPassword = document.getElementById('new_password');
const confirmPassword = document.getElementById('confirm_password');
function validatePassword() {
if (newPassword.value !== confirmPassword.value) {
confirmPassword.setCustomValidity('两次输入的密码不一致');
} else {
confirmPassword.setCustomValidity('');
}
}
newPassword.addEventListener('change', validatePassword);
confirmPassword.addEventListener('keyup', validatePassword);
});
</script>
{% endblock %}
Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

+973
View File
@@ -0,0 +1,973 @@
{% extends "base.html" %}
{% block title %}DVS云盘 - 首页{% endblock %}
{% block content %}
<!-- 面包屑 -->
<div class="mac-breadcrumb">
<div class="flex items-center flex-wrap gap-2">
<div class="mac-breadcrumb-item">
<a href="{{ url_for('index') }}">根目录</a>
</div>
{% if breadcrumbs %}
{% for crumb in breadcrumbs[1:] %}
<div class="mac-breadcrumb-item">/</div>
<div class="mac-breadcrumb-item">
<a href="{{ url_for('index', path=crumb.path) }}">{{ crumb.name }}</a>
</div>
{% endfor %}
{% endif %}
</div>
</div>
<!-- Flash 消息 -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div id="flashMessages" class="fixed top-20 right-4 left-4 md:left-auto z-50 space-y-2 max-w-md">
{% for category, message in messages %}
<div class="mac-alert mac-alert-{{ category }} animate-slideInRight" data-duration="5000">
<div class="flex items-start gap-3">
{% if category == 'success' %}
<i class="fas fa-check-circle text-success text-xl"></i>
{% elif category == 'danger' %}
<i class="fas fa-exclamation-circle text-danger text-xl"></i>
{% elif category == 'warning' %}
<i class="fas fa-exclamation-triangle text-warning text-xl"></i>
{% else %}
<i class="fas fa-info-circle text-primary text-xl"></i>
{% endif %}
<div class="flex-1">
<div class="font-medium">
{% if category == 'success' %}成功{% elif category == 'danger' %}错误{% elif category == 'warning' %}警告{% else %}提示{% endif %}
</div>
<div class="text-sm mt-1">{{ message|safe }}</div>
</div>
<button type="button" class="text-gray-400 hover:text-gray-600" onclick="this.parentElement.parentElement.remove()">
<i class="fas fa-times"></i>
</button>
</div>
</div>
{% endfor %}
</div>
<script>
document.querySelectorAll('#flashMessages .mac-alert').forEach(msg => {
const duration = parseInt(msg.getAttribute('data-duration')) || 5000;
setTimeout(() => { msg.style.opacity = '0'; setTimeout(() => msg.remove(), 300); }, duration);
});
</script>
{% endif %}
{% endwith %}
<!-- 顶部操作按钮 - 移动端适配 -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
<div>
<h1 class="text-2xl font-bold text-dark">我的文件</h1>
<p class="text-muted mt-1 text-sm">管理你的文件与文件夹</p>
</div>
<div class="flex gap-3 flex-wrap">
<button onclick="showUploadPopup()" class="mac-btn mac-btn-sm">
<i class="fas fa-upload"></i><span>上传文件</span>
</button>
<button onclick="showCreateFolderPopup()" class="mac-btn mac-btn-sm mac-btn-outline">
<i class="fas fa-folder-plus"></i><span>新建文件夹</span>
</button>
</div>
</div>
<!-- 文件列表 - 自适应空白 -->
<div class="mac-card">
<div class="mac-card-header">
<div class="flex items-center gap-2">
<i class="fas fa-folder-open text-primary"></i><span>文件列表</span>
</div>
<span class="mac-badge mac-badge-primary">共 {{ items|length if items else 0 }} 项</span>
</div>
<div class="file-list-container">
{% if items and items|length > 0 %}
{% for item in items %}
<div class="mac-file-item">
<!-- 图标区域 -->
<div class="mac-file-icon
{% if item.is_dir %}folder
{% elif item.ext in ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico'] %}image
{% elif item.ext in ['mp4', 'avi', 'mov', 'mkv', 'flv', 'wmv', 'webm', 'm4v'] %}video
{% elif item.ext in ['mp3', 'wav', 'flac', 'aac', 'ogg', 'm4a', 'wma'] %}audio
{% elif item.ext == 'pdf' %}pdf
{% elif item.ext in ['doc', 'docx'] %}word
{% elif item.ext in ['xls', 'xlsx', 'csv'] %}excel
{% elif item.ext in ['ppt', 'pptx'] %}powerpoint
{% elif item.ext in ['zip', 'rar', '7z', 'tar', 'gz', 'bz2'] %}archive
{% elif item.ext in ['py', 'js', 'java', 'c', 'cpp', 'h', 'html', 'css', 'php', 'rb', 'go', 'rs', 'swift', 'kt'] %}code
{% elif item.ext in ['txt', 'md', 'log', 'ini', 'cfg', 'conf'] %}text
{% elif item.ext in ['exe', 'msi', 'dmg', 'pkg', 'deb', 'rpm', 'appimage'] %}executable
{% elif item.ext in ['iso', 'img', 'vhd', 'vmdk'] %}disk
{% elif item.ext in ['psd', 'ai', 'eps'] %}design
{% elif item.ext in ['bin', 'dat', 'db'] %}binary
{% else %}document
{% endif %}">
<!-- 图标内容 -->
{% if item.is_dir %}
<i class="fas fa-folder"></i>
{% elif item.ext in ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico'] %}
<i class="fas fa-image"></i>
{% elif item.ext in ['mp4', 'avi', 'mov', 'mkv', 'flv', 'wmv', 'webm', 'm4v'] %}
<i class="fas fa-video"></i>
{% elif item.ext in ['mp3', 'wav', 'flac', 'aac', 'ogg', 'm4a', 'wma'] %}
<i class="fas fa-music"></i>
{% elif item.ext == 'pdf' %}
<i class="fas fa-file-pdf"></i>
{% elif item.ext in ['doc', 'docx'] %}
<i class="fas fa-file-word"></i>
{% elif item.ext in ['xls', 'xlsx', 'csv'] %}
<i class="fas fa-file-excel"></i>
{% elif item.ext in ['ppt', 'pptx'] %}
<i class="fas fa-file-powerpoint"></i>
{% elif item.ext in ['zip', 'rar', '7z', 'tar', 'gz', 'bz2'] %}
<i class="fas fa-file-archive"></i>
{% elif item.ext in ['py', 'js', 'java', 'c', 'cpp', 'h', 'html', 'css', 'php', 'rb', 'go', 'rs', 'swift', 'kt'] %}
<i class="fas fa-code"></i>
{% elif item.ext in ['txt', 'md', 'log', 'ini', 'cfg', 'conf'] %}
<i class="fas fa-file-alt"></i>
{% elif item.ext in ['exe', 'msi', 'dmg', 'pkg', 'deb', 'rpm', 'appimage'] %}
<i class="fas fa-cog"></i>
{% elif item.ext in ['iso', 'img', 'vhd', 'vmdk'] %}
<i class="fas fa-compact-disc"></i>
{% elif item.ext in ['psd', 'ai', 'eps'] %}
<i class="fas fa-palette"></i>
{% elif item.ext in ['bin', 'dat', 'db'] %}
<i class="fas fa-microchip"></i>
{% else %}
<i class="fas fa-file"></i>
{% endif %}
</div>
<!-- 文件信息区域 - 支持自动换行 -->
<div class="flex-1 min-w-0">
<div class="flex flex-col gap-2">
<!-- 文件名和标签行 - 自动换行 -->
<div class="flex flex-wrap items-center gap-2">
{% if item.is_dir %}
<a class="font-semibold text-dark hover:text-primary text-base break-all" href="{{ url_for('index', path=item.path) }}">{{ item.name }}</a>
{% else %}
<span class="font-semibold text-dark text-base break-all">{{ item.name }}</span>
{% endif %}
<!-- 文件类型标签 -->
{% if not item.is_dir %}
<span class="mac-badge mac-badge-sm file-type-badge flex-shrink-0">
<i class="fas fa-tag text-xs"></i>
{% if item.ext %}
{{ item.ext|upper }} -
{% if item.ext in ['mp3', 'wav', 'flac', 'aac', 'ogg', 'm4a', 'wma'] %}音频文件
{% elif item.ext in ['mp4', 'avi', 'mov', 'mkv', 'flv', 'wmv', 'webm', 'm4v'] %}视频文件
{% elif item.ext in ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico'] %}图片文件
{% elif item.ext == 'pdf' %}PDF文档
{% elif item.ext in ['doc', 'docx'] %}Word文档
{% elif item.ext in ['xls', 'xlsx', 'csv'] %}Excel表格
{% elif item.ext in ['ppt', 'pptx'] %}演示文稿
{% elif item.ext in ['zip', 'rar', '7z', 'tar', 'gz', 'bz2'] %}压缩文件
{% elif item.ext in ['py', 'js', 'java', 'c', 'cpp', 'h', 'html', 'css', 'php', 'rb', 'go', 'rs', 'swift', 'kt'] %}代码文件
{% elif item.ext in ['txt', 'md', 'log', 'ini', 'cfg', 'conf'] %}文本文件
{% elif item.ext in ['exe', 'msi', 'dmg', 'pkg', 'deb', 'rpm', 'appimage'] %}可执行文件
{% elif item.ext in ['iso', 'img', 'vhd', 'vmdk'] %}磁盘镜像
{% elif item.ext in ['psd', 'ai', 'eps'] %}设计文件
{% elif item.ext in ['bin', 'dat', 'db'] %}二进制文件
{% else %}其他文件
{% endif %}
{% else %}
未知类型
{% endif %}
</span>
{% else %}
<span class="mac-badge mac-badge-sm folder-badge flex-shrink-0">
<i class="fas fa-folder-open text-xs"></i>
文件夹目录
</span>
{% endif %}
{% if item.previewable %}
<span class="mac-badge mac-badge-info flex-shrink-0"><i class="fas fa-eye"></i>可预览</span>
{% endif %}
</div>
<!-- 文件详情 - 自动换行 -->
<div class="text-sm text-muted flex flex-wrap items-center gap-x-3 gap-y-1">
{% if item.is_dir %}
<span><i class="far fa-folder-open mr-1"></i>文件夹</span>
{% else %}
<span><i class="far fa-file mr-1"></i>{{ item.size or "未知大小" }}</span>
{% endif %}
<span><i class="far fa-clock mr-1"></i>{{ item.modified or "未知" }}</span>
{% if not item.is_dir %}
<span><i class="fas fa-hashtag mr-1"></i>扩展名: {{ item.ext|upper if item.ext else '无' }}</span>
{% endif %}
</div>
</div>
</div>
<!-- 操作按钮区域 - 彩色但柔和 -->
<div class="mac-action-buttons">
<!-- 文件夹专用"打开"按钮 -->
{% if item.is_dir %}
<a class="mac-action-btn mac-action-btn-folder" data-tooltip="打开文件夹" href="{{ url_for('index', path=item.path) }}">
<i class="fas fa-folder-open"></i>
</a>
{% endif %}
{% if not item.is_dir %}
<a class="mac-action-btn mac-action-btn-download" data-tooltip="下载" href="{{ url_for('download', file_path=item.path) }}">
<i class="fas fa-download"></i>
</a>
{% if item.previewable %}
<a class="mac-action-btn mac-action-btn-preview" data-tooltip="预览" href="{{ url_for('preview', file_path=item.path) }}" target="_blank">
<i class="fas fa-eye"></i>
</a>
{% endif %}
{% endif %}
<button class="mac-action-btn mac-action-btn-rename" data-tooltip="重命名" onclick="openRenamePopup('{{ item.path }}', '{{ item.name }}')">
<i class="fas fa-pen"></i>
</button>
<button class="mac-action-btn mac-action-btn-share" data-tooltip="分享" onclick="openSharePopup('{{ item.path }}')">
<i class="fas fa-link"></i>
</button>
<button class="mac-action-btn mac-action-btn-delete" data-tooltip="删除" onclick="openDeletePopup('{{ item.path }}', '{{ item.name }}')">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
{% endfor %}
{% else %}
<div class="mac-alert mac-alert-info">
<i class="fas fa-info-circle text-primary text-xl"></i>
<div>
<div class="font-semibold text-dark">这里还没有文件</div>
<div class="text-muted text-sm mt-1">点击右上角上传文件或创建文件夹。</div>
</div>
</div>
{% endif %}
</div>
</div>
<!-- 上传弹窗 - MAC风格圆角 -->
<div id="uploadPopup" class="hidden fixed inset-0 items-center justify-center" style="display:none;">
<div class="absolute inset-0 bg-black/50" onclick="closePopup('uploadPopup')"></div>
<div class="mac-dialog relative w-[90%] max-w-lg mx-4 rounded-2xl overflow-hidden">
<div class="mac-card-header bg-gray-50 px-5 py-4">
<div class="flex items-center gap-2"><i class="fas fa-upload text-primary"></i><span class="font-semibold">上传文件</span></div>
<button class="mac-action-btn" onclick="closePopup('uploadPopup')"><i class="fas fa-times"></i></button>
</div>
<form method="POST" action="{{ url_for('upload') }}" enctype="multipart/form-data" class="p-5 space-y-4">
<input type="hidden" name="path" value="{{ path or '' }}">
<div>
<label class="block text-sm font-medium text-dark mb-2">选择文件(支持多选)</label>
<input class="mac-input w-full rounded-xl" type="file" name="file" multiple required>
<p class="text-muted text-xs mt-2"><i class="fas fa-info-circle"></i> 可同时选择多个文件上传</p>
</div>
<div class="flex justify-end gap-3 pt-2 flex-wrap">
<button type="button" class="mac-btn mac-btn-outline rounded-full" onclick="closePopup('uploadPopup')">取消</button>
<button type="submit" class="mac-btn mac-btn-success rounded-full"><i class="fas fa-cloud-upload-alt"></i>开始上传</button>
</div>
</form>
</div>
</div>
<!-- 新建文件夹弹窗 -->
<div id="createFolderPopup" class="hidden fixed inset-0 items-center justify-center" style="display:none;">
<div class="absolute inset-0 bg-black/50" onclick="closePopup('createFolderPopup')"></div>
<div class="mac-dialog relative w-[90%] max-w-lg mx-4 rounded-2xl overflow-hidden">
<div class="mac-card-header bg-gray-50 px-5 py-4">
<div class="flex items-center gap-2"><i class="fas fa-folder-plus text-primary"></i><span class="font-semibold">新建文件夹</span></div>
<button class="mac-action-btn" onclick="closePopup('createFolderPopup')"><i class="fas fa-times"></i></button>
</div>
<form method="POST" action="{{ url_for('create_folder') }}" class="p-5 space-y-4">
<input type="hidden" name="path" value="{{ path or '' }}">
<div>
<label class="block text-sm font-medium text-dark mb-2">文件夹名称</label>
<input class="mac-input w-full rounded-xl" type="text" name="folder_name" placeholder="例如:学习资料" required>
</div>
<div class="flex justify-end gap-3 pt-2 flex-wrap">
<button type="button" class="mac-btn mac-btn-outline rounded-full" onclick="closePopup('createFolderPopup')">取消</button>
<button type="submit" class="mac-btn rounded-full"><i class="fas fa-check"></i>创建</button>
</div>
</form>
</div>
</div>
<!-- 分享弹窗 -->
<div id="sharePopup" class="hidden fixed inset-0 items-center justify-center" style="display:none;">
<div class="absolute inset-0 bg-black/50" onclick="closePopup('sharePopup')"></div>
<div class="mac-dialog relative w-[90%] max-w-lg mx-4 rounded-2xl overflow-hidden">
<div class="mac-card-header bg-gray-50 px-5 py-4">
<div class="flex items-center gap-2"><i class="fas fa-link text-primary"></i><span class="font-semibold">分享</span></div>
<button class="mac-action-btn" onclick="closePopup('sharePopup')"><i class="fas fa-times"></i></button>
</div>
<form method="POST" action="{{ url_for('share') }}" class="p-5 space-y-4">
<input type="hidden" name="item_path" id="shareItemPathInput">
<div class="mac-alert mac-alert-info rounded-xl">
<i class="fas fa-info-circle text-primary text-xl"></i>
<div><div class="font-semibold text-dark">创建分享链接</div><div class="text-muted text-sm mt-1">提交后,后端会显示分享链接。</div></div>
</div>
<div class="flex justify-end gap-3 pt-2 flex-wrap">
<button type="button" class="mac-btn mac-btn-outline rounded-full" onclick="closePopup('sharePopup')">取消</button>
<button type="submit" class="mac-btn rounded-full"><i class="fas fa-link"></i>生成链接</button>
</div>
</form>
</div>
</div>
<!-- 重命名弹窗 -->
<div id="renamePopup" class="hidden fixed inset-0 items-center justify-center" style="display:none;">
<div class="absolute inset-0 bg-black/50" onclick="closePopup('renamePopup')"></div>
<div class="mac-dialog relative w-[90%] max-w-lg mx-4 rounded-2xl overflow-hidden">
<div class="mac-card-header bg-gray-50 px-5 py-4">
<div class="flex items-center gap-2"><i class="fas fa-pen text-primary"></i><span class="font-semibold">重命名</span></div>
<button class="mac-action-btn" onclick="closePopup('renamePopup')"><i class="fas fa-times"></i></button>
</div>
<form method="POST" action="{{ url_for('rename') }}" class="p-5 space-y-4">
<input type="hidden" name="path" value="{{ path or '' }}">
<input type="hidden" name="item_path" id="renameItemPathInput">
<div>
<label class="block text-sm font-medium text-dark mb-2">新名称</label>
<input class="mac-input w-full rounded-xl" type="text" name="new_name" id="renameNewNameInput" required>
</div>
<div class="flex justify-end gap-3 pt-2 flex-wrap">
<button type="button" class="mac-btn mac-btn-outline rounded-full" onclick="closePopup('renamePopup')">取消</button>
<button type="submit" class="mac-btn mac-btn-warning rounded-full"><i class="fas fa-check"></i>确认修改</button>
</div>
</form>
</div>
</div>
<!-- 删除弹窗 -->
<div id="deletePopup" class="hidden fixed inset-0 items-center justify-center" style="display:none;">
<div class="absolute inset-0 bg-black/50" onclick="closePopup('deletePopup')"></div>
<div class="mac-dialog relative w-[90%] max-w-lg mx-4 rounded-2xl overflow-hidden">
<div class="mac-card-header bg-gray-50 px-5 py-4">
<div class="flex items-center gap-2 text-danger"><i class="fas fa-trash"></i><span class="font-semibold">确认删除</span></div>
<button class="mac-action-btn" onclick="closePopup('deletePopup')"><i class="fas fa-times"></i></button>
</div>
<form method="POST" action="{{ url_for('delete') }}" class="p-5 space-y-4">
<input type="hidden" name="path" value="{{ path or '' }}">
<input type="hidden" name="item_path" id="deleteItemPathInput">
<div class="mac-alert mac-alert-danger rounded-xl">
<i class="fas fa-exclamation-triangle text-danger text-xl"></i>
<div><div class="font-semibold text-dark">你确定要删除 <span id="deleteNameSpan"></span> 吗?</div><div class="text-muted text-sm mt-1">删除后不可恢复。</div></div>
</div>
<div class="flex justify-end gap-3 pt-2 flex-wrap">
<button type="button" class="mac-btn mac-btn-outline rounded-full" onclick="closePopup('deletePopup')">取消</button>
<button type="submit" class="mac-btn mac-btn-danger rounded-full"><i class="fas fa-trash"></i>确认删除</button>
</div>
</form>
</div>
</div>
<!-- 浮动快速操作菜单 -->
<div id="floatMenu" class="hidden fixed bottom-24 right-6 left-6 sm:left-auto mac-dropdown z-50 rounded-xl shadow-lg">
<div class="mac-dropdown-item rounded-lg" onclick="showUploadPopup(); hideFloatMenu();"><i class="fas fa-upload text-primary"></i><span>上传文件</span></div>
<div class="mac-dropdown-item rounded-lg" onclick="showCreateFolderPopup(); hideFloatMenu();"><i class="fas fa-folder-plus text-success"></i><span>新建文件夹</span></div>
<div class="mac-dropdown-item rounded-lg" onclick="window.location.href='{{ url_for('shared_files') }}'; hideFloatMenu();"><i class="fas fa-share-alt text-info"></i><span>我的分享</span></div>
</div>
<style>
/* ========== 自适应空白系统 ========== */
/* 基础容器 - 响应式内边距 */
.container {
width: 100%;
margin: 0 auto;
}
/* 主内容区域 - 自适应边距 */
main {
width: 100%;
max-width: 1400px;
margin: 0 auto;
}
/* 文件列表容器 - 动态边距 */
.file-list-container {
width: 100%;
padding: 0;
}
/* 屏幕宽度 >= 1200px (电脑) - 保持原样 */
@media (min-width: 1200px) {
main .container {
padding-left: 24px;
padding-right: 24px;
}
.file-list-container {
padding: 24px;
}
.mac-card {
margin: 0;
}
}
/* 屏幕宽度 992px - 1199px (小电脑/大平板横屏) - 较小空白 */
@media (min-width: 992px) and (max-width: 1199px) {
main .container {
padding-left: 20px;
padding-right: 20px;
}
.file-list-container {
padding: 20px;
}
.mac-card {
margin: 0;
}
}
/* 屏幕宽度 768px - 991px (平板横屏) - 中等空白 */
@media (min-width: 768px) and (max-width: 991px) {
main .container {
padding-left: 28px;
padding-right: 28px;
}
.file-list-container {
padding: 18px;
}
.mac-card {
margin: 0 4px;
}
}
/* 屏幕宽度 640px - 767px (平板竖屏/大手机) - 较小空白 */
@media (min-width: 640px) and (max-width: 767px) {
main .container {
padding-left: 16px;
padding-right: 16px;
}
.file-list-container {
padding: 14px;
}
.mac-card {
margin: 0 6px;
}
}
/* 屏幕宽度 480px - 639px (手机) - 很小空白 */
@media (min-width: 480px) and (max-width: 639px) {
main .container {
padding-left: 12px;
padding-right: 12px;
}
.file-list-container {
padding: 12px;
}
.mac-card {
margin: 0 8px;
}
}
/* 屏幕宽度 360px - 479px (小手机) - 最小空白 */
@media (min-width: 360px) and (max-width: 479px) {
main .container {
padding-left: 10px;
padding-right: 10px;
}
.file-list-container {
padding: 10px;
}
.mac-card {
margin: 0 6px;
}
}
/* 屏幕宽度 < 360px (极小手机) - 边缘贴边 */
@media (max-width: 359px) {
main .container {
padding-left: 8px;
padding-right: 8px;
}
.file-list-container {
padding: 8px;
}
.mac-card {
margin: 0 4px;
}
}
/* 文件类型图标颜色增强 */
.mac-file-icon.folder { background: linear-gradient(135deg, #60a5fa, #3b82f6); }
.mac-file-icon.image { background: linear-gradient(135deg, #38bdf8, #0ea5e9); }
.mac-file-icon.video { background: linear-gradient(135deg, #8b5cf6, #7c3aed); }
.mac-file-icon.audio { background: linear-gradient(135deg, #10b981, #059669); }
.mac-file-icon.pdf { background: linear-gradient(135deg, #f87171, #ef4444); }
.mac-file-icon.word { background: linear-gradient(135deg, #3b82f6, #2563eb); }
.mac-file-icon.excel { background: linear-gradient(135deg, #10b981, #059669); }
.mac-file-icon.powerpoint { background: linear-gradient(135deg, #f97316, #ea580c); }
.mac-file-icon.archive { background: linear-gradient(135deg, #f59e0b, #d97706); }
.mac-file-icon.code { background: linear-gradient(135deg, #6366f1, #4f46e5); }
.mac-file-icon.text { background: linear-gradient(135deg, #94a3b8, #64748b); }
.mac-file-icon.executable { background: linear-gradient(135deg, #ef4444, #dc2626); }
.mac-file-icon.disk { background: linear-gradient(135deg, #06b6d4, #0891b2); }
.mac-file-icon.design { background: linear-gradient(135deg, #ec4899, #db2777); }
.mac-file-icon.binary { background: linear-gradient(135deg, #78716c, #57534e); }
.mac-file-icon.document { background: linear-gradient(135deg, #a855f7, #9333ea); }
/* 文件列表项 */
.mac-file-item {
background: var(--repo-color-bg);
border: 1px solid var(--repo-color-border);
border-radius: 16px;
padding: 14px;
margin-bottom: 12px;
transition: all 0.2s ease;
display: flex;
flex-direction: row;
gap: 12px;
}
.mac-file-item:hover {
background: var(--repo-color-bg-hover, #f8fafc);
border-color: var(--repo-color-border-hover);
}
/* 移动端布局调整 */
@media (max-width: 640px) {
.mac-file-item {
flex-direction: column;
padding: 14px;
gap: 12px;
}
.mac-file-icon {
width: 48px;
height: 48px;
font-size: 20px;
align-self: flex-start;
}
.mac-action-buttons {
justify-content: flex-start;
margin-top: 4px;
}
}
/* 桌面端保持行内布局 */
@media (min-width: 641px) {
.mac-file-item {
flex-direction: row;
align-items: center;
padding: 16px;
}
.mac-action-buttons {
flex-shrink: 0;
}
}
/* 文件名自动换行 */
.break-all {
word-break: break-all;
overflow-wrap: break-word;
white-space: normal;
}
/* 文件类型标签 */
.file-type-badge {
background: rgba(59, 130, 246, 0.1);
color: var(--repo-color-primary);
font-size: 11px;
padding: 3px 10px;
border-radius: 20px;
}
.folder-badge {
background: rgba(16, 185, 129, 0.1);
color: var(--repo-color-success);
}
.mac-badge-sm {
padding: 4px 12px;
font-size: 11px;
border-radius: 20px;
}
.mac-badge-info {
background: rgba(59, 130, 246, 0.1);
color: var(--repo-color-primary);
}
/* 操作按钮 - 柔和彩色 */
.mac-action-btn {
width: 38px;
height: 38px;
display: flex;
align-items: center;
justify-content: center;
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 12px;
color: #6b7280;
cursor: pointer;
transition: all 0.2s ease;
}
.mac-action-btn:hover {
transform: translateY(-2px);
}
/* 文件夹按钮 */
.mac-action-btn-folder {
background: rgba(59, 130, 246, 0.08);
border-color: rgba(59, 130, 246, 0.2);
color: #3b82f6;
}
.mac-action-btn-folder:hover {
background: rgba(59, 130, 246, 0.15);
border-color: #3b82f6;
color: #2563eb;
}
/* 下载按钮 */
.mac-action-btn-download {
background: rgba(16, 185, 129, 0.08);
border-color: rgba(16, 185, 129, 0.2);
color: #10b981;
}
.mac-action-btn-download:hover {
background: rgba(16, 185, 129, 0.15);
border-color: #10b981;
color: #059669;
}
/* 预览按钮 */
.mac-action-btn-preview {
background: rgba(139, 92, 246, 0.08);
border-color: rgba(139, 92, 246, 0.2);
color: #8b5cf6;
}
.mac-action-btn-preview:hover {
background: rgba(139, 92, 246, 0.15);
border-color: #8b5cf6;
color: #7c3aed;
}
/* 重命名按钮 */
.mac-action-btn-rename {
background: rgba(245, 158, 11, 0.08);
border-color: rgba(245, 158, 11, 0.2);
color: #f59e0b;
}
.mac-action-btn-rename:hover {
background: rgba(245, 158, 11, 0.15);
border-color: #f59e0b;
color: #d97706;
}
/* 分享按钮 */
.mac-action-btn-share {
background: rgba(6, 182, 212, 0.08);
border-color: rgba(6, 182, 212, 0.2);
color: #06b6d4;
}
.mac-action-btn-share:hover {
background: rgba(6, 182, 212, 0.15);
border-color: #06b6d4;
color: #0891b2;
}
/* 删除按钮 */
.mac-action-btn-delete {
background: rgba(239, 68, 68, 0.08);
border-color: rgba(239, 68, 68, 0.2);
color: #ef4444;
}
.mac-action-btn-delete:hover {
background: rgba(239, 68, 68, 0.15);
border-color: #ef4444;
color: #dc2626;
}
/* 移动端按钮触摸优化 */
@media (max-width: 640px) {
.mac-action-btn {
width: 44px;
height: 44px;
border-radius: 14px;
}
.mac-action-buttons {
gap: 10px;
}
}
/* 动画 */
@keyframes slideInRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.animate-slideInRight { animation: slideInRight 0.3s ease-out; }
/* 面包屑移动端适配 */
@media (max-width: 640px) {
.mac-breadcrumb {
padding: 10px 14px;
overflow-x: auto;
white-space: nowrap;
flex-wrap: nowrap;
border-radius: 14px;
}
.mac-breadcrumb .flex {
flex-wrap: nowrap;
}
.mac-breadcrumb-item {
font-size: 12px;
}
}
/* Flash消息移动端适配 */
@media (max-width: 640px) {
#flashMessages {
top: 70px;
right: 12px;
left: 12px;
max-width: none;
}
}
/* 卡片移动端边距优化 */
@media (max-width: 640px) {
.mac-card {
border-radius: 20px;
}
.mac-card-header {
padding: 14px 16px;
}
}
/* 模态框MAC风格圆角 */
.mac-dialog {
border-radius: 24px !important;
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}
.mac-card-header {
border-bottom: 1px solid #e5e7eb;
}
/* 面包屑样式 */
.mac-breadcrumb {
background: var(--repo-color-bg);
border: 1px solid var(--repo-color-border);
border-radius: 14px;
padding: 12px 20px;
margin-bottom: 24px;
}
.mac-breadcrumb-item {
color: var(--repo-color-text-secondary);
font-size: 14px;
}
.mac-breadcrumb-item a {
color: var(--repo-color-primary);
text-decoration: none;
font-weight: 500;
}
.mac-breadcrumb-item a:hover {
text-decoration: underline;
}
/* 卡片样式 */
.mac-card {
background: var(--repo-color-bg);
border-radius: 20px;
border: 1px solid var(--repo-color-border);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
overflow: hidden;
transition: all 0.2s ease;
}
.mac-card:hover {
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.mac-card-header {
background: var(--repo-color-bg-secondary);
padding: 18px 24px;
border-bottom: 1px solid var(--repo-color-border);
font-weight: 600;
color: var(--repo-color-text);
display: flex;
align-items: center;
justify-content: space-between;
}
/* 按钮样式 */
.mac-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
background: var(--repo-color-primary);
color: white;
border: none;
border-radius: 9999px;
padding: 10px 24px;
font-weight: 500;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.mac-btn:hover {
background: var(--repo-color-primary-hover);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
.mac-btn-sm {
padding: 8px 16px;
font-size: 13px;
}
.mac-btn-outline {
background: transparent;
color: var(--repo-color-primary);
border: 1px solid var(--repo-color-border);
box-shadow: none;
}
.mac-btn-outline:hover {
background: rgba(59, 130, 246, 0.08);
border-color: var(--repo-color-primary);
}
.mac-btn-success {
background: var(--repo-color-success);
}
.mac-btn-success:hover {
background: #059669;
}
.mac-btn-warning {
background: var(--repo-color-warning);
}
.mac-btn-warning:hover {
background: #d97706;
}
.mac-btn-danger {
background: var(--repo-color-danger);
}
.mac-btn-danger:hover {
background: #dc2626;
}
/* 警告框 */
.mac-alert {
border-radius: 14px;
padding: 14px 18px;
display: flex;
align-items: center;
gap: 12px;
}
.mac-alert-success {
background: rgba(16, 185, 129, 0.08);
border: 1px solid rgba(16, 185, 129, 0.2);
}
.mac-alert-danger {
background: rgba(239, 68, 68, 0.08);
border: 1px solid rgba(239, 68, 68, 0.2);
}
.mac-alert-warning {
background: rgba(245, 158, 11, 0.08);
border: 1px solid rgba(245, 158, 11, 0.2);
}
.mac-alert-info {
background: rgba(59, 130, 246, 0.08);
border: 1px solid rgba(59, 130, 246, 0.2);
}
/* 浮动按钮 */
.mac-float-btn {
position: fixed;
bottom: 24px;
right: 24px;
width: 56px;
height: 56px;
border-radius: 50%;
background: var(--repo-color-primary);
color: white;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3);
cursor: pointer;
transition: all 0.2s ease;
z-index: 100;
border: none;
}
.mac-float-btn:hover {
background: var(--repo-color-primary-hover);
transform: translateY(-2px) scale(1.05);
}
/* 下拉菜单 */
.mac-dropdown {
background: var(--repo-color-bg);
border: 1px solid var(--repo-color-border);
border-radius: 16px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
padding: 8px;
min-width: 180px;
}
.mac-dropdown-item {
padding: 10px 16px;
border-radius: 12px;
color: var(--repo-color-text);
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 12px;
}
.mac-dropdown-item:hover {
background: var(--repo-color-bg-hover);
color: var(--repo-color-primary);
}
</style>
<script>
function hideFloatMenu() { const m = document.getElementById('floatMenu'); if (m) m.classList.add('hidden'); }
const floatBtn = document.querySelector('.mac-float-btn');
const floatMenu = document.getElementById('floatMenu');
if (floatBtn && floatMenu) {
floatBtn.addEventListener('click', function(e) { e.stopPropagation(); floatMenu.classList.toggle('hidden'); });
document.addEventListener('click', hideFloatMenu);
}
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
['uploadPopup','createFolderPopup','sharePopup','renamePopup','deletePopup'].forEach(id => { const el = document.getElementById(id); if(el) el.classList.add('hidden'); });
hideFloatMenu();
}
});
function openSharePopup(itemPath) {
document.getElementById('shareItemPathInput').value = itemPath;
const p = document.getElementById('sharePopup');
p.classList.remove('hidden');
p.style.cssText = 'display:flex!important;position:fixed!important;inset:0!important;background-color:rgba(0,0,0,0.5)!important;align-items:center!important;justify-content:center!important;z-index:9999!important;';
}
function openRenamePopup(itemPath, name) {
document.getElementById('renameItemPathInput').value = itemPath;
document.getElementById('renameNewNameInput').value = name;
const p = document.getElementById('renamePopup');
p.classList.remove('hidden');
p.style.cssText = 'display:flex!important;position:fixed!important;inset:0!important;background-color:rgba(0,0,0,0.5)!important;align-items:center!important;justify-content:center!important;z-index:9999!important;';
}
function openDeletePopup(itemPath, name) {
document.getElementById('deleteItemPathInput').value = itemPath;
document.getElementById('deleteNameSpan').textContent = name;
const p = document.getElementById('deletePopup');
p.classList.remove('hidden');
p.style.cssText = 'display:flex!important;position:fixed!important;inset:0!important;background-color:rgba(0,0,0,0.5)!important;align-items:center!important;justify-content:center!important;z-index:9999!important;';
}
</script>
{% endblock %}
+71
View File
@@ -0,0 +1,71 @@
{% extends "base.html" %}␍
{% block title %}登录 - DVS云盘{% endblock %}␍
␍
{% block content %}␍
<div class="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">␍
<div class="max-w-md w-full space-y-8">␍
<!-- Flash 消息 -->␍
{% with messages = get_flashed_messages(with_categories=true) %}␍
{% if messages %}␍
{% for category, message in messages %}␍
<div class="mac-alert mac-alert-{{ category }}">␍
<i class="fas fa-{{ 'check-circle' if category == 'success' else 'exclamation-circle' if category == 'danger' else 'info-circle' }}"></i>␍
<span>{{ message|safe }}</span>␍
<button type="button" class="ml-auto text-gray-400 hover:text-gray-600" onclick="this.parentElement.remove()"><i class="fas fa-times"></i></button>␍
</div>␍
{% endfor %}␍
{% endif %}␍
{% endwith %}␍
␍
<div class="mac-card">␍
<div class="mac-card-header text-center justify-center">␍
<div>␍
<i class="fas fa-cloud text-4xl text-primary"></i>␍
<h2 class="mt-3 text-2xl font-bold text-dark">DVS云盘</h2>␍
<p class="text-muted text-sm mt-1">安全、高效的云端存储解决方案</p>␍
</div>␍
</div>␍
<div class="p-6">␍
<form method="POST" action="/login">␍
<div class="mb-4">␍
<label class="block text-dark font-medium mb-2"><i class="fas fa-user mr-2"></i>用户名</label>␍
<input type="text" class="mac-input w-full" name="username" required placeholder="请输入用户名">␍
</div>␍
<div class="mb-6">␍
<label class="block text-dark font-medium mb-2"><i class="fas fa-lock mr-2"></i>密码</label>␍
<input type="password" class="mac-input w-full" name="password" required placeholder="请输入密码" id="password">␍
</div>␍
<button type="submit" class="mac-btn w-full"><i class="fas fa-sign-in-alt mr-2"></i>登录</button>␍
</form>␍
<div class="border-t border-gray-200 my-6"></div>␍
<div class="text-center">␍
<p class="text-muted">还没有账户? <a href="/register" class="text-primary font-medium">立即注册</a></p>␍
</div>␍
<div class="flex justify-center gap-6 mt-6">␍
<div class="flex items-center gap-2 text-muted text-sm"><i class="fas fa-shield-alt text-primary"></i><span>数据加密</span></div>␍
<div class="flex items-center gap-2 text-muted text-sm"><i class="fas fa-bolt text-warning"></i><span>高速传输</span></div>␍
<div class="flex items-center gap-2 text-muted text-sm"><i class="fas fa-sync text-success"></i><span>多端同步</span></div>␍
</div>␍
</div>␍
</div>␍
␍
<div class="text-center">␍
<p class="text-muted text-xs"><i class="fas fa-shield-alt mr-1"></i>安全提示:请妥善保管您的登录凭证</p>␍
</div>␍
␍
<!-- DVS 宣言 -->␍
<div class="mac-card mt-8">␍
<div class="p-6 text-center">␍
<div class="text-primary font-bold text-lg mb-4">🚀 DVS云盘</div>␍
<div class="grid grid-cols-2 md:grid-cols-4 gap-3">␍
<div class="text-sm text-muted">安全的代码是优美的代码</div>␍
<div class="text-sm text-muted">用户友好的设计是简单的设计</div>␍
<div class="text-sm text-muted">每一次302重定向都是明了的防护</div>␍
<div class="text-sm text-muted">每一行日志都是错误的明确记录</div>␍
</div>␍
␍
</div>␍
</div>␍
</div>␍
</div>␍
{% endblock %}
+29
View File
@@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block title %}音频预览 - DVS云盘{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<div class="mac-card">
<div class="mac-card-header">
<div class="flex items-center justify-between">
<div class="flex items-center">
<i class="fas fa-music mr-3"></i>
<h5 class="mb-0">音频预览</h5>
</div>
<a href="{{ url_for('index') }}" class="mac-btn mac-btn-sm">
<i class="fas fa-arrow-left mr-2"></i>返回
</a>
</div>
</div>
<div class="p-6">
<div class="text-center">
<audio controls class="w-full max-w-2xl mx-auto rounded-lg shadow-mac">
<source src="{{ file_path }}" type="audio/mpeg">
您的浏览器不支持音频播放。
</audio>
</div>
</div>
</div>
</div>
{% endblock %}
+26
View File
@@ -0,0 +1,26 @@
{% extends "base.html" %}
{% block title %}PDF预览 - DVS云盘{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<div class="mac-card">
<div class="mac-card-header">
<div class="flex items-center justify-between">
<div class="flex items-center">
<i class="fas fa-file-pdf mr-3"></i>
<h5 class="mb-0">PDF预览</h5>
</div>
<a href="{{ url_for('index') }}" class="mac-btn mac-btn-sm">
<i class="fas fa-arrow-left mr-2"></i>返回
</a>
</div>
</div>
<div class="p-6">
<div class="rounded-xl overflow-hidden shadow-mac">
<iframe src="{{ file_path }}" class="w-full" style="height: 80vh;" frameborder="0"></iframe>
</div>
</div>
</div>
</div>
{% endblock %}
+27
View File
@@ -0,0 +1,27 @@
{% extends "base.html" %}
{% block title %}视频预览 - DVS云盘{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<div class="mac-card">
<div class="mac-card-header">
<div class="flex items-center">
<i class="fas fa-video mr-3"></i>
<h5 class="mb-0">视频预览</h5>
</div>
<a href="{{ url_for('index') }}" class="mac-btn mac-btn-sm">
<i class="fas fa-arrow-left mr-2"></i>返回
</a>
</div>
<div class="p-6">
<div class="text-center">
<video controls class="w-full rounded-xl shadow-mac" style="max-height: 70vh;">
<source src="{{ file_path }}" type="video/mp4">
您的浏览器不支持视频播放。
</video>
</div>
</div>
</div>
</div>
{% endblock %}
+179
View File
@@ -0,0 +1,179 @@
{% extends "base.html" %}
{% block title %}注册 - DVS云盘{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-12">
<div class="max-w-md mx-auto">
<div class="mac-card mb-8">
<div class="mac-card-header">
<div class="flex items-center">
<i class="fas fa-user-plus mr-3"></i>
<h5 class="mb-0">注册账户</h5>
</div>
</div>
<div class="p-6">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="mac-alert mac-alert-{{ 'success' if category == 'success' else 'info' if category == 'info' else 'warning' if category == 'warning' else 'danger' }} mb-6">
<i class="fas fa-{{ 'check-circle' if category == 'success' else 'info-circle' if category == 'info' else 'exclamation-triangle' if category == 'warning' else 'times-circle' }}"></i>
<span>{{ message|safe }}</span>
<button type="button" class="mac-action-btn ml-auto" onclick="this.parentElement.remove()">
<i class="fas fa-times"></i>
</button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST" action="{{ url_for('register') }}">
<div class="mb-6">
<label for="username" class="block text-gray-700 mb-2 font-medium">
<i class="fas fa-user mr-2"></i>用户名
</label>
<input type="text" class="mac-input w-full" id="username" name="username" required
placeholder="请输入用户名" minlength="3" maxlength="20">
<div class="text-gray-500 text-sm mt-2">用户名长度3-20个字符,支持字母、数字和下划线</div>
</div>
<div class="mb-6">
<label for="password" class="block text-gray-700 mb-2 font-medium">
<i class="fas fa-lock mr-2"></i>密码
</label>
<input type="password" class="mac-input w-full" id="password" name="password" required
placeholder="请输入密码" minlength="6">
<div class="text-gray-500 text-sm mt-2">密码长度至少6个字符</div>
</div>
<div class="mb-6">
<label for="confirm-password" class="block text-gray-700 mb-2 font-medium">
<i class="fas fa-lock mr-2"></i>确认密码
</label>
<input type="password" class="mac-input w-full" id="confirm-password" name="confirm-password" required
placeholder="请再次输入密码">
<div class="text-gray-500 text-sm mt-2">请再次输入密码以确保一致性</div>
</div>
<div class="mb-8">
<label for="invitation_code" class="block text-gray-700 mb-2 font-medium">
<i class="fas fa-key mr-2"></i>邀请码
</label>
<input type="text" class="mac-input w-full" id="invitation_code" name="invitation_code" required
placeholder="请输入邀请码">
<div class="text-gray-500 text-sm mt-2">请联系管理员获取邀请码</div>
</div>
<div class="mb-6">
<div class="flex items-center">
<input class="mac-checkbox mr-3" type="checkbox" id="agreeTerms" required>
<label class="text-gray-700 text-sm" for="agreeTerms">
我已阅读并同意
<a href="#" class="text-blue-500 hover:text-blue-600">服务条款</a>
和
<a href="#" class="text-blue-500 hover:text-blue-600">隐私政策</a>
</label>
</div>
</div>
<div class="mb-6">
<button type="submit" class="mac-btn mac-btn-lg w-full">
<i class="fas fa-user-plus mr-2"></i>注册账户
</button>
</div>
</form>
<div class="border-t border-gray-200 pt-6 text-center">
<p class="text-gray-600 mb-0">
已有账户?
<a href="{{ url_for('login') }}" class="text-blue-500 font-medium hover:text-blue-600">
<i class="fas fa-sign-in-alt mr-1"></i>立即登录
</a>
</p>
</div>
</div>
</div>
<div class="mac-card">
<div class="mac-card-header">
<div class="flex items-center">
<i class="fas fa-crown text-yellow-500 mr-3"></i>
<h5 class="mb-0">VIP专属特权</h5>
</div>
</div>
<div class="p-6">
<div class="grid grid-cols-3 gap-6 text-center">
<div class="feature-item">
<div class="w-12 h-12 bg-blue-100 rounded-2xl flex items-center justify-center mx-auto mb-3">
<i class="fas fa-cloud-upload-alt text-blue-500 text-xl"></i>
</div>
<p class="text-sm font-medium text-gray-700">超大存储</p>
</div>
<div class="feature-item">
<div class="w-12 h-12 bg-green-100 rounded-2xl flex items-center justify-center mx-auto mb-3">
<i class="fas fa-bolt text-green-500 text-xl"></i>
</div>
<p class="text-sm font-medium text-gray-700">极速传输</p>
</div>
<div class="feature-item">
<div class="w-12 h-12 bg-red-100 rounded-2xl flex items-center justify-center mx-auto mb-3">
<i class="fas fa-shield-alt text-red-500 text-xl"></i>
</div>
<p class="text-sm font-medium text-gray-700">高级安全</p>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.feature-item {
transition: transform 0.2s ease;
}
.feature-item:hover {
transform: translateY(-2px);
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 密码确认验证
const password = document.getElementById('password');
const confirmPassword = document.getElementById('confirm-password');
function validatePassword() {
if (password.value !== confirmPassword.value) {
confirmPassword.setCustomValidity('两次输入的密码不一致');
} else {
confirmPassword.setCustomValidity('');
}
}
password.addEventListener('change', validatePassword);
confirmPassword.addEventListener('keyup', validatePassword);
// 用户名实时验证
const username = document.getElementById('username');
username.addEventListener('input', function() {
const usernameRegex = /^[a-zA-Z0-9_]{3,20}$/;
if (!usernameRegex.test(username.value)) {
username.setCustomValidity('用户名只能包含字母、数字和下划线,长度3-20位');
} else {
username.setCustomValidity('');
}
});
// 表单提交前的最终验证
document.querySelector('form').addEventListener('submit', function(e) {
validatePassword();
if (!document.getElementById('agreeTerms').checked) {
e.preventDefault();
alert('请同意服务条款和隐私政策');
return false;
}
});
});
</script>
{% endblock %}
+128
View File
@@ -0,0 +1,128 @@
{% extends "base.html" %}
{% block title %}搜索结果 - DVS云盘{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<!-- 面包屑导航 -->
<div class="mac-breadcrumb mb-8">
<div class="flex items-center space-x-2">
{% for crumb in breadcrumbs %}
{% if loop.last %}
<span class="mac-breadcrumb-item text-dark font-medium">{{ crumb.name }}</span>
{% else %}
<a href="{{ url_for('index', path=crumb.path) }}" class="mac-breadcrumb-item">
{{ crumb.name }}
</a>
<i class="fas fa-chevron-right text-gray-400"></i>
{% endif %}
{% endfor %}
</div>
</div>
<!-- 搜索结果统计 -->
<div class="mac-card mb-8">
<div class="mac-card-header">
<div class="flex items-center">
<i class="fas fa-search mr-3"></i>
<h5 class="mb-0">搜索结果</h5>
<span class="mac-badge mac-badge-primary ml-3">{{ results|length }} 个匹配项</span>
</div>
</div>
<div class="p-6">
<p class="text-gray-700">
搜索关键词: <strong>"{{ query }}"</strong>
</p>
</div>
</div>
<!-- 搜索结果列表 -->
<div class="mac-card">
<div class="mac-card-header">
<div class="flex items-center">
<i class="fas fa-list mr-3"></i>
<h5 class="mb-0">搜索结果列表</h5>
</div>
</div>
<div class="p-6">
{% if results %}
<div class="space-y-3">
{% for item in results %}
<div class="mac-file-item">
<div class="flex items-center">
<div class="mr-4">
{% if item.is_dir %}
<div class="mac-file-icon folder">
<i class="fas fa-folder"></i>
</div>
{% else %}
{% if item.ext in ['png', 'jpg', 'jpeg', 'gif'] %}
<div class="mac-file-icon image">
<i class="fas fa-image"></i>
</div>
{% elif item.ext in ['mp4', 'avi', 'mov'] %}
<div class="mac-file-icon video">
<i class="fas fa-video"></i>
</div>
{% elif item.ext in ['mp3', 'wav'] %}
<div class="mac-file-icon audio">
<i class="fas fa-music"></i>
</div>
{% elif item.ext == 'pdf' %}
<div class="mac-file-icon pdf">
<i class="fas fa-file-pdf"></i>
</div>
{% else %}
<div class="mac-file-icon document">
<i class="fas fa-file"></i>
</div>
{% endif %}
{% endif %}
</div>
<div class="flex-1">
<div class="mb-1">
<strong class="text-dark">{{ item.name }}</strong>
</div>
<div class="text-sm text-gray-600">
{% if item.is_dir %}
文件夹 • {{ item.path }}
{% else %}
文件 • {{ item.size }} • {{ item.path }}
{% endif %}
• 修改时间: {{ item.modified }}
</div>
</div>
<div class="mac-action-buttons">
{% if item.is_dir %}
<a href="{{ url_for('index', path=item.path) }}" class="mac-action-btn" title="打开文件夹">
<i class="fas fa-folder-open"></i>
</a>
{% else %}
{% if item.previewable %}
<a href="{{ url_for('preview', file_path=item.path) }}" class="mac-action-btn" title="预览" target="_blank">
<i class="fas fa-eye"></i>
</a>
{% endif %}
<a href="{{ url_for('download', file_path=item.path) }}" class="mac-action-btn" title="下载">
<i class="fas fa-download"></i>
</a>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-12">
<i class="fas fa-search text-gray-400 text-5xl mb-4"></i>
<h5 class="text-gray-600 text-lg font-medium mb-2">没有找到匹配的结果</h5>
<p class="text-gray-500 mb-6">请尝试使用其他关键词搜索</p>
<a href="{{ url_for('index', path=path) }}" class="mac-btn">
<i class="fas fa-arrow-left mr-2"></i>返回文件列表
</a>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}
+618
View File
@@ -0,0 +1,618 @@
{% extends "base.html" %}
{% block title %}已分享文件 - DVS云盘{% endblock %}
{% block content %}
<!-- Flash 消息 -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div id="flashMessages" class="fixed top-20 right-4 left-4 md:left-auto z-50 space-y-2 max-w-md">
{% for category, message in messages %}
<div class="mac-alert mac-alert-{{ category }} animate-slideInRight" data-duration="5000">
<div class="flex items-start gap-3">
{% if category == 'success' %}
<i class="fas fa-check-circle text-success text-xl"></i>
{% elif category == 'danger' %}
<i class="fas fa-exclamation-circle text-danger text-xl"></i>
{% elif category == 'warning' %}
<i class="fas fa-exclamation-triangle text-warning text-xl"></i>
{% else %}
<i class="fas fa-info-circle text-primary text-xl"></i>
{% endif %}
<div class="flex-1">
<div class="font-medium">
{% if category == 'success' %}成功{% elif category == 'danger' %}错误{% elif category == 'warning' %}警告{% else %}提示{% endif %}
</div>
<div class="text-sm mt-1">{{ message|safe }}</div>
</div>
<button type="button" class="text-gray-400 hover:text-gray-600" onclick="this.parentElement.parentElement.remove()">
<i class="fas fa-times"></i>
</button>
</div>
</div>
{% endfor %}
</div>
<script>
document.querySelectorAll('#flashMessages .mac-alert').forEach(msg => {
const duration = parseInt(msg.getAttribute('data-duration')) || 5000;
setTimeout(() => { msg.style.opacity = '0'; setTimeout(() => msg.remove(), 300); }, duration);
});
</script>
{% endif %}
{% endwith %}
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
<div>
<h1 class="text-2xl font-bold text-dark">已分享文件</h1>
<p class="text-muted mt-1 text-sm">管理你分享的文件和文件夹</p>
</div>
<div class="flex gap-3 flex-wrap">
<a href="{{ url_for('index') }}" class="mac-btn mac-btn-sm mac-btn-outline">
<i class="fas fa-arrow-left"></i><span>返回文件列表</span>
</a>
</div>
</div>
<div class="mac-card">
<div class="mac-card-header">
<div class="flex items-center gap-2">
<i class="fas fa-share-alt text-primary"></i><span>分享列表</span>
</div>
<span class="mac-badge mac-badge-primary">共 {{ shared_items|length }} 个分享</span>
</div>
<div class="file-list-container">
{% if shared_items %}
{% for item in shared_items %}
<div class="mac-file-item">
<!-- 图标区域 -->
<div class="mac-file-icon {% if item.is_dir %}folder{% else %}document{% endif %}">
{% if item.is_dir %}
<i class="fas fa-folder"></i>
{% else %}
<i class="fas fa-file-share"></i>
{% endif %}
</div>
<!-- 文件信息区域 - 支持自动换行 -->
<div class="flex-1 min-w-0">
<div class="flex flex-col gap-2">
<!-- 文件名和标签行 - 自动换行 -->
<div class="flex flex-wrap items-center gap-2">
<span class="font-semibold text-dark text-base break-all">{{ item.name }}</span>
<!-- 文件类型标签 -->
{% if not item.is_dir %}
<span class="mac-badge mac-badge-sm file-type-badge flex-shrink-0">
<i class="fas fa-tag text-xs"></i>
分享文件
</span>
{% else %}
<span class="mac-badge mac-badge-sm folder-badge flex-shrink-0">
<i class="fas fa-folder-open text-xs"></i>
分享文件夹
</span>
{% endif %}
</div>
<!-- 文件详情 - 自动换行 -->
<div class="text-sm text-muted flex flex-wrap items-center gap-x-3 gap-y-1">
<span><i class="fas fa-link mr-1"></i>分享ID: {{ item.share_id }}</span>
<span><i class="far fa-calendar-alt mr-1"></i>创建时间: {{ item.created_at }}</span>
<span><i class="fas fa-download mr-1"></i>下载次数: {{ item.downloads }}</span>
<span><i class="far fa-folder mr-1"></i>路径: {{ item.path }}</span>
</div>
</div>
</div>
<!-- 操作按钮区域 - 彩色但柔和 -->
<div class="mac-action-buttons">
<a class="mac-action-btn mac-action-btn-share" data-tooltip="访问分享链接" href="{{ url_for('download_shared', share_id=item.share_id) }}" target="_blank">
<i class="fas fa-external-link-alt"></i>
</a>
<button class="mac-action-btn mac-action-btn-delete unshare-btn" data-tooltip="取消分享" data-share-id="{{ item.share_id }}" data-item-name="{{ item.name }}">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
{% endfor %}
{% else %}
<div class="mac-alert mac-alert-info">
<i class="fas fa-info-circle text-primary text-xl"></i>
<div>
<div class="font-semibold text-dark">还没有分享任何文件</div>
<div class="text-muted text-sm mt-1">从文件列表中选择文件或文件夹并点击"分享"按钮来创建分享链接。</div>
</div>
</div>
<div class="text-center py-6">
<a href="{{ url_for('index') }}" class="mac-btn">
<i class="fas fa-folder mr-2"></i>浏览文件
</a>
</div>
{% endif %}
</div>
</div>
<!-- 取消分享弹窗 -->
<div id="unshareDialog" class="hidden fixed inset-0 items-center justify-center" style="display:none;">
<div class="absolute inset-0 bg-black/50" onclick="closeDialog()"></div>
<div class="mac-dialog relative w-[90%] max-w-lg mx-4 rounded-2xl overflow-hidden">
<div class="mac-card-header bg-gray-50 px-5 py-4">
<div class="flex items-center gap-2 text-danger"><i class="fas fa-trash"></i><span class="font-semibold">取消分享</span></div>
<button class="mac-action-btn mac-dialog-close" onclick="closeDialog()"><i class="fas fa-times"></i></button>
</div>
<form id="unshareForm" method="POST" action="{{ url_for('unshare') }}" class="p-5 space-y-4">
<input type="hidden" id="unshare_share_id" name="share_id">
<div class="mac-alert mac-alert-warning rounded-xl">
<i class="fas fa-exclamation-triangle text-warning text-xl"></i>
<div>
<div class="font-semibold text-dark">确定要取消分享 "<span id="unshare_item_name" class="font-medium"></span>" 吗?</div>
<div class="text-muted text-sm mt-1">取消分享后,已分享的链接将立即失效。</div>
</div>
</div>
<div class="flex justify-end gap-3 pt-2 flex-wrap">
<button type="button" class="mac-btn mac-btn-outline rounded-full mac-dialog-close" onclick="closeDialog()">取消</button>
<button type="submit" class="mac-btn mac-btn-danger rounded-full">确认取消</button>
</div>
</form>
</div>
</div>
<style>
/* ========== 自适应空白系统 ========== */
/* 主内容区域 - 自适应边距 */
main {
width: 100%;
max-width: 1400px;
margin: 0 auto;
}
/* 文件列表容器 - 动态边距 */
.file-list-container {
width: 100%;
padding: 0;
}
/* 屏幕宽度 >= 1200px (电脑) - 保持原样 */
@media (min-width: 1200px) {
main .container {
padding-left: 24px;
padding-right: 24px;
}
.file-list-container {
padding: 24px;
}
.mac-card {
margin: 0;
}
}
/* 屏幕宽度 992px - 1199px (小电脑/大平板横屏) - 较小空白 */
@media (min-width: 992px) and (max-width: 1199px) {
main .container {
padding-left: 20px;
padding-right: 20px;
}
.file-list-container {
padding: 20px;
}
.mac-card {
margin: 0;
}
}
/* 屏幕宽度 768px - 991px (平板横屏) - 中等空白 */
@media (min-width: 768px) and (max-width: 991px) {
main .container {
padding-left: 28px;
padding-right: 28px;
}
.file-list-container {
padding: 18px;
}
.mac-card {
margin: 0 4px;
}
}
/* 屏幕宽度 640px - 767px (平板竖屏/大手机) - 较小空白 */
@media (min-width: 640px) and (max-width: 767px) {
main .container {
padding-left: 16px;
padding-right: 16px;
}
.file-list-container {
padding: 14px;
}
.mac-card {
margin: 0 6px;
}
}
/* 屏幕宽度 480px - 639px (手机) - 很小空白 */
@media (min-width: 480px) and (max-width: 639px) {
main .container {
padding-left: 12px;
padding-right: 12px;
}
.file-list-container {
padding: 12px;
}
.mac-card {
margin: 0 8px;
}
}
/* 屏幕宽度 360px - 479px (小手机) - 最小空白 */
@media (min-width: 360px) and (max-width: 479px) {
main .container {
padding-left: 10px;
padding-right: 10px;
}
.file-list-container {
padding: 10px;
}
.mac-card {
margin: 0 6px;
}
}
/* 屏幕宽度 < 360px (极小手机) - 边缘贴边 */
@media (max-width: 359px) {
main .container {
padding-left: 8px;
padding-right: 8px;
}
.file-list-container {
padding: 8px;
}
.mac-card {
margin: 0 4px;
}
}
/* 文件类型图标颜色 */
.mac-file-icon.folder { background: linear-gradient(135deg, #60a5fa, #3b82f6); }
.mac-file-icon.document { background: linear-gradient(135deg, #a855f7, #9333ea); }
/* 文件列表项 */
.mac-file-item {
background: var(--repo-color-bg);
border: 1px solid var(--repo-color-border);
border-radius: 16px;
padding: 14px;
margin-bottom: 12px;
transition: all 0.2s ease;
display: flex;
flex-direction: row;
gap: 12px;
}
.mac-file-item:hover {
background: var(--repo-color-bg-hover, #f8fafc);
border-color: var(--repo-color-border-hover);
}
/* 移动端布局调整 */
@media (max-width: 640px) {
.mac-file-item {
flex-direction: column;
padding: 14px;
gap: 12px;
}
.mac-file-icon {
width: 48px;
height: 48px;
font-size: 20px;
align-self: flex-start;
}
.mac-action-buttons {
justify-content: flex-start;
margin-top: 4px;
}
}
/* 桌面端保持行内布局 */
@media (min-width: 641px) {
.mac-file-item {
flex-direction: row;
align-items: center;
padding: 16px;
}
.mac-action-buttons {
flex-shrink: 0;
}
}
/* 文件名自动换行 */
.break-all {
word-break: break-all;
overflow-wrap: break-word;
white-space: normal;
}
/* 文件类型标签 */
.file-type-badge {
background: rgba(59, 130, 246, 0.1);
color: var(--repo-color-primary);
font-size: 11px;
padding: 3px 10px;
border-radius: 20px;
}
.folder-badge {
background: rgba(16, 185, 129, 0.1);
color: var(--repo-color-success);
}
.mac-badge-sm {
padding: 4px 12px;
font-size: 11px;
border-radius: 20px;
}
/* 操作按钮 - 柔和彩色 */
.mac-action-btn {
width: 38px;
height: 38px;
display: flex;
align-items: center;
justify-content: center;
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 12px;
color: #6b7280;
cursor: pointer;
transition: all 0.2s ease;
}
.mac-action-btn:hover {
transform: translateY(-2px);
}
/* 分享按钮 */
.mac-action-btn-share {
background: rgba(6, 182, 212, 0.08);
border-color: rgba(6, 182, 212, 0.2);
color: #06b6d4;
}
.mac-action-btn-share:hover {
background: rgba(6, 182, 212, 0.15);
border-color: #06b6d4;
color: #0891b2;
}
/* 删除按钮 */
.mac-action-btn-delete {
background: rgba(239, 68, 68, 0.08);
border-color: rgba(239, 68, 68, 0.2);
color: #ef4444;
}
.mac-action-btn-delete:hover {
background: rgba(239, 68, 68, 0.15);
border-color: #ef4444;
color: #dc2626;
}
/* 移动端按钮触摸优化 */
@media (max-width: 640px) {
.mac-action-btn {
width: 44px;
height: 44px;
border-radius: 14px;
}
.mac-action-buttons {
gap: 10px;
}
}
/* 动画 */
@keyframes slideInRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.animate-slideInRight { animation: slideInRight 0.3s ease-out; }
/* 卡片样式 */
.mac-card {
background: var(--repo-color-bg);
border-radius: 20px;
border: 1px solid var(--repo-color-border);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
overflow: hidden;
transition: all 0.2s ease;
}
.mac-card:hover {
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.mac-card-header {
background: var(--repo-color-bg-secondary);
padding: 18px 24px;
border-bottom: 1px solid var(--repo-color-border);
font-weight: 600;
color: var(--repo-color-text);
display: flex;
align-items: center;
justify-content: space-between;
}
/* 移动端卡片头部调整 */
@media (max-width: 640px) {
.mac-card-header {
padding: 14px 16px;
}
}
/* 按钮样式 */
.mac-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
background: var(--repo-color-primary);
color: white;
border: none;
border-radius: 9999px;
padding: 10px 24px;
font-weight: 500;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
text-decoration: none;
}
.mac-btn:hover {
background: var(--repo-color-primary-hover);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
.mac-btn-sm {
padding: 8px 16px;
font-size: 13px;
}
.mac-btn-outline {
background: transparent;
color: var(--repo-color-primary);
border: 1px solid var(--repo-color-border);
box-shadow: none;
}
.mac-btn-outline:hover {
background: rgba(59, 130, 246, 0.08);
border-color: var(--repo-color-primary);
}
.mac-btn-danger {
background: var(--repo-color-danger);
}
.mac-btn-danger:hover {
background: #dc2626;
}
/* 警告框 */
.mac-alert {
border-radius: 14px;
padding: 14px 18px;
display: flex;
align-items: center;
gap: 12px;
}
.mac-alert-success {
background: rgba(16, 185, 129, 0.08);
border: 1px solid rgba(16, 185, 129, 0.2);
}
.mac-alert-danger {
background: rgba(239, 68, 68, 0.08);
border: 1px solid rgba(239, 68, 68, 0.2);
}
.mac-alert-warning {
background: rgba(245, 158, 11, 0.08);
border: 1px solid rgba(245, 158, 11, 0.2);
}
.mac-alert-info {
background: rgba(59, 130, 246, 0.08);
border: 1px solid rgba(59, 130, 246, 0.2);
}
/* 徽章 */
.mac-badge {
display: inline-flex;
align-items: center;
gap: 4px;
background: var(--repo-color-bg-tertiary);
color: var(--repo-color-text-secondary);
padding: 4px 12px;
border-radius: 9999px;
font-size: 12px;
font-weight: 500;
}
.mac-badge-primary {
background: rgba(59, 130, 246, 0.1);
color: var(--repo-color-primary);
}
/* 模态框MAC风格圆角 */
.mac-dialog {
border-radius: 24px !important;
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}
/* Flash消息移动端适配 */
@media (max-width: 640px) {
#flashMessages {
top: 70px;
right: 12px;
left: 12px;
max-width: none;
}
}
/* 文本颜色 */
.text-dark { color: #1e293b; }
.text-muted { color: #64748b; }
.text-primary { color: #3b82f6; }
.text-success { color: #10b981; }
.text-danger { color: #ef4444; }
.text-warning { color: #f59e0b; }
/* 背景色 */
.bg-gray-50 { background-color: #f9fafb; }
</style>
<script>
$(document).ready(function() {
// 取消分享功能 - 显示弹窗
$('.unshare-btn').click(function() {
var shareId = $(this).data('share-id');
var itemName = $(this).data('item-name');
$('#unshare_share_id').val(shareId);
$('#unshare_item_name').text(itemName);
// 显示弹窗
$('#unshareDialog').removeClass('hidden');
$('#unshareDialog').css({
'display': 'flex',
'position': 'fixed',
'inset': '0',
'background-color': 'rgba(0, 0, 0, 0.5)',
'align-items': 'center',
'justify-content': 'center',
'z-index': '9999'
});
$('body').css('overflow', 'hidden');
});
// ESC键关闭弹窗
$(document).keyup(function(e) {
if (e.key === "Escape") {
closeDialog();
}
});
});
// 关闭弹窗函数
function closeDialog() {
$('#unshareDialog').addClass('hidden');
$('#unshareDialog').css('display', 'none');
$('body').css('overflow', 'auto');
}
// 点击遮罩层关闭弹窗
document.addEventListener('click', function(e) {
const dialog = document.getElementById('unshareDialog');
if (dialog && !dialog.classList.contains('hidden')) {
if (e.target === dialog || e.target.classList.contains('mac-dialog-close') || e.target.closest('.mac-dialog-close')) {
closeDialog();
}
}
});
</script>
{% endblock %}