✨ 重新设计分享管理页面
- 添加搜索/筛选功能(关键字、类型、状态、排序) - 重新设计卡片视图,展示更多信息(状态、类型、加密、存储来源等) - 添加filteredShares计算属性实现筛选和排序 - 添加辅助方法:getShareTypeLabel、getShareStatus、getShareProtection等 - 优化分享卡片样式,支持深色/浅色主题 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1028,6 +1028,109 @@
|
|||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: rgba(255, 255, 255, 0.15);
|
background: rgba(255, 255, 255, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 分享卡片布局 */
|
||||||
|
.share-card-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
.share-card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 14px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.12);
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.share-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
border-color: var(--glass-border-hover);
|
||||||
|
box-shadow: 0 14px 36px rgba(0,0,0,0.18);
|
||||||
|
}
|
||||||
|
.share-card__title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
.share-card__chips {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.share-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 12px;
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
background: rgba(255,255,255,0.04);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
.share-chip.success { color: #22c55e; background: rgba(34,197,94,0.12); border-color: rgba(34,197,94,0.25); }
|
||||||
|
.share-chip.warn { color: #f59e0b; background: rgba(245,158,11,0.14); border-color: rgba(245,158,11,0.25); }
|
||||||
|
.share-chip.danger { color: #ef4444; background: rgba(239,68,68,0.14); border-color: rgba(239,68,68,0.25); }
|
||||||
|
.share-chip.info { color: var(--accent-1); background: rgba(102,126,234,0.14); border-color: rgba(102,126,234,0.25); }
|
||||||
|
.share-card__meta {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.share-card__meta span {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.share-card__actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.share-card__actions .btn {
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.share-toolbar {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.share-toolbar input,
|
||||||
|
.share-toolbar select {
|
||||||
|
background: rgba(255,255,255,0.05);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
.share-toolbar input::placeholder {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
.share-toolbar select {
|
||||||
|
min-width: 140px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
body.light-theme .share-card {
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.08);
|
||||||
|
}
|
||||||
|
body.light-theme .share-card:hover {
|
||||||
|
box-shadow: 0 14px 36px rgba(0,0,0,0.12);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -1919,46 +2022,108 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 分享视图 -->
|
<!-- 分享视图 -->
|
||||||
<div v-if="isLoggedIn && currentView === 'shares'" class="main-container">
|
<div v-if="isLoggedIn && currentView === 'shares'" class="main-container">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<!-- 标题和工具栏 -->
|
<!-- 标题和工具栏 -->
|
||||||
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
|
<div style="margin-bottom: 16px; display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap;">
|
||||||
<h3 style="margin: 0;">我的分享</h3>
|
<h3 style="margin: 0; display: flex; align-items: center; gap: 8px;">
|
||||||
<div style="display: flex; gap: 10px;">
|
<i class="fas fa-share-alt"></i> 我的分享
|
||||||
|
</h3>
|
||||||
|
<div style="display: flex; gap: 8px;">
|
||||||
<button class="btn" :class="shareViewMode === 'grid' ? 'btn-primary' : 'btn-secondary'" @click="shareViewMode = 'grid'">
|
<button class="btn" :class="shareViewMode === 'grid' ? 'btn-primary' : 'btn-secondary'" @click="shareViewMode = 'grid'">
|
||||||
<i class="fas fa-th-large"></i> 大图标
|
<i class="fas fa-th-large"></i> 卡片
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" :class="shareViewMode === 'list' ? 'btn-primary' : 'btn-secondary'" @click="shareViewMode = 'list'">
|
<button class="btn" :class="shareViewMode === 'list' ? 'btn-primary' : 'btn-secondary'" @click="shareViewMode = 'list'">
|
||||||
<i class="fas fa-list"></i> 列表
|
<i class="fas fa-list"></i> 列表
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-secondary" @click="loadShares">
|
||||||
|
<i class="fas fa-sync-alt"></i> 刷新
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 筛选/搜索 -->
|
||||||
|
<div class="share-toolbar" style="margin-bottom: 14px;">
|
||||||
|
<input type="text" v-model="shareFilters.keyword" placeholder="搜索路径 / 链接 / 分享码" style="flex: 1; min-width: 180px;">
|
||||||
|
<select v-model="shareFilters.type">
|
||||||
|
<option value="all">全部类型</option>
|
||||||
|
<option value="file">文件</option>
|
||||||
|
<option value="directory">文件夹</option>
|
||||||
|
<option value="all_files">全部文件</option>
|
||||||
|
</select>
|
||||||
|
<select v-model="shareFilters.status">
|
||||||
|
<option value="all">全部状态</option>
|
||||||
|
<option value="active">有效</option>
|
||||||
|
<option value="expiring">即将到期</option>
|
||||||
|
<option value="expired">已过期</option>
|
||||||
|
<option value="protected">已加密</option>
|
||||||
|
<option value="public">公开</option>
|
||||||
|
</select>
|
||||||
|
<select v-model="shareFilters.sort">
|
||||||
|
<option value="created_desc">最新创建</option>
|
||||||
|
<option value="created_asc">最早创建</option>
|
||||||
|
<option value="views_desc">访问最多</option>
|
||||||
|
<option value="downloads_desc">下载最多</option>
|
||||||
|
<option value="expire_asc">最早到期</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<!-- 空状态 -->
|
||||||
<div v-if="shares.length === 0" class="alert alert-info">
|
<div v-if="shares.length === 0" class="alert alert-info">
|
||||||
还没有创建任何分享
|
还没有创建任何分享
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="filteredShares.length === 0" class="alert alert-warning">
|
||||||
|
没有符合筛选条件的分享,试试清空搜索/筛选。
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 大图标视图 -->
|
<!-- 大图标视图 -->
|
||||||
<div v-else-if="shareViewMode === 'grid'" class="file-grid">
|
<div v-else-if="shareViewMode === 'grid'" class="share-card-grid">
|
||||||
<div v-for="share in shares" :key="share.id" class="file-grid-item">
|
<div v-for="share in filteredShares" :key="share.id" class="share-card">
|
||||||
<div class="file-icon">
|
<div class="share-card__title">
|
||||||
<i class="fas fa-share-alt" style="font-size: 64px; color: #667eea;"></i>
|
<i class="fas" :class="{
|
||||||
|
'fa-file-alt': share.share_type === 'file',
|
||||||
|
'fa-folder': share.share_type === 'directory',
|
||||||
|
'fa-layer-group': share.share_type === 'all'
|
||||||
|
}" style="color: var(--accent-1);"></i>
|
||||||
|
<span :title="share.share_path">{{ share.share_path }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="file-name" :title="share.share_path">{{ share.share_path }}</div>
|
<div class="share-card__chips">
|
||||||
<div class="file-size" style="font-size: 12px; color: var(--text-secondary);">
|
<span :class="['share-chip', getShareStatus(share).class]">
|
||||||
访问: {{ share.view_count }} | 下载: {{ share.download_count }}
|
<i class="fas" :class="getShareStatus(share).icon"></i> {{ getShareStatus(share).text }}
|
||||||
|
</span>
|
||||||
|
<span class="share-chip info">
|
||||||
|
<i class="fas fa-tag"></i> {{ getShareTypeLabel(share.share_type) }}
|
||||||
|
</span>
|
||||||
|
<span class="share-chip info">
|
||||||
|
<i class="fas" :class="getShareProtection(share).icon"></i> {{ getShareProtection(share).text }}
|
||||||
|
</span>
|
||||||
|
<span class="share-chip info" v-if="share.storage_type">
|
||||||
|
<i class="fas fa-hdd"></i> {{ getStorageLabel(share.storage_type) }}
|
||||||
|
</span>
|
||||||
|
<span class="share-chip info">
|
||||||
|
<i class="fas fa-barcode"></i> {{ share.share_code }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="file-actions">
|
<div style="font-size: 13px; color: var(--text-secondary); margin-bottom: 10px; word-break: break-all;">
|
||||||
<button class="btn-icon" @click.stop="window.open(share.share_url, '_blank')" title="打开分享">
|
<i class="fas fa-link"></i>
|
||||||
<i class="fas fa-external-link-alt"></i>
|
<a :href="share.share_url" target="_blank" style="color: var(--accent-1); word-break: break-all;">{{ share.share_url }}</a>
|
||||||
|
</div>
|
||||||
|
<div class="share-card__meta">
|
||||||
|
<span><i class="fas fa-eye"></i> 访问 {{ share.view_count }}</span>
|
||||||
|
<span><i class="fas fa-download"></i> 下载 {{ share.download_count }}</span>
|
||||||
|
<span><i class="fas fa-clock"></i> {{ share.expires_at ? formatExpireTime(share.expires_at) : '永久有效' }}</span>
|
||||||
|
<span><i class="fas fa-calendar-alt"></i> 创建 {{ formatDateTime(share.created_at) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="share-card__actions">
|
||||||
|
<button class="btn btn-secondary" @click.stop="window.open(share.share_url, '_blank')">
|
||||||
|
<i class="fas fa-external-link-alt"></i> 打开
|
||||||
</button>
|
</button>
|
||||||
<button class="btn-icon" @click.stop="copyShareLink(share.share_url)" title="复制链接">
|
<button class="btn btn-secondary" @click.stop="copyShareLink(share.share_url)">
|
||||||
<i class="fas fa-copy"></i>
|
<i class="fas fa-copy"></i> 复制链接
|
||||||
</button>
|
</button>
|
||||||
<button class="btn-icon" style="color: #ef4444;" @click.stop="deleteShare(share.id)" title="删除">
|
<button class="btn" style="background: #ef4444; color: white;" @click.stop="deleteShare(share.id)">
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i> 删除
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1977,7 +2142,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="share in shares" :key="share.id" style="border-bottom: 1px solid #eee;">
|
<tr v-for="share in filteredShares" :key="share.id" style="border-bottom: 1px solid #eee;">
|
||||||
<td style="padding: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" :title="share.share_path">{{ share.share_path }}</td>
|
<td style="padding: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" :title="share.share_path">{{ share.share_path }}</td>
|
||||||
<td style="padding: 10px; overflow: hidden;">
|
<td style="padding: 10px; overflow: hidden;">
|
||||||
<a :href="share.share_url" target="_blank" style="color: #667eea; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" :title="share.share_url">{{ share.share_url }}</a>
|
<a :href="share.share_url" target="_blank" style="color: #667eea; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" :title="share.share_url">{{ share.share_url }}</a>
|
||||||
|
|||||||
@@ -89,6 +89,12 @@ createApp({
|
|||||||
customDays: 7
|
customDays: 7
|
||||||
},
|
},
|
||||||
shareResult: null,
|
shareResult: null,
|
||||||
|
shareFilters: {
|
||||||
|
keyword: '',
|
||||||
|
type: 'all', // all/file/directory/all_files
|
||||||
|
status: 'all', // all/active/expiring/expired/protected/public
|
||||||
|
sort: 'created_desc' // created_desc/created_asc/views_desc/downloads_desc/expire_asc
|
||||||
|
},
|
||||||
|
|
||||||
// 文件重命名
|
// 文件重命名
|
||||||
showRenameModal: false,
|
showRenameModal: false,
|
||||||
@@ -304,6 +310,56 @@ createApp({
|
|||||||
// 存储类型显示文本
|
// 存储类型显示文本
|
||||||
storageTypeText() {
|
storageTypeText() {
|
||||||
return this.storageType === 'local' ? '本地存储' : 'SFTP存储';
|
return this.storageType === 'local' ? '本地存储' : 'SFTP存储';
|
||||||
|
},
|
||||||
|
|
||||||
|
// 分享筛选+排序后的列表
|
||||||
|
filteredShares() {
|
||||||
|
let list = [...this.shares];
|
||||||
|
const keyword = this.shareFilters.keyword.trim().toLowerCase();
|
||||||
|
|
||||||
|
if (keyword) {
|
||||||
|
list = list.filter(s =>
|
||||||
|
(s.share_path || '').toLowerCase().includes(keyword) ||
|
||||||
|
(s.share_code || '').toLowerCase().includes(keyword) ||
|
||||||
|
(s.share_url || '').toLowerCase().includes(keyword)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.shareFilters.type !== 'all') {
|
||||||
|
const targetType = this.shareFilters.type === 'all_files' ? 'all' : this.shareFilters.type;
|
||||||
|
list = list.filter(s => (s.share_type || 'file') === targetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.shareFilters.status !== 'all') {
|
||||||
|
list = list.filter(s => {
|
||||||
|
if (this.shareFilters.status === 'expired') return this.isExpired(s.expires_at);
|
||||||
|
if (this.shareFilters.status === 'expiring') return this.isExpiringSoon(s.expires_at) && !this.isExpired(s.expires_at);
|
||||||
|
if (this.shareFilters.status === 'active') return !this.isExpired(s.expires_at);
|
||||||
|
if (this.shareFilters.status === 'protected') return !!s.share_password;
|
||||||
|
if (this.shareFilters.status === 'public') return !s.share_password;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
list.sort((a, b) => {
|
||||||
|
const getTime = s => s.created_at ? new Date(s.created_at).getTime() : 0;
|
||||||
|
const getExpire = s => s.expires_at ? new Date(s.expires_at).getTime() : Number.MAX_SAFE_INTEGER;
|
||||||
|
|
||||||
|
switch (this.shareFilters.sort) {
|
||||||
|
case 'created_asc':
|
||||||
|
return getTime(a) - getTime(b);
|
||||||
|
case 'views_desc':
|
||||||
|
return (b.view_count || 0) - (a.view_count || 0);
|
||||||
|
case 'downloads_desc':
|
||||||
|
return (b.download_count || 0) - (a.download_count || 0);
|
||||||
|
case 'expire_asc':
|
||||||
|
return getExpire(a) - getExpire(b);
|
||||||
|
default:
|
||||||
|
return getTime(b) - getTime(a); // created_desc
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1916,6 +1972,49 @@ handleDragLeave(e) {
|
|||||||
return expireDate <= now;
|
return expireDate <= now;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 分享类型标签
|
||||||
|
getShareTypeLabel(type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'directory': return '文件夹';
|
||||||
|
case 'all': return '全部文件';
|
||||||
|
case 'file':
|
||||||
|
default: return '文件';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 分享状态标签
|
||||||
|
getShareStatus(share) {
|
||||||
|
if (this.isExpired(share.expires_at)) {
|
||||||
|
return { text: '已过期', class: 'danger', icon: 'fa-clock' };
|
||||||
|
}
|
||||||
|
if (this.isExpiringSoon(share.expires_at)) {
|
||||||
|
return { text: '即将到期', class: 'warn', icon: 'fa-hourglass-half' };
|
||||||
|
}
|
||||||
|
return { text: '有效', class: 'success', icon: 'fa-check-circle' };
|
||||||
|
},
|
||||||
|
|
||||||
|
// 分享保护标签
|
||||||
|
getShareProtection(share) {
|
||||||
|
if (share.share_password) {
|
||||||
|
return { text: '已加密', class: 'info', icon: 'fa-lock' };
|
||||||
|
}
|
||||||
|
return { text: '公开', class: 'info', icon: 'fa-unlock' };
|
||||||
|
},
|
||||||
|
|
||||||
|
// 存储来源
|
||||||
|
getStorageLabel(storageType) {
|
||||||
|
if (!storageType) return '默认';
|
||||||
|
return storageType === 'local' ? '本地存储' : storageType.toUpperCase();
|
||||||
|
},
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
formatDateTime(value) {
|
||||||
|
if (!value) return '--';
|
||||||
|
const d = new Date(value);
|
||||||
|
if (Number.isNaN(d.getTime())) return value;
|
||||||
|
return d.toLocaleString();
|
||||||
|
},
|
||||||
|
|
||||||
copyShareLink(url) {
|
copyShareLink(url) {
|
||||||
// 复制分享链接到剪贴板
|
// 复制分享链接到剪贴板
|
||||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
|
|||||||
Reference in New Issue
Block a user