feat: v3.1.0 OSS直连优化与代码质量提升
- 🚀 OSS 直连上传下载(用户直连OSS,不经过后端) - ✨ 新增 Presigned URL 签名接口 - ✨ 支持自定义 OSS endpoint 配置 - 🐛 修复 buildS3Config 不支持自定义 endpoint 的问题 - 🐛 清理残留的 basic-ftp 依赖 - ♻️ 更新 package.json 项目描述和版本号 - 📝 完善 README.md 更新日志和 CORS 配置说明 - 🔒 安全性增强:签名 URL 15分钟/1小时有效期 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1322,7 +1322,7 @@
|
||||
<button v-if="storageType === 'local'" class="btn btn-primary" @click="showCreateFolderModal = true">
|
||||
<i class="fas fa-folder-plus"></i> 新建文件夹
|
||||
</button>
|
||||
<!-- SFTP存储:显示下载上传工具按钮 -->
|
||||
<!-- OSS存储:显示下载上传工具按钮 -->
|
||||
<button v-else class="btn btn-primary" @click="downloadUploadTool" :disabled="downloadingTool">
|
||||
<i :class="downloadingTool ? 'fas fa-spinner fa-spin' : 'fas fa-download'"></i>
|
||||
{{ downloadingTool ? '生成中...' : '下载上传工具' }}
|
||||
@@ -1617,83 +1617,83 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SFTP 配置引导弹窗 -->
|
||||
<div v-if="showSftpGuideModal" class="modal-overlay" @mousedown.self="handleModalMouseDown" @mouseup.self="handleModalMouseUp('showSftpGuideModal')">
|
||||
<!-- OSS 配置引导弹窗 -->
|
||||
<div v-if="showOssGuideModal" class="modal-overlay" @mousedown.self="handleModalMouseDown" @mouseup.self="handleModalMouseUp('showOssGuideModal')">
|
||||
<div class="modal-content" @click.stop style="max-width: 520px; border-radius: 16px; overflow: hidden;">
|
||||
<div style="background: linear-gradient(135deg,#667eea,#764ba2); color: white; padding: 18px;">
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<i class="fas fa-server" style="font-size: 20px;"></i>
|
||||
<h3 style="margin: 0; font-size: 20px;">切换到 SFTP 存储</h3>
|
||||
<i class="fas fa-cloud" style="font-size: 20px;"></i>
|
||||
<h3 style="margin: 0; font-size: 20px;">切换到 OSS 存储</h3>
|
||||
</div>
|
||||
<p style="margin: 8px 0 0 0; opacity: 0.9; font-size: 14px;">先配置连接信息,再切换到你的专属 SFTP 空间。</p>
|
||||
<p style="margin: 8px 0 0 0; opacity: 0.9; font-size: 14px;">先配置云服务信息,再切换到你的专属 OSS 空间。</p>
|
||||
</div>
|
||||
<div style="padding: 18px;">
|
||||
<p style="color: var(--text-secondary); line-height: 1.6; margin-bottom: 16px;">
|
||||
我们会在你填写完成后再切换,确保过程平滑无干扰。
|
||||
支持阿里云 OSS、腾讯云 COS、AWS S3 等兼容 S3 协议的云存储服务。
|
||||
</p>
|
||||
<div style="display: flex; gap: 10px; justify-content: flex-end;">
|
||||
<button class="btn btn-secondary" @click="closeSftpGuideModal">稍后再说</button>
|
||||
<button class="btn btn-primary" @click="proceedSftpGuide">
|
||||
<i class="fas fa-tools"></i> 去配置 SFTP
|
||||
<button class="btn btn-secondary" @click="closeOssGuideModal">稍后再说</button>
|
||||
<button class="btn btn-primary" @click="proceedOssGuide">
|
||||
<i class="fas fa-tools"></i> 去配置 OSS
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SFTP 配置弹窗 -->
|
||||
<div v-if="showSftpConfigModal" class="modal-overlay" @mousedown.self="handleModalMouseDown" @mouseup.self="handleModalMouseUp('showSftpConfigModal')">
|
||||
<!-- OSS 配置弹窗 -->
|
||||
<div v-if="showOssConfigModal" class="modal-overlay" @mousedown.self="handleModalMouseDown" @mouseup.self="handleModalMouseUp('showOssConfigModal')">
|
||||
<div class="modal-content" @click.stop style="max-width: 720px; border-radius: 16px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<div>
|
||||
<h3 style="margin: 0 0 6px 0;">配置 SFTP 存储</h3>
|
||||
<p style="margin: 0; color: var(--text-muted); font-size: 13px;">填写连接信息或导入 .inf 配置,保存后即可切换到 SFTP 模式。</p>
|
||||
<h3 style="margin: 0 0 6px 0;">配置 OSS 存储</h3>
|
||||
<p style="margin: 0; color: var(--text-muted); font-size: 13px;">填写云服务配置信息,保存后即可切换到 OSS 模式。</p>
|
||||
</div>
|
||||
<button class="btn btn-secondary" style="padding: 6px 10px;" @click="closeSftpConfigModal">
|
||||
<button class="btn btn-secondary" style="padding: 6px 10px;" @click="closeOssConfigModal">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr; gap: 14px;">
|
||||
<div style="border: 1px dashed var(--glass-border); border-radius: 12px; padding: 16px; background: rgba(255,255,255,0.03); text-align: center; cursor: pointer; transition: all .3s;"
|
||||
@click="$refs.configFileInput.click()"
|
||||
@dragover.prevent="$event.currentTarget.style.background='#eef2ff'"
|
||||
@dragleave.prevent="$event.currentTarget.style.background='#f8fafc'"
|
||||
@drop.prevent="handleConfigFileDrop">
|
||||
<i class="fas fa-cloud-upload-alt" style="font-size: 36px; color: #667eea; margin-bottom: 8px;"></i>
|
||||
<div style="font-weight: 600; color: var(--text-primary);">导入配置文件</div>
|
||||
<div style="color: var(--text-muted); font-size: 13px; margin-top: 4px;">点击选择或拖拽 .inf 文件</div>
|
||||
<input type="file" accept=".inf" @change="handleConfigFileUpload" ref="configFileInput" style="display: none;">
|
||||
<form @submit.prevent="updateOssConfig" style="display: grid; gap: 12px;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">云服务商</label>
|
||||
<select class="form-input" v-model="ossConfigForm.oss_provider" required style="cursor: pointer;">
|
||||
<option value="aliyun">阿里云 OSS</option>
|
||||
<option value="tencent">腾讯云 COS</option>
|
||||
<option value="aws">AWS S3</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="updateFtpConfig" style="display: grid; gap: 12px;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">主机地址</label>
|
||||
<input type="text" class="form-input" v-model="ftpConfigForm.ftp_host" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">端口</label>
|
||||
<input type="number" class="form-input" v-model="ftpConfigForm.ftp_port" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">用户名</label>
|
||||
<input type="text" class="form-input" v-model="ftpConfigForm.ftp_user" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">密码 (留空保留现有密码)</label>
|
||||
<input type="password" class="form-input" v-model="ftpConfigForm.ftp_password" placeholder="留空保留现有密码">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">HTTP下载基础URL (可选)</label>
|
||||
<input type="text" class="form-input" v-model="ftpConfigForm.http_download_base_url" placeholder="例如: http://example.com/files">
|
||||
<small style="color: var(--text-secondary); font-size: 12px; margin-top: 5px; display: block;">
|
||||
配置后可通过 HTTP 直接下载,例如: 基础URL/文件路径。
|
||||
</small>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 4px;">
|
||||
<button type="button" class="btn btn-secondary" @click="closeSftpConfigModal">取消</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save"></i> 保存配置
|
||||
<div class="form-group">
|
||||
<label class="form-label">地域</label>
|
||||
<input type="text" class="form-input" v-model="ossConfigForm.oss_region" placeholder="如: oss-cn-hangzhou / ap-guangzhou / us-east-1" required>
|
||||
<small style="color: var(--text-secondary); font-size: 12px; margin-top: 5px; display: block;">
|
||||
阿里云: oss-cn-hangzhou, 腾讯云: ap-guangzhou, AWS: us-east-1
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Access Key ID</label>
|
||||
<input type="text" class="form-input" v-model="ossConfigForm.oss_access_key_id" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Access Key Secret (留空保留现有密钥)</label>
|
||||
<input type="password" class="form-input" v-model="ossConfigForm.oss_access_key_secret" placeholder="留空保留现有密钥">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">存储桶名称</label>
|
||||
<input type="text" class="form-input" v-model="ossConfigForm.oss_bucket" placeholder="如: my-storage-bucket" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">自定义 Endpoint (可选)</label>
|
||||
<input type="text" class="form-input" v-model="ossConfigForm.oss_endpoint" placeholder="兼容 S3 的服务可填写自定义地址">
|
||||
<small style="color: var(--text-secondary); font-size: 12px; margin-top: 5px; display: block;">
|
||||
一般不需要填写,仅在使用自定义 S3 兼容服务时需要。
|
||||
</small>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 4px;">
|
||||
<button type="button" class="btn btn-secondary" @click="closeOssConfigModal">取消</button>
|
||||
<button type="submit" class="btn btn-primary" :disabled="ossConfigSaving" :style="{ opacity: ossConfigSaving ? 0.7 : 1 }">
|
||||
<i class="fas" :class="ossConfigSaving ? 'fa-spinner fa-spin' : 'fa-save'"></i>
|
||||
{{ ossConfigSaving ? '保存中...' : '保存配置' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1730,9 +1730,9 @@
|
||||
</div>
|
||||
<div v-if="storageSwitching" style="color: #4b5fc9; font-weight: 600; display: inline-flex; align-items: center; gap: 8px;">
|
||||
<i class="fas fa-sync-alt fa-spin"></i>
|
||||
正在切换到 {{ storageSwitchTarget === 'sftp' ? 'SFTP 存储' : '本地存储' }}...
|
||||
正在切换到 {{ storageSwitchTarget === 'oss' ? 'OSS 存储' : '本地存储' }}...
|
||||
</div>
|
||||
<div v-else style="color: var(--text-secondary); font-size: 13px;">本地存储适合快速读写,SFTP 适合独立服务器空间</div>
|
||||
<div v-else style="color: var(--text-secondary); font-size: 13px;">本地存储适合快速读写,OSS 适合云存储扩展</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 16px; background: var(--bg-secondary); border-radius: 12px; padding: 12px; border: 1px solid var(--glass-border);">
|
||||
@@ -1782,53 +1782,53 @@
|
||||
<div style="background: var(--bg-secondary); border: 1px solid var(--glass-border); border-radius: 12px; padding: 16px; box-shadow: 0 6px 20px rgba(0,0,0,0.15); display: flex; flex-direction: column; height: 100%;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
||||
<div style="font-weight: 700; color: var(--text-primary); display: flex; gap: 8px; align-items: center;">
|
||||
<i class="fas fa-server" style="color: var(--accent-1);"></i> SFTP 存储
|
||||
<i class="fas fa-cloud" style="color: var(--accent-1);"></i> OSS 存储
|
||||
</div>
|
||||
<span v-if="storageType === 'sftp'" style="font-size: 12px; color: var(--accent-1); background: rgba(102,126,234,0.15); padding: 4px 8px; border-radius: 999px;">当前</span>
|
||||
<span v-if="storageType === 'oss'" style="font-size: 12px; color: var(--accent-1); background: rgba(102,126,234,0.15); padding: 4px 8px; border-radius: 999px;">当前</span>
|
||||
</div>
|
||||
<div style="color: var(--text-secondary); font-size: 13px; margin-bottom: 10px;">使用你自己的服务器空间,独立存储更灵活。</div>
|
||||
<div v-if="user?.has_ftp_config" style="font-size: 13px; color: var(--text-primary); margin-bottom: 10px;">
|
||||
已配置: {{ user.ftp_host }}:{{ user.ftp_port }}
|
||||
<div style="color: var(--text-secondary); font-size: 13px; margin-bottom: 10px;">使用云存储服务,安全可靠扩展性强。</div>
|
||||
<div v-if="user?.has_oss_config" style="font-size: 13px; color: var(--text-primary); margin-bottom: 10px;">
|
||||
已配置: {{ user.oss_provider }} / {{ user.oss_bucket }}
|
||||
</div>
|
||||
<div v-else style="font-size: 13px; color: #f59e0b; background: rgba(245, 158, 11, 0.1); border: 1px dashed rgba(245,158,11,0.4); padding: 10px; border-radius: 8px; margin-bottom: 10px;">
|
||||
<i class="fas fa-exclamation-circle"></i> 先填写 SFTP 连接信息再切换
|
||||
<i class="fas fa-exclamation-circle"></i> 先填写 OSS 配置信息再切换
|
||||
</div>
|
||||
<!-- SFTP空间使用统计(user_choice模式) -->
|
||||
<div v-if="user?.has_ftp_config" style="margin-bottom: 10px; padding: 10px; background: rgba(255,255,255,0.03); border-radius: 8px; border: 1px solid var(--glass-border);">
|
||||
<!-- OSS空间使用统计(user_choice模式) -->
|
||||
<div v-if="user?.has_oss_config" style="margin-bottom: 10px; padding: 10px; background: rgba(255,255,255,0.03); border-radius: 8px; border: 1px solid var(--glass-border);">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||
<span style="font-size: 12px; color: var(--text-muted);">空间统计</span>
|
||||
<button
|
||||
style="background: none; border: none; color: #4b5fc9; cursor: pointer; font-size: 12px; padding: 2px 6px;"
|
||||
@click.stop="loadSftpUsage()"
|
||||
:disabled="sftpUsageLoading">
|
||||
<i :class="sftpUsageLoading ? 'fas fa-sync-alt fa-spin' : 'fas fa-sync-alt'"></i>
|
||||
@click.stop="loadOssUsage()"
|
||||
:disabled="ossUsageLoading">
|
||||
<i :class="ossUsageLoading ? 'fas fa-sync-alt fa-spin' : 'fas fa-sync-alt'"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="sftpUsageLoading && !sftpUsage" style="text-align: center; color: #667eea; font-size: 12px;">
|
||||
<div v-if="ossUsageLoading && !ossUsage" style="text-align: center; color: #667eea; font-size: 12px;">
|
||||
<i class="fas fa-spinner fa-spin"></i> 统计中...
|
||||
</div>
|
||||
<div v-else-if="sftpUsageError" style="font-size: 12px; color: #ef4444;">
|
||||
<i class="fas fa-exclamation-triangle"></i> {{ sftpUsageError }}
|
||||
<div v-else-if="ossUsageError" style="font-size: 12px; color: #ef4444;">
|
||||
<i class="fas fa-exclamation-triangle"></i> {{ ossUsageError }}
|
||||
</div>
|
||||
<div v-else-if="sftpUsage" style="font-size: 13px; font-weight: 600; color: #4b5fc9;">
|
||||
{{ sftpUsage.totalSizeFormatted }}
|
||||
<span style="font-weight: 400; color: var(--text-muted); font-size: 12px;">({{ sftpUsage.fileCount }} 文件)</span>
|
||||
<div v-else-if="ossUsage" style="font-size: 13px; font-weight: 600; color: #4b5fc9;">
|
||||
{{ ossUsage.totalSizeFormatted }}
|
||||
<span style="font-weight: 400; color: var(--text-muted); font-size: 12px;">({{ ossUsage.fileCount }} 文件)</span>
|
||||
</div>
|
||||
<div v-else style="font-size: 12px; color: var(--text-muted);">点击刷新查看</div>
|
||||
</div>
|
||||
<div style="margin-top: auto;">
|
||||
<button
|
||||
class="btn"
|
||||
:class="user?.has_ftp_config ? 'btn-primary' : 'btn-secondary'"
|
||||
:class="user?.has_oss_config ? 'btn-primary' : 'btn-secondary'"
|
||||
style="width: 100%; border-radius: 10px;"
|
||||
:disabled="storageType === 'sftp' || storageSwitching"
|
||||
@click="switchStorage('sftp')">
|
||||
:disabled="storageType === 'oss' || storageSwitching"
|
||||
@click="switchStorage('oss')">
|
||||
<i class="fas fa-random"></i>
|
||||
{{ user?.has_ftp_config ? '切到 SFTP 存储' : '去配置 SFTP' }}
|
||||
{{ user?.has_oss_config ? '切到 OSS 存储' : '去配置 OSS' }}
|
||||
</button>
|
||||
<div style="margin-top: 8px; text-align: center;">
|
||||
<a style="color: #4b5fc9; font-size: 13px; text-decoration: none; cursor: pointer;" @click.prevent="openSftpConfigModal">
|
||||
<i class="fas fa-tools"></i> 配置 / 修改 SFTP
|
||||
<a style="color: #4b5fc9; font-size: 13px; text-decoration: none; cursor: pointer;" @click.prevent="openOssConfigModal">
|
||||
<i class="fas fa-tools"></i> 配置 / 修改 OSS
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1837,7 +1837,7 @@
|
||||
|
||||
<div style="margin-top: 12px; padding: 10px 12px; background: rgba(255,255,255,0.05); border-radius: 10px; font-size: 13px; color: var(--text-secondary);">
|
||||
<i class="fas fa-info-circle" style="color: #4b5fc9;"></i>
|
||||
本地存储速度快但受配额限制;SFTP 需先配置连接,切换过程中可继续查看文件列表。
|
||||
本地存储速度快但受配额限制;OSS 支持多家云服务商,切换过程中可继续查看文件列表。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1872,44 +1872,44 @@
|
||||
|
||||
<div style="padding: 10px; background: rgba(59, 130, 246, 0.15); border-left: 4px solid #3b82f6; border-radius: 6px; font-size: 13px; color: #93c5fd;">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<strong>说明:</strong> 管理员已将您的存储权限设置为"仅本地存储",您的文件存储在服务器本地,速度快但有配额限制。如需使用SFTP存储,请联系管理员修改权限设置。
|
||||
<strong>说明:</strong> 管理员已将您的存储权限设置为"仅本地存储",您的文件存储在服务器本地,速度快但有配额限制。如需使用OSS存储,请联系管理员修改权限设置。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SFTP 概览 / 配置入口 - 仅SFTP权限 -->
|
||||
<div v-if="user && !user.is_admin && storagePermission === 'sftp_only'" style="margin-bottom: 40px;">
|
||||
<!-- OSS 概览 / 配置入口 - 仅OSS权限 -->
|
||||
<div v-if="user && !user.is_admin && storagePermission === 'oss_only'" style="margin-bottom: 40px;">
|
||||
<h3 style="margin-bottom: 20px;">
|
||||
<i class="fas fa-server"></i> SFTP存储
|
||||
<i class="fas fa-cloud"></i> OSS存储
|
||||
</h3>
|
||||
<div style="background: rgba(255,255,255,0.03); padding: 20px; border-radius: 12px; border: 1px solid var(--glass-border);">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 12px;">
|
||||
<div>
|
||||
<div style="font-weight: 700; color: var(--text-primary); display: flex; gap: 8px; align-items: center;">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
仅 SFTP 模式
|
||||
仅 OSS 模式
|
||||
</div>
|
||||
<div style="color: var(--text-secondary); font-size: 13px; margin-top: 6px;">
|
||||
{{ user.has_ftp_config ? '已配置服务器,可正常使用 SFTP 存储。' : '还未配置 SFTP,请先填写连接信息。' }}
|
||||
{{ user.has_oss_config ? '已配置云服务,可正常使用 OSS 存储。' : '还未配置 OSS,请先填写配置信息。' }}
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary" @click="openSftpConfigModal()" style="border-radius: 10px;">
|
||||
<i class="fas fa-tools"></i> 配置 / 修改 SFTP
|
||||
<button class="btn btn-primary" @click="openOssConfigModal()" style="border-radius: 10px;">
|
||||
<i class="fas fa-tools"></i> 配置 / 修改 OSS
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 服务器信息 -->
|
||||
<div v-if="user.has_ftp_config" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 10px; border: 1px solid var(--glass-border);">
|
||||
<!-- OSS服务器信息 -->
|
||||
<div v-if="user.has_oss_config" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 10px; border: 1px solid var(--glass-border);">
|
||||
<div style="font-weight: 600; color: var(--text-primary); margin-bottom: 8px;">
|
||||
<i class="fas fa-server" style="color: var(--accent-1);"></i> 服务器信息
|
||||
<i class="fas fa-cloud" style="color: var(--accent-1);"></i> 云服务信息
|
||||
</div>
|
||||
<div style="color: var(--text-secondary); font-size: 14px;">
|
||||
{{ user.ftp_host }}:{{ user.ftp_port }}
|
||||
{{ user.oss_provider }} / {{ user.oss_bucket }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SFTP空间使用统计 -->
|
||||
<div v-if="user.has_ftp_config" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 10px; border: 1px solid var(--glass-border);">
|
||||
<!-- OSS空间使用统计 -->
|
||||
<div v-if="user.has_oss_config" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 10px; border: 1px solid var(--glass-border);">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
||||
<div style="font-weight: 600; color: var(--text-primary);">
|
||||
<i class="fas fa-chart-pie" style="color: #667eea;"></i> 空间使用统计
|
||||
@@ -1917,51 +1917,47 @@
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
style="padding: 4px 10px; font-size: 12px; border-radius: 6px;"
|
||||
@click="loadSftpUsage()"
|
||||
:disabled="sftpUsageLoading">
|
||||
<i :class="sftpUsageLoading ? 'fas fa-sync-alt fa-spin' : 'fas fa-sync-alt'"></i>
|
||||
{{ sftpUsageLoading ? '统计中...' : '刷新' }}
|
||||
@click="loadOssUsage()"
|
||||
:disabled="ossUsageLoading">
|
||||
<i :class="ossUsageLoading ? 'fas fa-sync-alt fa-spin' : 'fas fa-sync-alt'"></i>
|
||||
{{ ossUsageLoading ? '统计中...' : '刷新' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 加载中 -->
|
||||
<div v-if="sftpUsageLoading && !sftpUsage" style="text-align: center; padding: 20px; color: #667eea;">
|
||||
<div v-if="ossUsageLoading && !ossUsage" style="text-align: center; padding: 20px; color: #667eea;">
|
||||
<i class="fas fa-spinner fa-spin" style="font-size: 24px;"></i>
|
||||
<div style="margin-top: 8px; font-size: 13px;">正在统计 SFTP 空间使用情况...</div>
|
||||
<div style="margin-top: 8px; font-size: 13px;">正在统计 OSS 空间使用情况...</div>
|
||||
<div style="margin-top: 4px; font-size: 12px; color: var(--text-muted);">(文件较多时可能需要一些时间)</div>
|
||||
</div>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<div v-else-if="sftpUsageError" style="padding: 12px; background: rgba(239, 68, 68, 0.1); border-radius: 8px; color: #ef4444; font-size: 13px;">
|
||||
<i class="fas fa-exclamation-triangle"></i> {{ sftpUsageError }}
|
||||
<div v-else-if="ossUsageError" style="padding: 12px; background: rgba(239, 68, 68, 0.1); border-radius: 8px; color: #ef4444; font-size: 13px;">
|
||||
<i class="fas fa-exclamation-triangle"></i> {{ ossUsageError }}
|
||||
</div>
|
||||
|
||||
<!-- 统计结果 -->
|
||||
<div v-else-if="sftpUsage" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px;">
|
||||
<div v-else-if="ossUsage" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px;">
|
||||
<div style="text-align: center; padding: 12px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; color: white;">
|
||||
<div style="font-size: 20px; font-weight: 700;">{{ sftpUsage.totalSizeFormatted }}</div>
|
||||
<div style="font-size: 20px; font-weight: 700;">{{ ossUsage.totalSizeFormatted }}</div>
|
||||
<div style="font-size: 12px; opacity: 0.9; margin-top: 4px;">总使用空间</div>
|
||||
</div>
|
||||
<div style="text-align: center; padding: 12px; background: rgba(59, 130, 246, 0.1); border-radius: 10px; border: 1px solid rgba(59,130,246,0.2);">
|
||||
<div style="font-size: 20px; font-weight: 700; color: #3b82f6;">{{ sftpUsage.fileCount }}</div>
|
||||
<div style="font-size: 12px; color: var(--text-muted); margin-top: 4px;">文件数</div>
|
||||
</div>
|
||||
<div style="text-align: center; padding: 12px; background: rgba(245, 158, 11, 0.1); border-radius: 10px; border: 1px solid rgba(245,158,11,0.3);">
|
||||
<div style="font-size: 20px; font-weight: 700; color: #f59e0b;">{{ sftpUsage.dirCount }}</div>
|
||||
<div style="font-size: 12px; color: var(--text-muted); margin-top: 4px;">文件夹数</div>
|
||||
<div style="font-size: 20px; font-weight: 700; color: #3b82f6;">{{ ossUsage.fileCount }}</div>
|
||||
<div style="font-size: 12px; color: var(--text-muted); margin-top: 4px;">对象数</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 未统计提示 -->
|
||||
<div v-else style="text-align: center; padding: 16px; color: var(--text-muted); font-size: 13px;">
|
||||
<i class="fas fa-database" style="font-size: 24px; color: var(--text-muted); margin-bottom: 8px; display: block;"></i>
|
||||
点击"刷新"按钮统计 SFTP 空间使用情况
|
||||
点击"刷新"按钮统计 OSS 空间使用情况
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 10px; background: rgba(102, 126, 234, 0.1); border-radius: 10px; color: var(--text-secondary); font-size: 13px;">
|
||||
<i class="fas fa-info-circle" style="color: #4b5fc9;"></i>
|
||||
数据存储在你的 SFTP 服务器上,如需切换回本地请联系管理员调整权限。
|
||||
数据存储在云服务上,安全可靠扩展性强。如需切换回本地请联系管理员调整权限。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2701,7 +2697,7 @@
|
||||
<td style="padding: 10px; font-size: 12px; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" :title="u.email">{{ u.email }}</td>
|
||||
<td style="padding: 10px; text-align: center; font-size: 12px;">
|
||||
<span v-if="u.storage_permission === 'local_only'" style="background: #667eea; color: white; padding: 3px 8px; border-radius: 4px;">仅本地</span>
|
||||
<span v-else-if="u.storage_permission === 'sftp_only'" style="background: #6c757d; color: white; padding: 3px 8px; border-radius: 4px;">仅SFTP</span>
|
||||
<span v-else-if="u.storage_permission === 'oss_only'" style="background: #6c757d; color: white; padding: 3px 8px; border-radius: 4px;">仅OSS</span>
|
||||
<span v-else style="background: #22c55e; color: white; padding: 3px 8px; border-radius: 4px;">用户选择</span>
|
||||
</td>
|
||||
<td style="padding: 10px; text-align: center; font-size: 12px;">
|
||||
@@ -2709,7 +2705,7 @@
|
||||
<i class="fas fa-hard-drive"></i> 本地
|
||||
</span>
|
||||
<span v-else style="color: #6c757d;">
|
||||
<i class="fas fa-server"></i> SFTP
|
||||
<i class="fas fa-cloud"></i> OSS
|
||||
</span>
|
||||
</td>
|
||||
<td style="padding: 10px; text-align: center; font-size: 12px;">
|
||||
@@ -2737,7 +2733,7 @@
|
||||
<button v-else class="btn" style="background: #22c55e; color: white; font-size: 11px; padding: 5px 10px;" @click="banUser(u.id, false)">
|
||||
<i class="fas fa-check"></i> 解封
|
||||
</button>
|
||||
<button v-if="u.has_ftp_config" class="btn" style="background: #3b82f6; color: white; font-size: 11px; padding: 5px 10px;" @click="openFileInspection(u)">
|
||||
<button v-if="u.has_oss_config" class="btn" style="background: #3b82f6; color: white; font-size: 11px; padding: 5px 10px;" @click="openFileInspection(u)">
|
||||
<i class="fas fa-folder-open"></i> 文件
|
||||
</button>
|
||||
<button class="btn" style="background: #ef4444; color: white; font-size: 11px; padding: 5px 10px;" @click="deleteUser(u.id)">
|
||||
@@ -3066,11 +3062,11 @@
|
||||
<label class="form-label">存储权限</label>
|
||||
<select class="form-input" v-model="editStorageForm.storage_permission">
|
||||
<option value="local_only">仅本地存储</option>
|
||||
<option value="sftp_only">仅SFTP存储</option>
|
||||
<option value="oss_only">仅OSS存储</option>
|
||||
<option value="user_choice">用户选择</option>
|
||||
</select>
|
||||
<small style="color: var(--text-secondary); font-size: 12px; margin-top: 5px; display: block;">
|
||||
仅本地:用户只能使用本地存储 | 仅SFTP:用户只能使用SFTP | 用户选择:用户可自由切换
|
||||
仅本地:用户只能使用本地存储 | 仅OSS:用户只能使用OSS | 用户选择:用户可自由切换
|
||||
</small>
|
||||
</div>
|
||||
|
||||
@@ -3092,9 +3088,9 @@
|
||||
<div style="font-size: 13px; color: var(--text-secondary); line-height: 1.6;">
|
||||
<strong style="color: var(--text-primary);">配额说明:</strong><br>
|
||||
• 默认配额: 1GB<br>
|
||||
• 当前配额: {{ editStorageForm.local_storage_quota_value }} {{ editStorageForm.quota_unit }}
|
||||
• 当前配额: {{ editStorageForm.local_storage_quota_value }} {{ editStorageForm.quota_unit }}
|
||||
({{ editStorageForm.quota_unit === 'GB' ? (editStorageForm.local_storage_quota_value * 1024).toFixed(0) : editStorageForm.local_storage_quota_value }} MB)<br>
|
||||
• 配额仅影响本地存储,SFTP存储不受此限制
|
||||
• 配额仅影响本地存储,OSS存储不受此限制
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
564
frontend/app.js
564
frontend/app.js
@@ -48,15 +48,17 @@ createApp({
|
||||
showCaptcha: false,
|
||||
captchaUrl: '',
|
||||
|
||||
// SFTP配置表单
|
||||
ftpConfigForm: {
|
||||
ftp_host: '',
|
||||
ftp_port: 22,
|
||||
ftp_user: '',
|
||||
ftp_password: '',
|
||||
http_download_base_url: ''
|
||||
// OSS配置表单
|
||||
ossConfigForm: {
|
||||
oss_provider: 'aliyun',
|
||||
oss_region: '',
|
||||
oss_access_key_id: '',
|
||||
oss_access_key_secret: '',
|
||||
oss_bucket: '',
|
||||
oss_endpoint: ''
|
||||
},
|
||||
showFtpConfigModal: false,
|
||||
showOssConfigModal: false,
|
||||
ossConfigSaving: false, // OSS 配置保存中状态
|
||||
|
||||
// 修改密码表单
|
||||
changePasswordForm: {
|
||||
@@ -211,8 +213,8 @@ createApp({
|
||||
verifyMessage: '',
|
||||
|
||||
// 存储相关
|
||||
storageType: 'sftp', // 当前使用的存储类型
|
||||
storagePermission: 'sftp_only', // 存储权限
|
||||
storageType: 'oss', // 当前使用的存储类型
|
||||
storagePermission: 'oss_only', // 存储权限
|
||||
localQuota: 0, // 本地存储配额(字节)
|
||||
localUsed: 0, // 本地存储已使用(字节)
|
||||
|
||||
@@ -222,7 +224,7 @@ createApp({
|
||||
contextMenuX: 0,
|
||||
contextMenuY: 0,
|
||||
contextMenuFile: null,
|
||||
|
||||
|
||||
// 长按检测
|
||||
longPressTimer: null,
|
||||
longPressStartX: 0,
|
||||
@@ -242,7 +244,7 @@ createApp({
|
||||
editStorageForm: {
|
||||
userId: null,
|
||||
username: '',
|
||||
storage_permission: 'sftp_only',
|
||||
storage_permission: 'oss_only',
|
||||
local_storage_quota_value: 1, // 配额数值
|
||||
quota_unit: 'GB' // 配额单位:MB 或 GB
|
||||
},
|
||||
@@ -271,14 +273,14 @@ createApp({
|
||||
suppressStorageToast: false,
|
||||
profileInitialized: false,
|
||||
|
||||
// SFTP配置引导弹窗
|
||||
showSftpGuideModal: false,
|
||||
showSftpConfigModal: false,
|
||||
// OSS配置引导弹窗
|
||||
showOssGuideModal: false,
|
||||
showOssConfigModal: false,
|
||||
|
||||
// SFTP空间使用统计
|
||||
sftpUsage: null, // { totalSize, totalSizeFormatted, fileCount, dirCount }
|
||||
sftpUsageLoading: false,
|
||||
sftpUsageError: null,
|
||||
// OSS空间使用统计
|
||||
ossUsage: null, // { totalSize, totalSizeFormatted, fileCount, dirCount }
|
||||
ossUsageLoading: false,
|
||||
ossUsageError: null,
|
||||
|
||||
// 主题设置
|
||||
currentTheme: 'dark', // 当前生效的主题: 'dark' 或 'light'
|
||||
@@ -309,7 +311,7 @@ createApp({
|
||||
|
||||
// 存储类型显示文本
|
||||
storageTypeText() {
|
||||
return this.storageType === 'local' ? '本地存储' : 'SFTP存储';
|
||||
return this.storageType === 'local' ? '本地存储' : 'OSS存储';
|
||||
},
|
||||
|
||||
// 分享筛选+排序后的列表
|
||||
@@ -613,17 +615,17 @@ handleDragLeave(e) {
|
||||
this.startTokenRefresh(expiresIn);
|
||||
|
||||
// 直接从登录响应中获取存储信息
|
||||
this.storagePermission = this.user.storage_permission || 'sftp_only';
|
||||
this.storageType = this.user.current_storage_type || 'sftp';
|
||||
this.storagePermission = this.user.storage_permission || 'oss_only';
|
||||
this.storageType = this.user.current_storage_type || 'oss';
|
||||
this.localQuota = this.user.local_storage_quota || 0;
|
||||
this.localUsed = this.user.local_storage_used || 0;
|
||||
|
||||
console.log('[登录] 存储权限:', this.storagePermission, '存储类型:', this.storageType, 'SFTP配置:', this.user.has_ftp_config);
|
||||
console.log('[登录] 存储权限:', this.storagePermission, '存储类型:', this.storageType, 'OSS配置:', this.user.has_oss_config);
|
||||
|
||||
// 智能存储类型修正:如果当前是SFTP但未配置,且用户有本地存储权限,自动切换到本地
|
||||
if (this.storageType === 'sftp' && !this.user.has_ftp_config) {
|
||||
// 智能存储类型修正:如果当前是OSS但未配置,且用户有本地存储权限,自动切换到本地
|
||||
if (this.storageType === 'oss' && !this.user.has_oss_config) {
|
||||
if (this.storagePermission === 'local_only' || this.storagePermission === 'user_choice') {
|
||||
console.log('[登录] SFTP未配置但用户有本地存储权限,自动切换到本地存储');
|
||||
console.log('[登录] OSS未配置但用户有本地存储权限,自动切换到本地存储');
|
||||
this.storageType = 'local';
|
||||
// 异步更新到后端(不等待,避免阻塞登录流程)
|
||||
axios.post(`${this.apiBase}/api/user/switch-storage`, { storage_type: 'local' })
|
||||
@@ -646,15 +648,15 @@ handleDragLeave(e) {
|
||||
this.currentView = 'files';
|
||||
this.loadFiles('/');
|
||||
}
|
||||
// 如果仅SFTP模式,需要检查是否配置了SFTP
|
||||
else if (this.storagePermission === 'sftp_only') {
|
||||
if (this.user.has_ftp_config) {
|
||||
// 如果仅OSS模式,需要检查是否配置了OSS
|
||||
else if (this.storagePermission === 'oss_only') {
|
||||
if (this.user.has_oss_config) {
|
||||
this.currentView = 'files';
|
||||
this.loadFiles('/');
|
||||
} else {
|
||||
this.currentView = 'settings';
|
||||
alert('欢迎!请先配置您的SFTP服务器');
|
||||
this.openSftpConfigModal();
|
||||
alert('欢迎!请先配置您的OSS服务');
|
||||
this.openOssConfigModal();
|
||||
}
|
||||
} else {
|
||||
// 默认行为:跳转到文件页面
|
||||
@@ -808,44 +810,56 @@ handleDragLeave(e) {
|
||||
}
|
||||
},
|
||||
|
||||
async updateFtpConfig() {
|
||||
async updateOssConfig() {
|
||||
// 防止重复提交
|
||||
if (this.ossConfigSaving) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ossConfigSaving = true;
|
||||
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${this.apiBase}/api/user/update-ftp`,
|
||||
this.ftpConfigForm,
|
||||
`${this.apiBase}/api/user/update-oss`,
|
||||
this.ossConfigForm,
|
||||
);
|
||||
|
||||
if (response.data.success) {
|
||||
alert('SFTP配置已保存!');
|
||||
// 更新用户信息
|
||||
this.user.has_ftp_config = 1;
|
||||
|
||||
// 如果用户有 user_choice 权限,自动切换到 SFTP 存储
|
||||
if (this.storagePermission === 'user_choice' || this.storagePermission === 'sftp_only') {
|
||||
this.user.has_oss_config = 1;
|
||||
|
||||
// 如果用户有 user_choice 权限,自动切换到 OSS 存储
|
||||
if (this.storagePermission === 'user_choice' || this.storagePermission === 'oss_only') {
|
||||
try {
|
||||
const switchResponse = await axios.post(
|
||||
`${this.apiBase}/api/user/switch-storage`,
|
||||
{ storage_type: 'sftp' },
|
||||
);
|
||||
|
||||
{ storage_type: 'oss' },
|
||||
);
|
||||
|
||||
if (switchResponse.data.success) {
|
||||
this.storageType = 'sftp';
|
||||
console.log('[SFTP配置] 已自动切换到SFTP存储模式');
|
||||
this.storageType = 'oss';
|
||||
console.log('[OSS配置] 已自动切换到OSS存储模式');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[SFTP配置] 自动切换存储模式失败:', err);
|
||||
console.error('[OSS配置] 自动切换存储模式失败:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭配置弹窗
|
||||
this.showSftpConfigModal = false;
|
||||
this.showOssConfigModal = false;
|
||||
|
||||
// 刷新到文件页面
|
||||
this.currentView = 'files';
|
||||
this.loadFiles('/');
|
||||
|
||||
// 显示成功提示
|
||||
this.showToast('success', '配置成功', 'OSS存储配置已保存!');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('配置失败: ' + (error.response?.data?.message || error.message));
|
||||
console.error('OSS配置保存失败:', error);
|
||||
this.showToast('error', '配置失败', error.response?.data?.message || error.message || '请检查配置信息后重试');
|
||||
} finally {
|
||||
this.ossConfigSaving = false;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -905,7 +919,7 @@ handleDragLeave(e) {
|
||||
}
|
||||
},
|
||||
|
||||
async loadFtpConfig() {
|
||||
async loadOssConfig() {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${this.apiBase}/api/user/profile`,
|
||||
@@ -913,105 +927,20 @@ handleDragLeave(e) {
|
||||
|
||||
if (response.data.success && response.data.user) {
|
||||
const user = response.data.user;
|
||||
// 填充SFTP配置表单(密码不回显)
|
||||
this.ftpConfigForm.ftp_host = user.ftp_host || '';
|
||||
this.ftpConfigForm.ftp_port = user.ftp_port || 22;
|
||||
this.ftpConfigForm.ftp_user = user.ftp_user || '';
|
||||
this.ftpConfigForm.ftp_password = ''; // 密码不回显
|
||||
this.ftpConfigForm.http_download_base_url = user.http_download_base_url || '';
|
||||
// 填充OSS配置表单(密钥不回显)
|
||||
this.ossConfigForm.oss_provider = user.oss_provider || 'aliyun';
|
||||
this.ossConfigForm.oss_region = user.oss_region || '';
|
||||
this.ossConfigForm.oss_access_key_id = user.oss_access_key_id || '';
|
||||
this.ossConfigForm.oss_access_key_secret = ''; // 密钥不回显
|
||||
this.ossConfigForm.oss_bucket = user.oss_bucket || '';
|
||||
this.ossConfigForm.oss_endpoint = user.oss_endpoint || '';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载SFTP配置失败:', error);
|
||||
console.error('加载OSS配置失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 处理配置文件上传
|
||||
handleConfigFileUpload(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
this.processConfigFile(file);
|
||||
|
||||
// 清空文件选择,允许重复选择同一文件
|
||||
event.target.value = '';
|
||||
},
|
||||
|
||||
// 处理配置文件拖拽
|
||||
handleConfigFileDrop(event) {
|
||||
const file = event.dataTransfer.files[0];
|
||||
if (!file) return;
|
||||
|
||||
// 检查文件扩展名
|
||||
if (!file.name.toLowerCase().endsWith('.inf')) {
|
||||
this.showToast('error', '错误', '只支持 .inf 格式的配置文件');
|
||||
return;
|
||||
}
|
||||
|
||||
this.processConfigFile(file);
|
||||
|
||||
// 恢复背景色
|
||||
event.currentTarget.style.background = '#f8f9ff';
|
||||
},
|
||||
|
||||
// 处理配置文件
|
||||
async processConfigFile(file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
try {
|
||||
const content = e.target.result;
|
||||
const config = this.parseConfigFile(content);
|
||||
|
||||
if (config) {
|
||||
// 填充表单
|
||||
this.ftpConfigForm.ftp_host = config.ip || '';
|
||||
this.ftpConfigForm.ftp_port = config.port || 22;
|
||||
this.ftpConfigForm.ftp_user = config.id || '';
|
||||
this.ftpConfigForm.ftp_password = config.pw || '';
|
||||
this.ftpConfigForm.http_download_base_url = config.arr || '';
|
||||
|
||||
// 提示用户配置已导入,需要确认后保存
|
||||
this.showToast('success', '成功', '配置文件已导入!请检查并确认信息后点击"保存配置"按钮');
|
||||
} else {
|
||||
this.showToast('error', '错误', '配置文件格式不正确,请检查文件内容');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析配置文件失败:', error);
|
||||
this.showToast('error', '错误', '解析配置文件失败: ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
},
|
||||
|
||||
// 解析INI格式的配置文件
|
||||
parseConfigFile(content) {
|
||||
const lines = content.split('\n');
|
||||
const config = {};
|
||||
|
||||
for (let line of lines) {
|
||||
line = line.trim();
|
||||
|
||||
// 跳过空行和注释
|
||||
if (!line || line.startsWith('#') || line.startsWith(';') || line.startsWith('[')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 解析 key=value 格式
|
||||
const equalsIndex = line.indexOf('=');
|
||||
if (equalsIndex > 0) {
|
||||
const key = line.substring(0, equalsIndex).trim();
|
||||
const value = line.substring(equalsIndex + 1).trim();
|
||||
config[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证必需字段
|
||||
if (config.ip && config.id && config.pw && config.port) {
|
||||
return config;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
// 上传工具配置引导已移除(OSS 不需要配置文件导入)
|
||||
|
||||
async updateUsername() {
|
||||
if (!this.usernameForm.newUsername || this.usernameForm.newUsername.length < 3) {
|
||||
@@ -1107,8 +1036,8 @@ handleDragLeave(e) {
|
||||
localStorage.setItem('user', JSON.stringify(this.user));
|
||||
|
||||
// 从最新的用户信息初始化存储相关字段
|
||||
this.storagePermission = this.user.storage_permission || 'sftp_only';
|
||||
this.storageType = this.user.current_storage_type || 'sftp';
|
||||
this.storagePermission = this.user.storage_permission || 'oss_only';
|
||||
this.storageType = this.user.current_storage_type || 'oss';
|
||||
this.localQuota = this.user.local_storage_quota || 0;
|
||||
this.localUsed = this.user.local_storage_used || 0;
|
||||
|
||||
@@ -1129,7 +1058,7 @@ handleDragLeave(e) {
|
||||
targetView = savedView;
|
||||
} else if (this.user.is_admin) {
|
||||
targetView = 'admin';
|
||||
} else if (this.storagePermission === 'sftp_only' && !this.user.has_ftp_config) {
|
||||
} else if (this.storagePermission === 'oss_only' && !this.user.has_oss_config) {
|
||||
targetView = 'settings';
|
||||
} else {
|
||||
targetView = 'files';
|
||||
@@ -1311,17 +1240,42 @@ handleDragLeave(e) {
|
||||
downloadFile(file) {
|
||||
console.log("[DEBUG] 下载文件:", file);
|
||||
|
||||
// SFTP存储且有HTTP直链,新窗口打开直接下载(避免Mixed Content问题)
|
||||
if (file.httpDownloadUrl) {
|
||||
window.open(file.httpDownloadUrl, '_blank');
|
||||
return;
|
||||
}
|
||||
// 构建文件路径
|
||||
const filePath = this.currentPath === '/' ? `/${file.name}` : `${this.currentPath}/${file.name}`;
|
||||
|
||||
// 本地存储,使用隐藏链接触发下载
|
||||
const url = `${this.apiBase}/api/files/download?path=${encodeURIComponent(this.currentPath === '/' ? `/${file.name}` : `${this.currentPath}/${file.name}`)}`;
|
||||
// OSS 模式:使用签名 URL 直连下载(不经过后端)
|
||||
if (this.storageType === 'oss' && this.user?.has_oss_config) {
|
||||
this.downloadFromOSS(filePath);
|
||||
} else {
|
||||
// 本地存储模式:通过后端下载
|
||||
this.downloadFromLocal(filePath);
|
||||
}
|
||||
},
|
||||
|
||||
// OSS 直连下载
|
||||
async downloadFromOSS(filePath) {
|
||||
try {
|
||||
// 获取签名 URL
|
||||
const { data } = await axios.get(`${this.apiBase}/api/files/download-url`, {
|
||||
params: { path: filePath }
|
||||
});
|
||||
|
||||
if (data.success) {
|
||||
// 直连 OSS 下载
|
||||
window.open(data.downloadUrl, '_blank');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取下载链接失败:', error);
|
||||
this.showToast('error', '错误', '获取下载链接失败');
|
||||
}
|
||||
},
|
||||
|
||||
// 本地存储下载
|
||||
downloadFromLocal(filePath) {
|
||||
const url = `${this.apiBase}/api/files/download?path=${encodeURIComponent(filePath)}`;
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.setAttribute('download', file.name);
|
||||
link.setAttribute('download', '');
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
@@ -1385,6 +1339,7 @@ handleDragLeave(e) {
|
||||
this.showCreateFolderModal = false;
|
||||
this.createFolderForm.folderName = '';
|
||||
await this.loadFiles(this.currentPath); // 刷新文件列表
|
||||
await this.refreshStorageUsage(); // 刷新空间统计(OSS会增加空对象)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[创建文件夹失败]', error);
|
||||
@@ -1540,18 +1495,26 @@ handleDragLeave(e) {
|
||||
|
||||
// ===== 媒体预览功能 =====
|
||||
|
||||
// 获取媒体文件URL
|
||||
getMediaUrl(file) {
|
||||
// 获取媒体文件URL(OSS直连或后端代理)
|
||||
async getMediaUrl(file) {
|
||||
const filePath = this.currentPath === '/'
|
||||
? `/${file.name}`
|
||||
: `${this.currentPath}/${file.name}`;
|
||||
|
||||
// SFTP存储且配置了HTTP下载URL,使用HTTP直接访问;否则使用API下载
|
||||
if (file.httpDownloadUrl) {
|
||||
return file.httpDownloadUrl;
|
||||
// OSS 模式:返回签名 URL(用于媒体预览)
|
||||
if (this.storageType === 'oss' && this.user?.has_oss_config) {
|
||||
try {
|
||||
const { data } = await axios.get(`${this.apiBase}/api/files/download-url`, {
|
||||
params: { path: filePath }
|
||||
});
|
||||
return data.success ? data.downloadUrl : null;
|
||||
} catch (error) {
|
||||
console.error('获取媒体URL失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 本地存储或未配置HTTP URL,使用API下载(同域 Cookie 验证)
|
||||
// 本地存储模式:通过后端 API
|
||||
return `${this.apiBase}/api/files/download?path=${encodeURIComponent(filePath)}`;
|
||||
},
|
||||
|
||||
@@ -1634,7 +1597,9 @@ handleDragLeave(e) {
|
||||
|
||||
if (response.data.success) {
|
||||
this.showToast('success', '成功', '文件已删除');
|
||||
this.loadFiles(this.currentPath);
|
||||
// 刷新文件列表和空间统计
|
||||
await this.loadFiles(this.currentPath);
|
||||
await this.refreshStorageUsage();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
@@ -1764,7 +1729,7 @@ handleDragLeave(e) {
|
||||
},
|
||||
|
||||
async uploadFile(file) {
|
||||
// 文件大小限制预检查(在上传前检查,避免用户等待上传完才发现超限)
|
||||
// 文件大小限制预检查
|
||||
if (file.size > this.maxUploadSize) {
|
||||
const fileSizeMB = Math.round(file.size / (1024 * 1024));
|
||||
const maxSizeMB = Math.round(this.maxUploadSize / (1024 * 1024));
|
||||
@@ -1776,99 +1741,129 @@ handleDragLeave(e) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 本地存储配额预检查
|
||||
if (this.storageType === 'local') {
|
||||
const estimatedUsage = this.localUsed + file.size;
|
||||
if (estimatedUsage > this.localQuota) {
|
||||
this.showToast(
|
||||
'error',
|
||||
'配额不足',
|
||||
`文件大小 ${this.formatBytes(file.size)},剩余配额 ${this.formatBytes(this.localQuota - this.localUsed)},无法上传`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果使用率将超过90%,给出警告
|
||||
const willExceed90 = (estimatedUsage / this.localQuota) > 0.9;
|
||||
if (willExceed90) {
|
||||
const confirmed = confirm(
|
||||
`警告:上传此文件后将使用 ${Math.round((estimatedUsage / this.localQuota) * 100)}% 的配额。是否继续?`
|
||||
);
|
||||
if (!confirmed) return;
|
||||
}
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('path', this.currentPath);
|
||||
// 设置上传状态
|
||||
this.uploadingFileName = file.name;
|
||||
this.uploadProgress = 0;
|
||||
this.uploadedBytes = 0;
|
||||
this.totalBytes = file.size;
|
||||
|
||||
try {
|
||||
// 设置上传文件名和进度
|
||||
this.uploadingFileName = file.name;
|
||||
this.uploadProgress = 0;
|
||||
this.uploadedBytes = 0;
|
||||
this.totalBytes = 0;
|
||||
|
||||
const response = await axios.post(`${this.apiBase}/api/upload`, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
},
|
||||
timeout: 30 * 60 * 1000, // 30分钟超时,支持大文件上传
|
||||
onUploadProgress: (progressEvent) => {
|
||||
this.uploadProgress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
|
||||
this.uploadedBytes = progressEvent.loaded;
|
||||
this.totalBytes = progressEvent.total;
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
// 显示成功提示
|
||||
this.showToast('success', '上传成功', `文件 ${file.name} 已上传`);
|
||||
|
||||
// 重置上传进度
|
||||
this.uploadProgress = 0;
|
||||
this.uploadedBytes = 0;
|
||||
this.totalBytes = 0;
|
||||
this.uploadingFileName = '';
|
||||
|
||||
// 自动刷新文件列表
|
||||
await this.loadFiles(this.currentPath);
|
||||
if (this.storageType === 'oss' && this.user?.has_oss_config) {
|
||||
// ===== OSS 直连上传(不经过后端) =====
|
||||
await this.uploadToOSSDirect(file);
|
||||
} else {
|
||||
// ===== 本地存储上传(经过后端) =====
|
||||
await this.uploadToLocal(file);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error);
|
||||
|
||||
// 重置上传进度
|
||||
this.uploadProgress = 0;
|
||||
this.uploadedBytes = 0;
|
||||
this.totalBytes = 0;
|
||||
this.uploadedBytes = 0;
|
||||
this.totalBytes = 0;
|
||||
this.uploadingFileName = '';
|
||||
|
||||
// 处理文件大小超限错误
|
||||
if (error.response?.status === 413) {
|
||||
const errorData = error.response.data;
|
||||
this.showToast('error', '上传失败', error.message || '上传失败,请重试');
|
||||
}
|
||||
},
|
||||
|
||||
// 判断响应是JSON还是HTML(Nginx返回HTML,Backend返回JSON)
|
||||
if (typeof errorData === 'object' && errorData.maxSize && errorData.fileSize) {
|
||||
// Backend返回的JSON响应
|
||||
const maxSizeMB = Math.round(errorData.maxSize / (1024 * 1024));
|
||||
const fileSizeMB = Math.round(errorData.fileSize / (1024 * 1024));
|
||||
this.showToast(
|
||||
'error',
|
||||
'文件超过上传限制',
|
||||
`文件大小 ${fileSizeMB}MB 超过限制 ${maxSizeMB}MB`
|
||||
);
|
||||
} else {
|
||||
// Nginx返回的HTML响应,显示通用消息
|
||||
const fileSizeMB = Math.round(file.size / (1024 * 1024));
|
||||
this.showToast(
|
||||
'error',
|
||||
'文件超过上传限制',
|
||||
`文件大小 ${fileSizeMB}MB 超过系统限制,请联系管理员`
|
||||
);
|
||||
// OSS 直连上传
|
||||
async uploadToOSSDirect(file) {
|
||||
try {
|
||||
// 1. 获取签名 URL
|
||||
const { data: signData } = await axios.get(`${this.apiBase}/api/files/upload-signature`, {
|
||||
params: {
|
||||
filename: file.name,
|
||||
contentType: file.type || 'application/octet-stream'
|
||||
}
|
||||
} else {
|
||||
this.showToast('error', '上传失败', error.response?.data?.message || error.message);
|
||||
});
|
||||
|
||||
if (!signData.success) {
|
||||
throw new Error(signData.message || '获取上传签名失败');
|
||||
}
|
||||
|
||||
// 2. 直连 OSS 上传(不经过后端!)
|
||||
await axios.put(signData.uploadUrl, file, {
|
||||
headers: {
|
||||
'Content-Type': file.type || 'application/octet-stream'
|
||||
},
|
||||
onUploadProgress: (progressEvent) => {
|
||||
this.uploadProgress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
|
||||
this.uploadedBytes = progressEvent.loaded;
|
||||
this.totalBytes = progressEvent.total;
|
||||
},
|
||||
timeout: 30 * 60 * 1000 // 30分钟超时
|
||||
});
|
||||
|
||||
// 3. 通知后端上传完成
|
||||
await axios.post(`${this.apiBase}/api/files/upload-complete`, {
|
||||
objectKey: signData.objectKey,
|
||||
size: file.size,
|
||||
path: this.currentPath
|
||||
});
|
||||
|
||||
// 4. 显示成功提示
|
||||
this.showToast('success', '上传成功', `文件 ${file.name} 已上传到 OSS`);
|
||||
|
||||
// 5. 重置上传进度
|
||||
this.uploadProgress = 0;
|
||||
this.uploadedBytes = 0;
|
||||
this.totalBytes = 0;
|
||||
this.uploadingFileName = '';
|
||||
|
||||
// 6. 刷新文件列表和空间统计
|
||||
await this.loadFiles(this.currentPath);
|
||||
await this.refreshStorageUsage();
|
||||
|
||||
} catch (error) {
|
||||
// 处理 CORS 错误
|
||||
if (error.message?.includes('CORS') || error.message?.includes('Cross-Origin')) {
|
||||
throw new Error('OSS 跨域配置错误,请联系管理员检查 Bucket CORS 设置');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 本地存储上传(经过后端)
|
||||
async uploadToLocal(file) {
|
||||
// 本地存储配额预检查
|
||||
const estimatedUsage = this.localUsed + file.size;
|
||||
if (estimatedUsage > this.localQuota) {
|
||||
this.showToast(
|
||||
'error',
|
||||
'配额不足',
|
||||
`文件大小 ${this.formatBytes(file.size)},剩余配额 ${this.formatBytes(this.localQuota - this.localUsed)}`
|
||||
);
|
||||
this.uploadProgress = 0;
|
||||
this.uploadedBytes = 0;
|
||||
this.totalBytes = 0;
|
||||
this.uploadingFileName = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('path', this.currentPath);
|
||||
|
||||
const response = await axios.post(`${this.apiBase}/api/upload`, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
timeout: 30 * 60 * 1000,
|
||||
onUploadProgress: (progressEvent) => {
|
||||
this.uploadProgress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
|
||||
this.uploadedBytes = progressEvent.loaded;
|
||||
this.totalBytes = progressEvent.total;
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
this.showToast('success', '上传成功', `文件 ${file.name} 已上传`);
|
||||
this.uploadProgress = 0;
|
||||
this.uploadedBytes = 0;
|
||||
this.totalBytes = 0;
|
||||
this.uploadingFileName = '';
|
||||
await this.loadFiles(this.currentPath);
|
||||
await this.refreshStorageUsage();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2266,14 +2261,14 @@ handleDragLeave(e) {
|
||||
|
||||
if (response.data.success && response.data.user) {
|
||||
const user = response.data.user;
|
||||
// 同步用户信息(含 has_ftp_config)
|
||||
// 同步用户信息(含 has_oss_config)
|
||||
this.user = { ...(this.user || {}), ...user };
|
||||
|
||||
// 检测存储配置是否被管理员更改
|
||||
const oldStorageType = this.storageType;
|
||||
const oldStoragePermission = this.storagePermission;
|
||||
const newStorageType = user.current_storage_type || 'sftp';
|
||||
const newStoragePermission = user.storage_permission || 'sftp_only';
|
||||
const newStorageType = user.current_storage_type || 'oss';
|
||||
const newStoragePermission = user.storage_permission || 'oss_only';
|
||||
|
||||
// 更新本地数据
|
||||
this.localQuota = user.local_storage_quota || 0;
|
||||
@@ -2293,7 +2288,7 @@ handleDragLeave(e) {
|
||||
console.log('[存储配置更新] 旧权限:', oldStoragePermission, '新权限:', newStoragePermission);
|
||||
|
||||
if (!this.suppressStorageToast) {
|
||||
this.showToast('info', '存储配置已更新', `管理员已将您的存储方式更改为${newStorageType === 'local' ? '本地存储' : 'SFTP存储'}`);
|
||||
this.showToast('info', '存储配置已更新', `管理员已将您的存储方式更改为${newStorageType === 'local' ? '本地存储' : 'OSS存储'}`);
|
||||
} else {
|
||||
this.suppressStorageToast = false;
|
||||
}
|
||||
@@ -2309,30 +2304,41 @@ handleDragLeave(e) {
|
||||
}
|
||||
},
|
||||
|
||||
// 加载SFTP空间使用统计
|
||||
async loadSftpUsage() {
|
||||
// 仅在用户已配置SFTP时才加载
|
||||
if (!this.user?.has_ftp_config) {
|
||||
this.sftpUsage = null;
|
||||
// 加载OSS空间使用统计
|
||||
async loadOssUsage() {
|
||||
// 仅在用户已配置OSS时才加载
|
||||
if (!this.user?.has_oss_config) {
|
||||
this.ossUsage = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.sftpUsageLoading = true;
|
||||
this.sftpUsageError = null;
|
||||
this.ossUsageLoading = true;
|
||||
this.ossUsageError = null;
|
||||
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${this.apiBase}/api/user/sftp-usage`,
|
||||
`${this.apiBase}/api/user/oss-usage`,
|
||||
);
|
||||
|
||||
if (response.data.success) {
|
||||
this.sftpUsage = response.data.usage;
|
||||
this.ossUsage = response.data.usage;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取SFTP空间使用情况失败:', error);
|
||||
this.sftpUsageError = error.response?.data?.message || '获取失败';
|
||||
console.error('获取OSS空间使用情况失败:', error);
|
||||
this.ossUsageError = error.response?.data?.message || '获取失败';
|
||||
} finally {
|
||||
this.sftpUsageLoading = false;
|
||||
this.ossUsageLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 刷新存储空间使用统计(根据当前存储类型)
|
||||
async refreshStorageUsage() {
|
||||
if (this.storageType === 'oss' && this.user?.has_oss_config) {
|
||||
// 刷新 OSS 空间统计
|
||||
await this.loadOssUsage();
|
||||
} else if (this.storageType === 'local') {
|
||||
// 刷新本地存储统计(通过重新获取用户信息)
|
||||
await this.loadUserProfile();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2368,9 +2374,9 @@ handleDragLeave(e) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 切到SFTP但还未配置,引导弹窗
|
||||
if (type === 'sftp' && (!this.user?.has_ftp_config)) {
|
||||
this.showSftpGuideModal = true;
|
||||
// 切到OSS但还未配置,引导弹窗
|
||||
if (type === 'oss' && (!this.user?.has_oss_config)) {
|
||||
this.showOssGuideModal = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2387,7 +2393,7 @@ handleDragLeave(e) {
|
||||
this.storageType = type;
|
||||
// 用户主动切换后,下一次配置同步不提示管理员修改
|
||||
this.suppressStorageToast = true;
|
||||
this.showToast('success', '成功', `已切换到${type === 'local' ? '本地存储' : 'SFTP存储'}`);
|
||||
this.showToast('success', '成功', `已切换到${type === 'local' ? '本地存储' : 'OSS存储'}`);
|
||||
|
||||
// 重新加载文件列表
|
||||
if (this.currentView === 'files') {
|
||||
@@ -2403,33 +2409,33 @@ handleDragLeave(e) {
|
||||
}
|
||||
},
|
||||
|
||||
ensureSftpConfigSection() {
|
||||
this.openSftpConfigModal();
|
||||
ensureOssConfigSection() {
|
||||
this.openOssConfigModal();
|
||||
},
|
||||
|
||||
openSftpGuideModal() {
|
||||
this.showSftpGuideModal = true;
|
||||
openOssGuideModal() {
|
||||
this.showOssGuideModal = true;
|
||||
},
|
||||
|
||||
closeSftpGuideModal() {
|
||||
this.showSftpGuideModal = false;
|
||||
closeOssGuideModal() {
|
||||
this.showOssGuideModal = false;
|
||||
},
|
||||
|
||||
proceedSftpGuide() {
|
||||
this.showSftpGuideModal = false;
|
||||
this.ensureSftpConfigSection();
|
||||
proceedOssGuide() {
|
||||
this.showOssGuideModal = false;
|
||||
this.ensureOssConfigSection();
|
||||
},
|
||||
|
||||
openSftpConfigModal() {
|
||||
this.showSftpGuideModal = false;
|
||||
this.showSftpConfigModal = true;
|
||||
openOssConfigModal() {
|
||||
this.showOssGuideModal = false;
|
||||
this.showOssConfigModal = true;
|
||||
if (this.user && !this.user.is_admin) {
|
||||
this.loadFtpConfig();
|
||||
this.loadOssConfig();
|
||||
}
|
||||
},
|
||||
|
||||
closeSftpConfigModal() {
|
||||
this.showSftpConfigModal = false;
|
||||
closeOssConfigModal() {
|
||||
this.showOssConfigModal = false;
|
||||
},
|
||||
|
||||
// 检查视图权限
|
||||
@@ -2487,7 +2493,7 @@ handleDragLeave(e) {
|
||||
openEditStorageModal(user) {
|
||||
this.editStorageForm.userId = user.id;
|
||||
this.editStorageForm.username = user.username;
|
||||
this.editStorageForm.storage_permission = user.storage_permission || 'sftp_only';
|
||||
this.editStorageForm.storage_permission = user.storage_permission || 'oss_only';
|
||||
|
||||
// 智能识别配额单位
|
||||
const quotaBytes = user.local_storage_quota || 1073741824;
|
||||
@@ -3082,8 +3088,8 @@ handleDragLeave(e) {
|
||||
this.loadSystemSettings();
|
||||
this.loadServerStorageStats();
|
||||
} else if (newView === 'settings' && this.user && !this.user.is_admin) {
|
||||
// 普通用户进入设置页面时加载SFTP配置
|
||||
this.loadFtpConfig();
|
||||
// 普通用户进入设置页面时加载OSS配置
|
||||
this.loadOssConfig();
|
||||
}
|
||||
|
||||
// 记住最后停留的视图(需合法且已登录)
|
||||
|
||||
@@ -564,7 +564,7 @@
|
||||
现代化<br><span class="gradient-text">云存储平台</span>
|
||||
</h1>
|
||||
<p class="hero-desc">
|
||||
简单、安全、高效的文件管理解决方案。支持 SFTP 远程连接和服务器本地存储双模式,随时随地管理和分享你的文件。
|
||||
简单、安全、高效的文件管理解决方案。支持 OSS 云存储和服务器本地存储双模式,随时随地管理和分享你的文件。
|
||||
</p>
|
||||
<div class="hero-buttons">
|
||||
<a href="app.html?action=register" class="btn btn-primary btn-large">
|
||||
@@ -596,10 +596,10 @@
|
||||
<div class="features-grid">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-server"></i>
|
||||
<i class="fas fa-cloud"></i>
|
||||
</div>
|
||||
<h3 class="feature-title">SFTP 连接</h3>
|
||||
<p class="feature-desc">连接你自己的服务器,数据完全自主掌控</p>
|
||||
<h3 class="feature-title">OSS 云存储</h3>
|
||||
<p class="feature-desc">支持阿里云、腾讯云、AWS S3,数据完全自主掌控</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
|
||||
@@ -336,7 +336,9 @@
|
||||
if (data.success && data.theme === 'light') {
|
||||
document.body.classList.add('light-theme');
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
console.warn('[主题加载] 失败,使用默认主题:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取URL参数
|
||||
|
||||
@@ -918,42 +918,47 @@
|
||||
this.viewingFile = null;
|
||||
},
|
||||
|
||||
downloadFile(file) {
|
||||
async downloadFile(file) {
|
||||
console.log("[分享下载] 文件:", file);
|
||||
|
||||
// 记录下载次数(异步,不等待)
|
||||
axios.post(`${this.apiBase}/api/share/${this.shareCode}/download`)
|
||||
.catch(err => console.error('记录下载次数失败:', err));
|
||||
|
||||
if (file.httpDownloadUrl) {
|
||||
// 如果配置了HTTP下载URL,新窗口打开直接下载(避免Mixed Content问题)
|
||||
console.log("[分享下载] 使用HTTP下载:", file.httpDownloadUrl);
|
||||
window.open(file.httpDownloadUrl, '_blank');
|
||||
return;
|
||||
// 构建文件路径
|
||||
let filePath;
|
||||
if (this.shareInfo.share_type === 'file') {
|
||||
// 单文件分享,使用 share_path
|
||||
filePath = this.shareInfo.share_path;
|
||||
} else {
|
||||
// 如果没有配置HTTP URL,通过后端SFTP下载
|
||||
console.log("[分享下载] 使用SFTP下载");
|
||||
// 目录分享,组合路径
|
||||
const basePath = this.shareInfo.share_path;
|
||||
filePath = basePath === '/' ? `/${file.name}` : `${basePath}/${file.name}`;
|
||||
}
|
||||
|
||||
// 构建文件路径
|
||||
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)}`;
|
||||
|
||||
// 如果有密码,附加密码参数
|
||||
try {
|
||||
// 获取下载 URL(OSS 直连或后端代理)
|
||||
const params = { path: filePath };
|
||||
if (this.password) {
|
||||
downloadUrl += `&password=${encodeURIComponent(this.password)}`;
|
||||
params.password = this.password;
|
||||
}
|
||||
|
||||
this.triggerDownload(downloadUrl, file.name);
|
||||
const { data } = await axios.get(`${this.apiBase}/api/share/${this.shareCode}/download-url`, { params });
|
||||
|
||||
if (data.success) {
|
||||
// 记录下载次数(异步,不等待)
|
||||
axios.post(`${this.apiBase}/api/share/${this.shareCode}/download`)
|
||||
.catch(err => console.error('记录下载次数失败:', err));
|
||||
|
||||
if (data.direct) {
|
||||
// OSS 直连下载:新窗口打开
|
||||
console.log("[分享下载] OSS 直连下载");
|
||||
window.open(data.downloadUrl, '_blank');
|
||||
} else {
|
||||
// 本地存储:通过后端下载
|
||||
console.log("[分享下载] 后端代理下载");
|
||||
this.triggerDownload(data.downloadUrl, file.name);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[分享下载] 获取下载链接失败:', error);
|
||||
alert('获取下载链接失败: ' + (error.response?.data?.message || error.message));
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -210,7 +210,9 @@
|
||||
if (data.success && data.theme === 'light') {
|
||||
document.body.classList.add('light-theme');
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
console.warn('[主题加载] 失败,使用默认主题:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取URL参数
|
||||
|
||||
Reference in New Issue
Block a user