Initial commit - 玩玩云文件管理系统 v1.0.0
- 完整的前后端代码 - 支持本地存储和SFTP存储 - 文件分享功能 - 上传工具源代码 - 完整的部署文档 - Nginx配置模板 技术栈: - 后端: Node.js + Express + SQLite - 前端: Vue.js 3 + Axios - 存储: 本地存储 / SFTP远程存储
This commit is contained in:
1997
frontend/app.html
Normal file
1997
frontend/app.html
Normal file
File diff suppressed because it is too large
Load Diff
1708
frontend/app.js
Normal file
1708
frontend/app.js
Normal file
File diff suppressed because it is too large
Load Diff
191
frontend/index.html
Normal file
191
frontend/index.html
Normal file
@@ -0,0 +1,191 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>玩玩云 - 主页</title>
|
||||
<link rel="stylesheet" href="libs/fontawesome/css/all.min.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
.navbar {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
padding: 20px 50px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 20px rgba(0,0,0,0.1);
|
||||
}
|
||||
.logo {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.nav-buttons { display: flex; gap: 15px; }
|
||||
.btn {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
.btn-primary {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: #5568d3;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: #667eea;
|
||||
border: 2px solid #667eea;
|
||||
}
|
||||
.btn-outline:hover {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
.hero {
|
||||
max-width: 1200px;
|
||||
margin: 80px auto;
|
||||
padding: 0 50px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
.hero h1 {
|
||||
font-size: 56px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.hero p {
|
||||
font-size: 20px;
|
||||
margin-bottom: 40px;
|
||||
opacity: 0.95;
|
||||
}
|
||||
.hero-buttons {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
}
|
||||
.features {
|
||||
max-width: 1200px;
|
||||
margin: 80px auto;
|
||||
padding: 0 50px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 30px;
|
||||
}
|
||||
.feature-card {
|
||||
background: white;
|
||||
padding: 40px 30px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.feature-card:hover {
|
||||
transform: translateY(-8px);
|
||||
box-shadow: 0 20px 50px rgba(0,0,0,0.15);
|
||||
}
|
||||
.feature-icon {
|
||||
font-size: 48px;
|
||||
color: #667eea;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.feature-title {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
color: #333;
|
||||
}
|
||||
.feature-desc {
|
||||
font-size: 15px;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: white;
|
||||
opacity: 0.9;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar">
|
||||
<div class="logo">
|
||||
<i class="fas fa-cloud"></i>
|
||||
玩玩云
|
||||
</div>
|
||||
<div class="nav-buttons">
|
||||
<a href="app.html?action=login" class="btn btn-outline">
|
||||
<i class="fas fa-right-to-bracket"></i> 登录
|
||||
</a>
|
||||
<a href="app.html?action=register" class="btn btn-primary">
|
||||
<i class="fas fa-user-plus"></i> 注册
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="hero">
|
||||
<h1><i class="fas fa-cloud"></i> 玩玩云管理平台</h1>
|
||||
<p>简单、安全、高效的文件管理解决方案<br>连接你的SFTP服务器,随时随地管理文件</p>
|
||||
<div class="hero-buttons">
|
||||
<a href="app.html?action=register" class="btn btn-primary" style="padding: 16px 32px; font-size: 18px;">
|
||||
<i class="fas fa-rocket"></i> 立即开始
|
||||
</a>
|
||||
<a href="app.html?action=login" class="btn btn-outline" style="padding: 16px 32px; font-size: 18px;">
|
||||
<i class="fas fa-right-to-bracket"></i> 已有账号登录
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="features">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon"><i class="fas fa-server"></i></div>
|
||||
<div class="feature-title">连接你的SFTP</div>
|
||||
<div class="feature-desc">支持连接任何SFTP服务器,数据存储在你自己的服务器上,更安全可靠</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon"><i class="fas fa-cloud-upload-alt"></i></div>
|
||||
<div class="feature-title">轻松上传下载</div>
|
||||
<div class="feature-desc">网盘式界面,拖拽上传,快速下载,文件管理从未如此简单</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon"><i class="fas fa-share-alt"></i></div>
|
||||
<div class="feature-title">一键分享</div>
|
||||
<div class="feature-desc">生成分享链接,可设置密码保护,轻松分享文件给朋友</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon"><i class="fas fa-lock"></i></div>
|
||||
<div class="feature-title">安全可靠</div>
|
||||
<div class="feature-desc">JWT认证,密码加密存储,保护你的数据安全</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon"><i class="fas fa-mobile-alt"></i></div>
|
||||
<div class="feature-title">响应式设计</div>
|
||||
<div class="feature-desc">完美支持桌面和移动设备,随时随地访问你的文件</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon"><i class="fas fa-chart-line"></i></div>
|
||||
<div class="feature-title">分享统计</div>
|
||||
<div class="feature-desc">查看分享链接的访问次数和下载统计,了解分享效果</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p><i class="fas fa-heart" style="color: #ff6b6b;"></i> 玩玩云 © 2025</p>
|
||||
<p style="margin-top: 10px; font-size: 14px;">轻松管理你的文件,让分享更简单</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
3
frontend/libs/axios.min.js
vendored
Normal file
3
frontend/libs/axios.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
9
frontend/libs/fontawesome/css/all.min.css
vendored
Normal file
9
frontend/libs/fontawesome/css/all.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
frontend/libs/fontawesome/webfonts/fa-brands-400.woff2
Normal file
BIN
frontend/libs/fontawesome/webfonts/fa-brands-400.woff2
Normal file
Binary file not shown.
BIN
frontend/libs/fontawesome/webfonts/fa-regular-400.woff2
Normal file
BIN
frontend/libs/fontawesome/webfonts/fa-regular-400.woff2
Normal file
Binary file not shown.
BIN
frontend/libs/fontawesome/webfonts/fa-solid-900.woff2
Normal file
BIN
frontend/libs/fontawesome/webfonts/fa-solid-900.woff2
Normal file
Binary file not shown.
18323
frontend/libs/vue.global.js
Normal file
18323
frontend/libs/vue.global.js
Normal file
File diff suppressed because it is too large
Load Diff
13
frontend/libs/vue.global.prod.js
Normal file
13
frontend/libs/vue.global.prod.js
Normal file
File diff suppressed because one or more lines are too long
767
frontend/share.html
Normal file
767
frontend/share.html
Normal file
@@ -0,0 +1,767 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>文件分享 - 玩玩云</title>
|
||||
<script src="libs/vue.global.prod.js"></script>
|
||||
<script src="libs/axios.min.js"></script>
|
||||
<link rel="stylesheet" href="libs/fontawesome/css/all.min.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
padding: 30px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.form-group { margin-bottom: 20px; }
|
||||
.form-label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
color: #555;
|
||||
}
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.btn-primary {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
.btn-primary:hover { background: #5568d3; }
|
||||
.alert { padding: 12px; border-radius: 8px; margin-bottom: 15px; }
|
||||
.alert-error { background: #f8d7da; color: #721c24; }
|
||||
.file-list {
|
||||
list-style: none;
|
||||
}
|
||||
.file-item {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #eee;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.file-item:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.file-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
.file-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #999;
|
||||
}
|
||||
/* 视图切换按钮 */
|
||||
.view-controls {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.btn-secondary {
|
||||
background: #e0e0e0;
|
||||
color: #333;
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background: #d0d0d0;
|
||||
}
|
||||
/* 大图标视图 */
|
||||
.file-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 25px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.file-grid-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px 15px;
|
||||
border: 2px solid #e8e8e8;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
background: #fff;
|
||||
}
|
||||
.file-grid-item:hover {
|
||||
background: #f8f9fa;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
border-color: #667eea;
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
.file-grid-icon {
|
||||
font-size: 56px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.file-grid-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
margin-bottom: 8px;
|
||||
max-width: 100%;
|
||||
color: #333;
|
||||
/* 固定显示2行,超出显示省略号 */
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.4;
|
||||
min-height: 39px;
|
||||
max-height: 39px;
|
||||
}
|
||||
.file-grid-size {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
/* 单文件居中显示 */
|
||||
.single-file-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40px 20px;
|
||||
min-height: 300px;
|
||||
}
|
||||
.single-file-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 50px 40px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.single-file-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 12px 32px rgba(0,0,0,0.18);
|
||||
}
|
||||
.single-file-icon {
|
||||
font-size: 120px;
|
||||
margin-bottom: 25px;
|
||||
animation: float 3s ease-in-out infinite;
|
||||
}
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0px); }
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
.single-file-name {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
}
|
||||
.single-file-size {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.single-file-download {
|
||||
padding: 15px 40px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 30px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
.single-file-download:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
|
||||
}
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.single-file-card {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
.single-file-icon {
|
||||
font-size: 80px;
|
||||
}
|
||||
.single-file-name {
|
||||
font-size: 16px;
|
||||
}
|
||||
.single-file-size {
|
||||
font-size: 14px;
|
||||
}
|
||||
.single-file-download {
|
||||
padding: 12px 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
/* 分享不存在提示 */
|
||||
.share-not-found {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.share-not-found-icon {
|
||||
font-size: 100px;
|
||||
color: #ccc;
|
||||
margin-bottom: 30px;
|
||||
animation: fadeIn 0.5s;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: scale(0.8); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
.share-not-found-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.share-not-found-message {
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 移动端适配 */
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
.container {
|
||||
max-width: 100%;
|
||||
}
|
||||
.card {
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.title {
|
||||
font-size: 20px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* 视图切换按钮移动端优化 */
|
||||
.view-controls {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.view-controls .btn {
|
||||
padding: 8px 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 表单移动端优化 */
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.form-label {
|
||||
font-size: 14px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.form-input {
|
||||
padding: 10px 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 文件网格视图移动端优化 */
|
||||
.file-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
.file-grid-item {
|
||||
padding: 15px 10px;
|
||||
}
|
||||
.file-grid-icon {
|
||||
font-size: 44px !important;
|
||||
}
|
||||
.file-grid-name {
|
||||
font-size: 12px;
|
||||
min-height: 34px;
|
||||
max-height: 34px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.file-grid-size {
|
||||
font-size: 11px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.file-grid-item .btn {
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 列表视图移动端优化 */
|
||||
.file-list {
|
||||
padding: 0;
|
||||
}
|
||||
.file-item {
|
||||
padding: 12px 8px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
.file-info {
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
.file-icon {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
.file-item .btn {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
/* 单文件显示移动端优化 */
|
||||
.single-file-container {
|
||||
padding: 20px 10px;
|
||||
min-height: 250px;
|
||||
}
|
||||
.single-file-card {
|
||||
padding: 30px 20px;
|
||||
max-width: 100%;
|
||||
}
|
||||
.single-file-icon {
|
||||
font-size: 80px !important;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.single-file-name {
|
||||
font-size: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.single-file-size {
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.single-file-download {
|
||||
padding: 12px 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 分享不存在提示移动端优化 */
|
||||
.share-not-found {
|
||||
padding: 40px 15px;
|
||||
}
|
||||
.share-not-found-icon {
|
||||
font-size: 70px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.share-not-found-title {
|
||||
font-size: 20px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.share-not-found-message {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 加载状态移动端优化 */
|
||||
.loading {
|
||||
padding: 30px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 超小屏幕优化 (手机竖屏) */
|
||||
@media (max-width: 480px) {
|
||||
.card {
|
||||
padding: 15px;
|
||||
}
|
||||
.title {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* 文件网格更紧凑 */
|
||||
.file-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(75px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
.file-grid-icon {
|
||||
font-size: 36px !important;
|
||||
}
|
||||
.file-grid-name {
|
||||
font-size: 11px;
|
||||
min-height: 31px;
|
||||
max-height: 31px;
|
||||
}
|
||||
.file-grid-size {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* 单文件显示更紧凑 */
|
||||
.single-file-icon {
|
||||
font-size: 60px !important;
|
||||
}
|
||||
.single-file-name {
|
||||
font-size: 14px;
|
||||
}
|
||||
.single-file-size {
|
||||
font-size: 13px;
|
||||
}
|
||||
.single-file-download {
|
||||
padding: 10px 24px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 视图切换按钮 */
|
||||
.view-controls .btn {
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<div class="title">
|
||||
<i class="fas fa-cloud"></i>
|
||||
文件分享
|
||||
</div>
|
||||
<!-- 通用错误显示 -->
|
||||
<div v-if="errorMessage && !needPassword && !verified && !shareNotFound" class="share-not-found">
|
||||
<i class="fas fa-exclamation-circle share-not-found-icon" style="color: #dc3545;"></i>
|
||||
<div class="share-not-found-title">访问失败</div>
|
||||
<div class="share-not-found-message">{{ errorMessage }}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 密码验证 -->
|
||||
<div v-if="needPassword && !verified && !shareNotFound">
|
||||
<div v-if="errorMessage" class="alert alert-error">{{ errorMessage }}</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">请输入分享密码</label>
|
||||
<input type="password" class="form-input" v-model="password" @keyup.enter="verifyShare" placeholder="输入密码">
|
||||
</div>
|
||||
<button class="btn btn-primary" @click="verifyShare">
|
||||
<i class="fas fa-unlock"></i> 验证
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 分享不存在 -->
|
||||
<div v-else-if="shareNotFound" class="share-not-found">
|
||||
<i class="fas fa-inbox share-not-found-icon"></i>
|
||||
<div class="share-not-found-title">来晚了~</div>
|
||||
<div class="share-not-found-message">
|
||||
分享的内容已经取消了<br>
|
||||
或者该分享链接已过期
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<div v-else-if="verified">
|
||||
<p style="color: #666; margin-bottom: 20px;">
|
||||
分享者: <strong>{{ shareInfo.username }}</strong> |
|
||||
创建时间: {{ formatDate(shareInfo.created_at) }}
|
||||
</p>
|
||||
|
||||
<!-- 视图切换按钮 (多文件时才显示) -->
|
||||
<div v-if="files.length > 1" class="view-controls">
|
||||
<button class="btn" :class="viewMode === 'grid' ? 'btn-primary' : 'btn-secondary'" @click="viewMode = 'grid'">
|
||||
<i class="fas fa-th-large"></i> 大图标
|
||||
</button>
|
||||
<button class="btn" :class="viewMode === 'list' ? 'btn-primary' : 'btn-secondary'" @click="viewMode = 'list'">
|
||||
<i class="fas fa-list"></i> 列表
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
|
||||
<!-- 大图标视图 - 单文件居中显示 -->
|
||||
<!-- 单文件居中显示 -->
|
||||
<div v-else-if="files.length === 1" class="single-file-container">
|
||||
<div class="single-file-card">
|
||||
<i class="single-file-icon fas" :class="getFileIcon(files[0])" :style="getIconColor(files[0])"></i>
|
||||
<div class="single-file-name">{{ files[0].name }}</div>
|
||||
<div class="single-file-size">{{ files[0].sizeFormatted }}</div>
|
||||
<button v-if="!files[0].isDirectory" class="btn single-file-download" @click="downloadFile(files[0])">
|
||||
<i class="fas fa-download"></i> 下载文件
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 大图标视图 - 多文件网格显示 -->
|
||||
<div v-else-if="viewMode === 'grid'" class="file-grid">
|
||||
<div v-for="file in files" :key="file.name" class="file-grid-item"
|
||||
@click="handleFileClick(file)"
|
||||
@contextmenu="showFileContextMenu($event, file)"
|
||||
@touchstart="startLongPress($event, file)"
|
||||
@touchend="cancelLongPress"
|
||||
@touchmove="cancelLongPress">
|
||||
<i class="file-grid-icon fas" :class="getFileIcon(file)" :style="getIconColor(file)"></i>
|
||||
<div class="file-grid-name" :title="file.name">{{ file.name }}</div>
|
||||
<div class="file-grid-size">{{ file.sizeFormatted }}</div>
|
||||
<button v-if="!file.isDirectory" class="btn btn-primary" @click.stop="downloadFile(file)" style="width: 100%;">
|
||||
<i class="fas fa-download"></i> 下载
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 列表视图 -->
|
||||
<ul v-else class="file-list">
|
||||
<li v-for="file in files" :key="file.name" class="file-item">
|
||||
<div class="file-info">
|
||||
<i class="file-icon fas" :class="getFileIcon(file)" :style="getIconColor(file)"></i>
|
||||
<div>
|
||||
<div style="font-weight: 500;">{{ file.name }}</div>
|
||||
<div style="font-size: 12px; color: #999;">{{ file.sizeFormatted }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<button v-if="!file.isDirectory" class="btn btn-primary" @click.stop="downloadFile(file)">
|
||||
<i class="fas fa-download"></i> 下载
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p v-if="files.length === 0" style="text-align: center; color: #999; padding: 40px;">
|
||||
暂无文件
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 加载中 -->
|
||||
<div v-else-if="loading" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const { createApp } = Vue;
|
||||
|
||||
createApp({
|
||||
data() {
|
||||
return {
|
||||
// API配置 - 动态适配localhost或生产环境
|
||||
apiBase: window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
|
||||
? 'http://localhost:40001'
|
||||
: window.location.protocol + '//' + window.location.host,
|
||||
shareCode: '',
|
||||
password: '',
|
||||
needPassword: false,
|
||||
verified: false,
|
||||
shareNotFound: false,
|
||||
shareInfo: null,
|
||||
files: [],
|
||||
loading: true,
|
||||
errorMessage: '',
|
||||
viewMode: "grid", // 视图模式: grid 大图标, list 列表(默认大图标)
|
||||
// 媒体预览
|
||||
showImageViewer: false,
|
||||
showVideoPlayer: false,
|
||||
showAudioPlayer: false,
|
||||
currentMediaUrl: '',
|
||||
currentMediaName: '',
|
||||
currentMediaType: '', // 'image', 'video', 'audio'
|
||||
// 右键菜单
|
||||
showContextMenu: false,
|
||||
contextMenuX: 0,
|
||||
contextMenuY: 0,
|
||||
contextMenuFile: null,
|
||||
// 长按支持(移动端)
|
||||
longPressTimer: null,
|
||||
longPressFile: null
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
this.shareCode = urlParams.get('code');
|
||||
|
||||
if (!this.shareCode) {
|
||||
this.errorMessage = '无效的分享链接';
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 尝试验证分享
|
||||
await this.verifyShare();
|
||||
},
|
||||
|
||||
async verifyShare() {
|
||||
this.errorMessage = '';
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const response = await axios.post(`${this.apiBase}/api/share/${this.shareCode}/verify`, {
|
||||
password: this.password
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
this.verified = true;
|
||||
this.shareInfo = response.data.share;
|
||||
|
||||
// 如果是单文件分享且后端已返回文件信息,直接使用,无需再次请求
|
||||
if (response.data.file) {
|
||||
this.files = [response.data.file];
|
||||
this.loading = false;
|
||||
} else {
|
||||
// 目录分享,需要加载文件列表
|
||||
await this.loadFiles();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 404错误 - 分享不存在
|
||||
if (error.response?.status === 404) {
|
||||
this.shareNotFound = true;
|
||||
this.loading = false;
|
||||
}
|
||||
// 需要密码
|
||||
else if (error.response?.data?.needPassword) {
|
||||
this.needPassword = true;
|
||||
this.loading = false;
|
||||
}
|
||||
// 其他错误
|
||||
else {
|
||||
this.errorMessage = error.response?.data?.message || '验证失败';
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async loadFiles() {
|
||||
try {
|
||||
const response = await axios.post(`${this.apiBase}/api/share/${this.shareCode}/list`, {
|
||||
password: this.password,
|
||||
path: ''
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
this.files = response.data.items;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载文件失败:', error);
|
||||
this.errorMessage = '加载文件失败';
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
downloadFile(file) {
|
||||
console.log("[分享下载] 文件:", file);
|
||||
|
||||
// 记录下载次数(异步,不等待)
|
||||
axios.post(`${this.apiBase}/api/share/${this.shareCode}/download`)
|
||||
.catch(err => console.error('记录下载次数失败:', err));
|
||||
|
||||
if (file.httpDownloadUrl) {
|
||||
// 如果配置了HTTP下载URL,使用HTTP直接下载
|
||||
console.log("[分享下载] 使用HTTP下载:", file.httpDownloadUrl);
|
||||
window.open(file.httpDownloadUrl, '_blank');
|
||||
} else {
|
||||
// 如果没有配置HTTP URL,通过后端SFTP下载
|
||||
console.log("[分享下载] 使用SFTP下载");
|
||||
|
||||
// 构建文件路径
|
||||
let filePath;
|
||||
if (this.shareInfo.share_type === 'file') {
|
||||
// 单文件分享,使用 share_path
|
||||
filePath = this.shareInfo.share_path;
|
||||
} else {
|
||||
// 目录分享,组合路径
|
||||
const basePath = this.shareInfo.share_path;
|
||||
filePath = basePath === '/' ? `/${file.name}` : `${basePath}/${file.name}`;
|
||||
}
|
||||
|
||||
// 使用分享下载API(公开API,不需要认证)
|
||||
let downloadUrl = `${this.apiBase}/api/share/${this.shareCode}/download-file?path=${encodeURIComponent(filePath)}`;
|
||||
|
||||
// 如果有密码,附加密码参数
|
||||
if (this.password) {
|
||||
downloadUrl += `&password=${encodeURIComponent(this.password)}`;
|
||||
}
|
||||
|
||||
window.open(downloadUrl, '_blank');
|
||||
}
|
||||
},
|
||||
|
||||
getFileIcon(file) {
|
||||
if (file.isDirectory) return 'fa-folder';
|
||||
if (file.name.match(/\.(jpg|jpeg|png|gif|bmp|svg)$/i)) return 'fa-file-image';
|
||||
if (file.name.match(/\.(mp4|avi|mov|wmv|flv|mkv)$/i)) return 'fa-file-video';
|
||||
if (file.name.match(/\.(mp3|wav|flac|aac|ogg)$/i)) return 'fa-file-audio';
|
||||
if (file.name.match(/\.(pdf)$/i)) return 'fa-file-pdf';
|
||||
if (file.name.match(/\.(doc|docx)$/i)) return 'fa-file-word';
|
||||
if (file.name.match(/\.(xls|xlsx)$/i)) return 'fa-file-excel';
|
||||
if (file.name.match(/\.(zip|rar|7z|tar|gz)$/i)) return 'fa-file-archive';
|
||||
return 'fa-file';
|
||||
},
|
||||
|
||||
getIconColor(file) {
|
||||
if (file.isDirectory) return 'color: #FFC107;';
|
||||
if (file.name.match(/\.(jpg|jpeg|png|gif|bmp|svg)$/i)) return 'color: #4CAF50;';
|
||||
if (file.name.match(/\.(mp4|avi|mov|wmv|flv|mkv)$/i)) return 'color: #9C27B0;';
|
||||
if (file.name.match(/\.(mp3|wav|flac|aac|ogg)$/i)) return 'color: #FF5722;';
|
||||
if (file.name.match(/\.(pdf)$/i)) return 'color: #F44336;';
|
||||
if (file.name.match(/\.(doc|docx)$/i)) return 'color: #2196F3;';
|
||||
if (file.name.match(/\.(xls|xlsx)$/i)) return 'color: #4CAF50;';
|
||||
if (file.name.match(/\.(zip|rar|7z|tar|gz)$/i)) return 'color: #795548;';
|
||||
return 'color: #9E9E9E;';
|
||||
},
|
||||
|
||||
formatDate(dateString) {
|
||||
if (!dateString) return '';
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('zh-CN');
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.init();
|
||||
}
|
||||
}).mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user