feat: apply UI/storage/share optimizations and quota improvements
This commit is contained in:
@@ -4,9 +4,9 @@
|
||||
<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">
|
||||
<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>
|
||||
/* 防止 Vue 初始化前显示原始模板 */
|
||||
[v-cloak] { display: none !important; }
|
||||
@@ -626,14 +626,359 @@
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* ===== Enterprise Netdisk Share UI Rebuild (Classic Cloud Disk) ===== */
|
||||
body.enterprise-netdisk-share,
|
||||
body.enterprise-netdisk-share.light-theme,
|
||||
body.enterprise-netdisk-share:not(.light-theme) {
|
||||
--bg-primary: #edf2fb;
|
||||
--bg-secondary: #fbfdff;
|
||||
--bg-card: #f8fbff;
|
||||
--bg-card-hover: #eef3fb;
|
||||
--glass-border: #d2ddec;
|
||||
--glass-border-hover: #bccce2;
|
||||
--text-primary: #1f2937;
|
||||
--text-secondary: #5b6472;
|
||||
--text-muted: #8b95a7;
|
||||
--accent-1: #2563eb;
|
||||
--accent-2: #1d4ed8;
|
||||
--accent-3: #1e40af;
|
||||
--glow: rgba(37, 99, 235, 0.14);
|
||||
--danger: #dc2626;
|
||||
--success: #16a34a;
|
||||
--warning: #d97706;
|
||||
background: linear-gradient(180deg, #eef3fb 0%, #f8fafe 42%, #edf2fb 100%) !important;
|
||||
color: var(--text-primary) !important;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', Roboto, sans-serif;
|
||||
line-height: 1.5;
|
||||
padding: 14px 8px;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share::before {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .container {
|
||||
max-width: 1140px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .card {
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 10px;
|
||||
background: var(--bg-card);
|
||||
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.06);
|
||||
backdrop-filter: none;
|
||||
padding: 18px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .title {
|
||||
margin-bottom: 14px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
color: var(--text-primary);
|
||||
font-size: 21px;
|
||||
background: none;
|
||||
-webkit-text-fill-color: currentColor;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .title i {
|
||||
color: var(--accent-1);
|
||||
-webkit-text-fill-color: currentColor;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .share-back-btn {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .share-meta-bar {
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-card-hover);
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .share-expire-time {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .share-expire-time.expiring {
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .share-expire-time.expired {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .share-expire-time.valid {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .btn {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
padding: 9px 14px;
|
||||
box-shadow: none;
|
||||
transition: all .2s ease;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .btn-primary {
|
||||
background: var(--accent-1);
|
||||
border-color: var(--accent-1);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .btn-primary:hover {
|
||||
background: var(--accent-2);
|
||||
border-color: var(--accent-2);
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .btn-secondary {
|
||||
background: var(--bg-secondary);
|
||||
border-color: var(--glass-border);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .btn-secondary:hover {
|
||||
background: var(--bg-card-hover);
|
||||
border-color: var(--glass-border-hover);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .form-label {
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .form-input {
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
padding: 10px 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .form-input:focus {
|
||||
border-color: var(--accent-1);
|
||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.14);
|
||||
outline: none;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .alert {
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .alert-error {
|
||||
background: #fef2f2;
|
||||
border-color: #fca5a5;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .view-controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .loading {
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-card-hover);
|
||||
color: var(--text-secondary);
|
||||
padding: 28px 14px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .spinner {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 3px solid rgba(148, 163, 184, 0.25);
|
||||
border-top: 3px solid var(--accent-1);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .file-grid {
|
||||
gap: 10px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .file-grid-item {
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-secondary);
|
||||
min-height: 170px;
|
||||
padding: 12px;
|
||||
transition: all .2s ease;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .file-grid-item:hover {
|
||||
border-color: #93c5fd;
|
||||
background: #f8fbff;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .file-grid-icon {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .file-grid-name {
|
||||
font-size: 13px;
|
||||
line-height: 1.35;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .file-grid-size {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin: 6px 0 10px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .file-list {
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .file-item {
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
transition: all .2s ease;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .file-item:hover {
|
||||
background: var(--bg-card-hover);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .file-icon {
|
||||
width: 26px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .single-file-container {
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-card-hover);
|
||||
padding: 14px;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .single-file-card {
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-secondary);
|
||||
box-shadow: none;
|
||||
padding: 20px;
|
||||
max-width: 460px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .single-file-name {
|
||||
color: var(--text-primary);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .single-file-size {
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .single-file-download {
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .share-not-found {
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-card-hover);
|
||||
padding: 40px 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .share-not-found-title {
|
||||
margin-bottom: 10px;
|
||||
font-size: 22px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .share-not-found-message {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share ::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share ::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share ::-webkit-scrollbar-thumb {
|
||||
background: rgba(148, 163, 184, 0.45);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
body.enterprise-netdisk-share .card {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .view-controls {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .view-controls .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .file-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .file-grid-item {
|
||||
min-height: 152px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk-share .single-file-card {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<body class="enterprise-netdisk-share">
|
||||
<div id="app" v-cloak>
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<div class="title">
|
||||
<!-- 返回按钮:仅在查看单个文件详情且不是单文件分享时显示 -->
|
||||
<button v-if="viewingFile && shareInfo.share_type !== 'file'" class="btn btn-secondary" @click="backToList" style="margin-right: 10px;">
|
||||
<button v-if="viewingFile && shareInfo.share_type !== 'file'" class="btn btn-secondary share-back-btn" @click="backToList">
|
||||
<i class="fas fa-arrow-left"></i> 返回列表
|
||||
</button>
|
||||
<i class="fas fa-cloud"></i>
|
||||
@@ -671,11 +1016,11 @@
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<div v-else-if="verified">
|
||||
<p style="color: var(--text-secondary); margin-bottom: 20px;">
|
||||
<p class="share-meta-bar">
|
||||
分享者: <strong style="color: var(--text-primary);">{{ shareInfo.username }}</strong> |
|
||||
创建时间: {{ formatDate(shareInfo.created_at) }}
|
||||
<span v-if="shareInfo.expires_at"> | 到期时间: <strong :style="{color: isExpiringSoon(shareInfo.expires_at) ? '#f59e0b' : isExpired(shareInfo.expires_at) ? '#ef4444' : '#22c55e'}">{{ formatExpireTime(shareInfo.expires_at) }}</strong></span>
|
||||
<span v-else> | 有效期: <strong style="color: #22c55e;">永久有效</strong></span>
|
||||
<span v-if="shareInfo.expires_at"> | 到期时间: <strong class="share-expire-time" :class="{ expiring: isExpiringSoon(shareInfo.expires_at), expired: isExpired(shareInfo.expires_at) }">{{ formatExpireTime(shareInfo.expires_at) }}</strong></span>
|
||||
<span v-else> | 有效期: <strong class="share-expire-time valid">永久有效</strong></span>
|
||||
</p>
|
||||
|
||||
<!-- 视图切换按钮 (多文件时才显示) -->
|
||||
@@ -777,7 +1122,21 @@
|
||||
methods: {
|
||||
async init() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
this.shareCode = urlParams.get('code');
|
||||
let shareCode = (urlParams.get('code') || '').trim();
|
||||
|
||||
// 兼容 /s/{code} 直链(无 query 参数)
|
||||
if (!shareCode) {
|
||||
const match = window.location.pathname.match(/^\/s\/([^/?#]+)/i);
|
||||
if (match && match[1]) {
|
||||
try {
|
||||
shareCode = decodeURIComponent(match[1]).trim();
|
||||
} catch (_) {
|
||||
shareCode = match[1].trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.shareCode = shareCode;
|
||||
|
||||
if (!this.shareCode) {
|
||||
this.errorMessage = '无效的分享链接';
|
||||
@@ -906,21 +1265,20 @@
|
||||
|
||||
try {
|
||||
// 获取下载 URL(OSS 直连或后端代理)
|
||||
const params = { path: filePath };
|
||||
if (this.password) {
|
||||
params.password = this.password;
|
||||
}
|
||||
|
||||
const { data } = await axios.get(`${this.apiBase}/api/share/${this.shareCode}/download-url`, { params });
|
||||
const { data } = await axios.post(`${this.apiBase}/api/share/${this.shareCode}/download-url`, {
|
||||
path: filePath,
|
||||
password: this.password || undefined
|
||||
});
|
||||
|
||||
if (data.success) {
|
||||
// 记录下载次数(异步,不等待)
|
||||
axios.post(`${this.apiBase}/api/share/${this.shareCode}/download`)
|
||||
.catch(err => console.error('记录下载次数失败:', err));
|
||||
|
||||
if (data.direct) {
|
||||
// OSS 直连下载:新窗口打开
|
||||
console.log("[分享下载] OSS 直连下载");
|
||||
|
||||
// 仅直连下载需要单独记录下载次数(本地代理下载在后端接口内已计数)
|
||||
axios.post(`${this.apiBase}/api/share/${this.shareCode}/download`)
|
||||
.catch(err => console.error('记录下载次数失败:', err));
|
||||
|
||||
window.open(data.downloadUrl, '_blank');
|
||||
} else {
|
||||
// 本地存储:通过后端下载
|
||||
@@ -930,7 +1288,7 @@
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[分享下载] 获取下载链接失败:', error);
|
||||
alert('获取下载链接失败: ' + (error.response?.data?.message || error.message));
|
||||
this.errorMessage = '获取下载链接失败: ' + (error.response?.data?.message || error.message);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1049,7 +1407,28 @@
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
async mounted() {
|
||||
axios.defaults.withCredentials = true;
|
||||
|
||||
axios.interceptors.request.use(config => {
|
||||
const csrfToken = document.cookie
|
||||
.split('; ')
|
||||
.find(row => row.startsWith('csrf_token='))
|
||||
?.split('=')[1];
|
||||
|
||||
if (csrfToken && ['POST', 'PUT', 'DELETE', 'PATCH'].includes(config.method?.toUpperCase())) {
|
||||
config.headers['X-CSRF-Token'] = csrfToken;
|
||||
}
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
try {
|
||||
await axios.get(`${this.apiBase}/api/csrf-token`);
|
||||
} catch (error) {
|
||||
console.warn('初始化 CSRF Token 失败:', error?.message || error);
|
||||
}
|
||||
|
||||
this.init();
|
||||
}
|
||||
}).mount('#app');
|
||||
|
||||
Reference in New Issue
Block a user