feat: unify cloud workspace and release desktop v0.1.38
This commit is contained in:
@@ -1806,9 +1806,14 @@
|
||||
|
||||
<div class="navbar" v-if="isLoggedIn">
|
||||
<div class="navbar-brand">
|
||||
<i class="fas fa-cloud"></i> 玩玩云
|
||||
<span class="brand-mark"><i class="fas fa-cloud"></i></span>
|
||||
<span class="brand-copy">
|
||||
<strong>玩玩云</strong>
|
||||
<small>Cloud Workspace</small>
|
||||
</span>
|
||||
</div>
|
||||
<div class="navbar-menu">
|
||||
<div class="nav-section-label">工作台</div>
|
||||
<div v-if="user && !user.is_admin" class="nav-item" :class="{active: currentView === 'files'}" @click="switchView('files')">
|
||||
<i class="fas fa-folder"></i> 我的文件
|
||||
</div>
|
||||
@@ -1818,21 +1823,26 @@
|
||||
<div v-if="user && user.is_admin" class="nav-item" :class="{active: currentView === 'admin'}" @click="switchView('admin')">
|
||||
<i class="fas fa-user-shield"></i> 管理员
|
||||
</div>
|
||||
<div class="nav-section-label nav-service-label">服务</div>
|
||||
<div class="nav-item" :class="{active: currentView === 'settings'}" @click="switchView('settings')">
|
||||
|
||||
<i class="fas fa-cog"></i> 设置
|
||||
</div>
|
||||
<button class="btn btn-secondary navbar-download-btn" :disabled="desktopClientDownloading" @click="downloadDesktopClient">
|
||||
<i :class="desktopClientDownloading ? 'fas fa-spinner fa-spin' : 'fas fa-download'"></i>
|
||||
{{ desktopClientDownloading ? '准备中...' : '下载客户端' }}
|
||||
</button>
|
||||
<div class="user-info">
|
||||
<i class="fas fa-user-circle"></i>
|
||||
<span>{{ user.username }}</span>
|
||||
<div class="navbar-account">
|
||||
<div class="user-info">
|
||||
<span class="user-avatar"><i class="fas fa-user"></i></span>
|
||||
<span class="user-copy">
|
||||
<small>当前账号</small>
|
||||
<strong>{{ user.username }}</strong>
|
||||
</span>
|
||||
</div>
|
||||
<button class="btn btn-danger navbar-logout-btn" @click="logout" title="退出登录">
|
||||
<i class="fas fa-power-off"></i> 退出
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn btn-danger" @click="logout">
|
||||
<i class="fas fa-power-off"></i> 退出
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1840,6 +1850,34 @@
|
||||
<div v-if="isLoggedIn && currentView === 'files'" class="main-container">
|
||||
<div class="card files-view-card">
|
||||
|
||||
<header class="workspace-page-header files-page-header">
|
||||
<div class="workspace-page-heading">
|
||||
<span class="workspace-kicker"><i class="fas fa-layer-group"></i> 云端空间</span>
|
||||
<h1>我的文件</h1>
|
||||
<p>集中管理、查找和分享你的全部文件。</p>
|
||||
</div>
|
||||
<div class="storage-meter-card">
|
||||
<div class="storage-meter-head">
|
||||
<span class="files-storage-badge" :class="storageType === 'local' ? 'local' : 'oss'">
|
||||
<i :class="storageType === 'local' ? 'fas fa-hard-drive' : 'fas fa-server'"></i>
|
||||
{{ storageTypeText }}
|
||||
</span>
|
||||
<strong v-if="storageType === 'local'">{{ localUsedFormatted }} / {{ localQuotaFormatted }}</strong>
|
||||
<strong v-else>{{ ossUsedFormatted }} / {{ ossQuotaFormatted }}</strong>
|
||||
</div>
|
||||
<div class="storage-meter-track">
|
||||
<span :style="{
|
||||
width: (storageType === 'local' ? quotaPercentage : ossQuotaPercentage) + '%',
|
||||
background: (storageType === 'local' ? quotaPercentage : ossQuotaPercentage) > 90 ? '#ef4444' : (storageType === 'local' ? quotaPercentage : ossQuotaPercentage) > 75 ? '#f59e0b' : '#2878ff'
|
||||
}"></span>
|
||||
</div>
|
||||
<div class="storage-meter-foot">
|
||||
<span>{{ storageType === 'local' ? quotaPercentage : ossQuotaPercentage }}% 已使用</span>
|
||||
<span>{{ fileStats.totalCount }} 个项目</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 路径导航 (面包屑) -->
|
||||
<div v-if="currentPath !== '/'" class="breadcrumb-bar">
|
||||
<button class="btn-icon breadcrumb-home" @click="loadFiles('/')" title="返回根目录">
|
||||
@@ -1872,105 +1910,96 @@
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<div v-else class="files-content-shell">
|
||||
<div class="files-content-head files-content-head-compact files-content-head-actions-row">
|
||||
<span class="files-content-title"><i class="fas fa-folder-tree"></i> 文件视图 · {{ fileStats.totalCount }} 项</span>
|
||||
<div class="files-content-head-meta">
|
||||
<span class="files-storage-badge files-head-storage-badge" :class="storageType === 'local' ? 'local' : 'oss'">
|
||||
<i :class="storageType === 'local' ? 'fas fa-hard-drive' : 'fas fa-server'"></i>
|
||||
{{ storageTypeText }}
|
||||
</span>
|
||||
<div class="files-head-usage-progress" :title="storageType === 'local' ? `本地:${localUsedFormatted} / ${localQuotaFormatted} (${quotaPercentage}%)` : `OSS:${ossUsedFormatted} / ${ossQuotaFormatted} (${ossQuotaPercentage}%)`">
|
||||
<div class="files-head-usage-progress-bar" :style="{
|
||||
width: (storageType === 'local' ? quotaPercentage : ossQuotaPercentage) + '%',
|
||||
background: (storageType === 'local' ? quotaPercentage : ossQuotaPercentage) > 90 ? '#ef4444' : (storageType === 'local' ? quotaPercentage : ossQuotaPercentage) > 75 ? '#f59e0b' : '#22c55e'
|
||||
}"></div>
|
||||
<span class="files-head-usage-progress-text" v-if="storageType === 'local'">
|
||||
本地 {{ localUsedFormatted }} / {{ localQuotaFormatted }} · {{ quotaPercentage }}%
|
||||
</span>
|
||||
<span class="files-head-usage-progress-text" v-else>
|
||||
OSS {{ ossUsedFormatted }} / {{ ossQuotaFormatted }} · {{ ossQuotaPercentage }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="files-content-head-actions">
|
||||
<button class="btn btn-primary files-head-action-btn" @click="$refs.fileUploadInput.click()">
|
||||
<div class="files-command-bar">
|
||||
<div class="files-command-primary">
|
||||
<button class="btn btn-primary" @click="$refs.fileUploadInput.click()">
|
||||
<i class="fas fa-upload"></i> 上传文件
|
||||
</button>
|
||||
<button class="btn btn-secondary files-head-action-btn files-head-folder-btn" @click="showCreateFolderModal = true">
|
||||
<button class="btn btn-secondary" @click="showCreateFolderModal = true">
|
||||
<i class="fas fa-folder-plus"></i> 新建文件夹
|
||||
</button>
|
||||
<div class="view-toggle-group files-head-view-toggle">
|
||||
<button class="btn" :class="fileViewMode === 'grid' ? 'btn-primary' : 'btn-secondary'" @click="fileViewMode = 'grid'">
|
||||
<i class="fas fa-th-large"></i> 大图标
|
||||
</div>
|
||||
<div class="view-toggle-group files-view-toggle" aria-label="文件视图切换">
|
||||
<button class="btn" :class="fileViewMode === 'grid' ? 'btn-primary' : 'btn-secondary'" @click="fileViewMode = 'grid'" title="大图标视图">
|
||||
<i class="fas fa-th-large"></i><span>大图标</span>
|
||||
</button>
|
||||
<button class="btn" :class="fileViewMode === 'list' ? 'btn-primary' : 'btn-secondary'" @click="fileViewMode = 'list'">
|
||||
<i class="fas fa-list"></i> 列表
|
||||
<button class="btn" :class="fileViewMode === 'list' ? 'btn-primary' : 'btn-secondary'" @click="fileViewMode = 'list'" title="列表视图">
|
||||
<i class="fas fa-list"></i><span>列表</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin: 10px 0 14px 0; position: relative;">
|
||||
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
|
||||
|
||||
<div class="files-search-wrap">
|
||||
<div class="files-search-row">
|
||||
<div class="files-search-field">
|
||||
<i class="fas fa-search"></i>
|
||||
<input
|
||||
type="text"
|
||||
class="form-input"
|
||||
v-model="globalSearchKeyword"
|
||||
@input="triggerGlobalSearch"
|
||||
@focus="globalSearchVisible = !!globalSearchKeyword"
|
||||
placeholder="全局搜索文件名(跨全部目录)"
|
||||
style="flex: 1; min-width: 220px;">
|
||||
<select class="form-input" v-model="globalSearchType" @change="runGlobalSearch" style="width: 120px;">
|
||||
placeholder="搜索全部目录中的文件或文件夹">
|
||||
</div>
|
||||
<select class="form-input files-search-type" v-model="globalSearchType" @change="runGlobalSearch">
|
||||
<option value="all">全部</option>
|
||||
<option value="file">仅文件</option>
|
||||
<option value="directory">仅文件夹</option>
|
||||
</select>
|
||||
<button class="btn btn-secondary" @click="runGlobalSearch" :disabled="globalSearchLoading" style="min-width: 86px;">
|
||||
<button class="btn btn-secondary files-search-button" @click="runGlobalSearch" :disabled="globalSearchLoading">
|
||||
<i :class="globalSearchLoading ? 'fas fa-spinner fa-spin' : 'fas fa-search'"></i>
|
||||
搜索
|
||||
</button>
|
||||
<button class="btn btn-secondary" @click="clearGlobalSearch()" style="min-width: 72px;">
|
||||
<button class="btn btn-quiet files-search-clear" @click="clearGlobalSearch()">
|
||||
清空
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="globalSearchVisible" style="position: absolute; left: 0; right: 0; top: calc(100% + 8px); z-index: 30; background: var(--bg-card); border: 1px solid var(--glass-border); border-radius: 10px; box-shadow: 0 8px 28px rgba(0,0,0,0.22); max-height: 320px; overflow: auto;">
|
||||
<div v-if="globalSearchLoading" style="padding: 12px; color: var(--text-secondary);">
|
||||
<div v-if="globalSearchVisible" class="files-search-results">
|
||||
<div v-if="globalSearchLoading" class="files-search-message">
|
||||
<i class="fas fa-spinner fa-spin"></i> 正在搜索...
|
||||
</div>
|
||||
<div v-else-if="globalSearchError" style="padding: 12px; color: #ef4444;">
|
||||
<div v-else-if="globalSearchError" class="files-search-message error">
|
||||
<i class="fas fa-circle-exclamation"></i> {{ globalSearchError }}
|
||||
</div>
|
||||
<div v-else-if="globalSearchResults.length === 0" style="padding: 12px; color: var(--text-secondary);">
|
||||
<div v-else-if="globalSearchResults.length === 0" class="files-search-message">
|
||||
暂无匹配结果
|
||||
</div>
|
||||
<div v-else>
|
||||
<button
|
||||
v-for="item in globalSearchResults"
|
||||
:key="item.path"
|
||||
class="btn"
|
||||
@click="jumpToSearchResult(item)"
|
||||
style="display: block; width: 100%; border: none; border-bottom: 1px solid var(--glass-border); border-radius: 0; text-align: left; background: transparent; padding: 10px 12px;">
|
||||
<div style="display: flex; justify-content: space-between; gap: 12px; align-items: center;">
|
||||
<div style="min-width: 0;">
|
||||
<div style="font-weight: 600; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
<i class="fas" :class="item.isDirectory ? 'fa-folder' : 'fa-file'" style="margin-right: 6px; color: #667eea;"></i>
|
||||
class="files-search-result"
|
||||
@click="jumpToSearchResult(item)">
|
||||
<div class="files-search-result-main">
|
||||
<div class="files-search-result-copy">
|
||||
<div class="files-search-result-name">
|
||||
<i class="fas" :class="item.isDirectory ? 'fa-folder' : 'fa-file'"></i>
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div style="font-size: 12px; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
<div class="files-search-result-path">
|
||||
{{ item.path }}
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size: 12px; color: var(--text-muted); white-space: nowrap;">
|
||||
<div class="files-search-result-size">
|
||||
{{ item.isDirectory ? '文件夹' : (item.sizeFormatted || '-') }}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<div v-if="globalSearchMeta?.truncated" style="padding: 10px 12px; font-size: 12px; color: #f59e0b;">
|
||||
<div v-if="globalSearchMeta?.truncated" class="files-search-truncated">
|
||||
结果已截断,请缩小关键词范围
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="files-list-heading">
|
||||
<div>
|
||||
<strong>{{ currentPath === '/' ? '全部文件' : pathParts[pathParts.length - 1] }}</strong>
|
||||
<span>{{ fileStats.totalCount }} 个项目</span>
|
||||
</div>
|
||||
<span class="files-list-hint"><i class="fas fa-hand-pointer"></i> 双击打开,右键查看更多操作</span>
|
||||
</div>
|
||||
<div @dragenter="handleDragEnter" @dragover="handleDragOver" @dragleave="handleDragLeave" @drop="handleDrop" class="files-container" :class="{ 'drag-over': isDragging }">
|
||||
<div v-if="files.length === 0" class="empty-hint files-empty-state">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
@@ -3054,35 +3083,56 @@
|
||||
|
||||
<!-- 分享视图 -->
|
||||
<div v-if="isLoggedIn && currentView === 'shares'" class="main-container">
|
||||
<div class="card">
|
||||
<!-- 标题和工具栏 -->
|
||||
<div class="shares-page-head" style="margin-bottom: 16px; display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap;">
|
||||
<h3 class="shares-page-title" style="margin: 0; display: flex; align-items: center; gap: 8px;">
|
||||
<i class="fas fa-share-alt"></i> 我的分享
|
||||
</h3>
|
||||
<div class="shares-page-actions" style="display: flex; gap: 8px;">
|
||||
<button class="btn" :class="shareViewMode === 'grid' ? 'btn-primary' : 'btn-secondary'" @click="shareViewMode = 'grid'">
|
||||
<i class="fas fa-th-large"></i> 卡片
|
||||
</button>
|
||||
<button class="btn" :class="shareViewMode === 'list' ? 'btn-primary' : 'btn-secondary'" @click="shareViewMode = 'list'">
|
||||
<i class="fas fa-list"></i> 列表
|
||||
</button>
|
||||
<div class="card shares-page-card">
|
||||
<header class="workspace-page-header shares-page-head">
|
||||
<div class="workspace-page-heading">
|
||||
<span class="workspace-kicker"><i class="fas fa-share-nodes"></i> 共享中心</span>
|
||||
<h1>我的分享</h1>
|
||||
<p>集中查看分享状态、访问数据和文件直链。</p>
|
||||
</div>
|
||||
<div class="shares-page-actions">
|
||||
<div class="view-toggle-group shares-view-toggle" aria-label="分享视图切换">
|
||||
<button class="btn" :class="shareViewMode === 'grid' ? 'btn-primary' : 'btn-secondary'" @click="shareViewMode = 'grid'" title="卡片视图">
|
||||
<i class="fas fa-th-large"></i><span>卡片</span>
|
||||
</button>
|
||||
<button class="btn" :class="shareViewMode === 'list' ? 'btn-primary' : 'btn-secondary'" @click="shareViewMode = 'list'" title="列表视图">
|
||||
<i class="fas fa-list"></i><span>列表</span>
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn btn-secondary" @click="refreshShareResources">
|
||||
<i class="fas fa-sync-alt"></i> 刷新
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="share-metrics">
|
||||
<div class="share-metric">
|
||||
<span class="share-metric-icon blue"><i class="fas fa-share-alt"></i></span>
|
||||
<span><small>分享链接</small><strong>{{ shares.length }}</strong></span>
|
||||
</div>
|
||||
<div class="share-metric">
|
||||
<span class="share-metric-icon teal"><i class="fas fa-link"></i></span>
|
||||
<span><small>文件直链</small><strong>{{ directLinks.length }}</strong></span>
|
||||
</div>
|
||||
<div class="share-metric share-metric-wide">
|
||||
<span class="share-metric-icon amber"><i class="fas fa-layer-group"></i></span>
|
||||
<span><small>共享资源总数</small><strong>{{ shares.length + directLinks.length }}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选/搜索 -->
|
||||
<div class="share-toolbar shares-toolbar">
|
||||
<input type="text" v-model="shareFilters.keyword" placeholder="搜索路径 / 链接 / 分享码" style="flex: 1; min-width: 180px;">
|
||||
<select v-model="shareFilters.type">
|
||||
<div class="share-search-field">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" v-model="shareFilters.keyword" placeholder="搜索文件名、链接或分享码">
|
||||
</div>
|
||||
<select v-model="shareFilters.type" aria-label="分享类型">
|
||||
<option value="all">全部类型</option>
|
||||
<option value="file">文件</option>
|
||||
<option value="directory">文件夹</option>
|
||||
<option value="all_files">全部文件</option>
|
||||
</select>
|
||||
<select v-model="shareFilters.status">
|
||||
<select v-model="shareFilters.status" aria-label="分享状态">
|
||||
<option value="all">全部状态</option>
|
||||
<option value="active">有效</option>
|
||||
<option value="expiring">即将到期</option>
|
||||
@@ -3090,7 +3140,7 @@
|
||||
<option value="protected">已加密</option>
|
||||
<option value="public">公开</option>
|
||||
</select>
|
||||
<select v-model="shareFilters.sort">
|
||||
<select v-model="shareFilters.sort" aria-label="分享排序">
|
||||
<option value="created_desc">最新创建</option>
|
||||
<option value="created_asc">最早创建</option>
|
||||
<option value="views_desc">访问最多</option>
|
||||
@@ -3099,161 +3149,148 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="shares.length === 0" class="alert alert-info">
|
||||
还没有创建任何分享
|
||||
</div>
|
||||
<div v-else-if="filteredShares.length === 0" class="alert alert-warning">
|
||||
没有符合筛选条件的分享,试试清空搜索/筛选。
|
||||
</div>
|
||||
|
||||
<!-- 大图标视图 -->
|
||||
<div v-else-if="shareViewMode === 'grid'" class="share-card-grid">
|
||||
<div v-for="share in filteredShares" :key="share.id" class="share-card">
|
||||
<div class="share-card__title">
|
||||
<i class="fas" :class="getShareTypeIcon(share)" style="color: var(--accent-1);"></i>
|
||||
<span :title="share.share_path">{{ getPathBaseName(share.share_path) }}</span>
|
||||
</div>
|
||||
<div class="share-card__chips">
|
||||
<span :class="['share-chip', getShareStatus(share).class]">
|
||||
<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) }}
|
||||
</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 class="share-card__url" style="font-size: 13px; color: var(--text-secondary); margin-bottom: 10px; word-break: break-all;">
|
||||
<i class="fas fa-link"></i>
|
||||
<span class="share-card__url-link" :title="share.share_url">{{ share.share_url }}</span>
|
||||
</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="openShare(share.share_url)">
|
||||
<i class="fas fa-external-link-alt"></i> 打开
|
||||
</button>
|
||||
<button class="btn btn-secondary" @click.stop="copyShareLink(share.share_url)">
|
||||
<i class="fas fa-copy"></i> 复制链接
|
||||
</button>
|
||||
<button class="btn share-card__delete-btn" style="background: #ef4444; color: white;" @click.stop="requestDeleteShare(share.id, $event)">
|
||||
<i class="fas fa-trash"></i> 删除
|
||||
</button>
|
||||
<section class="share-section">
|
||||
<div class="share-section-head">
|
||||
<div>
|
||||
<span class="share-section-icon"><i class="fas fa-share-alt"></i></span>
|
||||
<span><strong>分享链接</strong><small>可设置有效期、访问密码并查看使用情况</small></span>
|
||||
</div>
|
||||
<span class="share-section-count">{{ filteredShares.length }} 项</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 列表视图 -->
|
||||
<table v-else class="share-list-table">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid #ddd;">
|
||||
<th style="padding: 10px; text-align: left; width: 18%;">文件名</th>
|
||||
<th style="padding: 10px; text-align: left; width: 30%;">链接地址</th>
|
||||
<th style="padding: 10px; text-align: center; width: 8%;">访问次数</th>
|
||||
<th style="padding: 10px; text-align: center; width: 8%;">下载次数</th>
|
||||
<th style="padding: 10px; text-align: center; width: 16%;">到期时间</th>
|
||||
<th style="padding: 10px; text-align: center; width: 20%;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<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">{{ getPathBaseName(share.share_path) }}</td>
|
||||
<td style="padding: 10px; overflow: hidden;">
|
||||
<span class="share-list-link-text" :title="share.share_url">{{ share.share_url }}</span>
|
||||
</td>
|
||||
<td style="padding: 10px; text-align: center;">{{ share.view_count }}</td>
|
||||
<td style="padding: 10px; text-align: center;">{{ share.download_count }}</td>
|
||||
<td style="padding: 10px; text-align: center;">
|
||||
<span v-if="!share.expires_at" style="color: #22c55e;"><i class="fas fa-infinity"></i> 永久有效</span>
|
||||
<span v-else :style="{color: isExpiringSoon(share.expires_at) ? '#ffc107' : isExpired(share.expires_at) ? '#dc3545' : '#667eea'}" :title="share.expires_at"><i class="fas fa-clock"></i> {{ formatExpireTime(share.expires_at) }}</span>
|
||||
</td>
|
||||
<td style="padding: 10px; text-align: center;">
|
||||
<div class="share-list-actions">
|
||||
<button class="btn btn-secondary" @click.stop="openShare(share.share_url)">
|
||||
<i class="fas fa-up-right-from-square"></i> 打开
|
||||
</button>
|
||||
<button class="btn btn-secondary" @click.stop="copyShareLink(share.share_url)">
|
||||
<i class="fas fa-copy"></i> 复制链接
|
||||
</button>
|
||||
<button class="btn" style="background: #ef4444; color: white;" @click.stop="requestDeleteShare(share.id, $event)">
|
||||
<i class="fas fa-trash"></i> 删除
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-if="shares.length === 0" class="share-empty-state">
|
||||
<span class="share-empty-icon"><i class="fas fa-paper-plane"></i></span>
|
||||
<strong>还没有创建分享</strong>
|
||||
<p>前往我的文件,选择文件或文件夹后即可生成分享链接。</p>
|
||||
<button class="btn btn-primary" @click="switchView('files')"><i class="fas fa-folder-open"></i> 前往我的文件</button>
|
||||
</div>
|
||||
<div v-else-if="filteredShares.length === 0" class="share-empty-state compact">
|
||||
<span class="share-empty-icon"><i class="fas fa-filter-circle-xmark"></i></span>
|
||||
<strong>没有匹配的分享</strong>
|
||||
<p>请调整上方的关键词或筛选条件。</p>
|
||||
</div>
|
||||
|
||||
<!-- 大图标视图 -->
|
||||
<div v-else-if="shareViewMode === 'grid'" class="share-card-grid">
|
||||
<article v-for="share in filteredShares" :key="share.id" class="share-card">
|
||||
<div class="share-card__title">
|
||||
<span class="share-card__type"><i class="fas" :class="getShareTypeIcon(share)"></i></span>
|
||||
<span :title="share.share_path">{{ getPathBaseName(share.share_path) }}</span>
|
||||
</div>
|
||||
<div class="share-card__chips">
|
||||
<span :class="['share-chip', getShareStatus(share).class]">
|
||||
<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) }}</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>
|
||||
</div>
|
||||
<div class="share-card__url">
|
||||
<i class="fas fa-link"></i>
|
||||
<span class="share-card__url-link" :title="share.share_url">{{ share.share_url }}</span>
|
||||
<span class="share-code">{{ share.share_code }}</span>
|
||||
</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="openShare(share.share_url)"><i class="fas fa-external-link-alt"></i> 打开</button>
|
||||
<button class="btn btn-secondary" @click.stop="copyShareLink(share.share_url)"><i class="fas fa-copy"></i> 复制</button>
|
||||
<button class="btn btn-danger share-card__delete-btn" @click.stop="requestDeleteShare(share.id, $event)"><i class="fas fa-trash"></i> 删除</button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!-- 列表视图 -->
|
||||
<div v-else class="share-table-wrap">
|
||||
<table class="share-list-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="share-col-name">文件名</th>
|
||||
<th class="share-col-link">链接地址</th>
|
||||
<th class="share-col-number">访问</th>
|
||||
<th class="share-col-number">下载</th>
|
||||
<th class="share-col-expire">有效期</th>
|
||||
<th class="share-col-actions">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="share in filteredShares" :key="share.id">
|
||||
<td :title="share.share_path"><span class="share-list-resource"><i class="fas" :class="getShareTypeIcon(share)"></i>{{ getPathBaseName(share.share_path) }}</span></td>
|
||||
<td><span class="share-list-link-text" :title="share.share_url">{{ share.share_url }}</span></td>
|
||||
<td class="share-number-cell">{{ share.view_count }}</td>
|
||||
<td class="share-number-cell">{{ share.download_count }}</td>
|
||||
<td class="share-expire-cell">
|
||||
<span v-if="!share.expires_at" class="share-forever"><i class="fas fa-infinity"></i> 永久有效</span>
|
||||
<span v-else :style="{color: isExpiringSoon(share.expires_at) ? '#d97706' : isExpired(share.expires_at) ? '#dc2626' : '#2878ff'}" :title="share.expires_at"><i class="fas fa-clock"></i> {{ formatExpireTime(share.expires_at) }}</span>
|
||||
</td>
|
||||
<td><div class="share-list-actions">
|
||||
<button class="btn btn-secondary" @click.stop="openShare(share.share_url)" title="打开"><i class="fas fa-up-right-from-square"></i></button>
|
||||
<button class="btn btn-secondary" @click.stop="copyShareLink(share.share_url)" title="复制链接"><i class="fas fa-copy"></i></button>
|
||||
<button class="btn btn-danger" @click.stop="requestDeleteShare(share.id, $event)" title="删除"><i class="fas fa-trash"></i></button>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 直链管理(独立于普通分享) -->
|
||||
<div style="margin-top: 24px;">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 10px;">
|
||||
<h4 style="margin: 0; display: flex; align-items: center; gap: 8px;">
|
||||
<i class="fas fa-link"></i> 我的直链
|
||||
</h4>
|
||||
<small style="color: var(--text-secondary);">提示:可在文件右键菜单中生成直链</small>
|
||||
<section class="share-section direct-link-section">
|
||||
<div class="share-section-head">
|
||||
<div>
|
||||
<span class="share-section-icon teal"><i class="fas fa-link"></i></span>
|
||||
<span><strong>文件直链</strong><small>适合外部引用和直接下载,可在文件右键菜单中创建</small></span>
|
||||
</div>
|
||||
<span class="share-section-count">{{ filteredDirectLinks.length }} 项</span>
|
||||
</div>
|
||||
|
||||
<div v-if="directLinksLoading" class="alert alert-info">
|
||||
正在加载直链列表...
|
||||
<div v-if="directLinksLoading" class="share-loading-state"><i class="fas fa-spinner fa-spin"></i> 正在加载直链列表...</div>
|
||||
<div v-else-if="directLinks.length === 0" class="share-empty-state compact">
|
||||
<span class="share-empty-icon teal"><i class="fas fa-link-slash"></i></span>
|
||||
<strong>还没有创建直链</strong>
|
||||
<p>在文件列表中右键目标文件,选择“生成直链”。</p>
|
||||
</div>
|
||||
<div v-else-if="directLinks.length === 0" class="alert alert-info">
|
||||
还没有创建直链
|
||||
<div v-else-if="filteredDirectLinks.length === 0" class="share-empty-state compact">
|
||||
<span class="share-empty-icon"><i class="fas fa-search"></i></span>
|
||||
<strong>没有匹配的直链</strong>
|
||||
<p>请调整上方搜索关键词。</p>
|
||||
</div>
|
||||
<div v-else-if="filteredDirectLinks.length === 0" class="alert alert-warning">
|
||||
没有符合搜索条件的直链
|
||||
<div v-else class="share-table-wrap">
|
||||
<table class="share-list-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="share-col-name">文件名</th>
|
||||
<th class="share-col-link">链接地址</th>
|
||||
<th class="share-col-number">访问</th>
|
||||
<th class="share-col-number">下载</th>
|
||||
<th class="share-col-expire">有效期</th>
|
||||
<th class="share-col-actions">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="link in filteredDirectLinks" :key="`direct-${link.id}`">
|
||||
<td :title="link.file_path"><span class="share-list-resource"><i class="fas fa-file"></i>{{ getPathBaseName(link.file_path, link.file_name) }}</span></td>
|
||||
<td><span class="share-list-link-text" :title="link.direct_url">{{ link.direct_url }}</span></td>
|
||||
<td class="share-number-cell muted" title="直链暂不统计访问次数">—</td>
|
||||
<td class="share-number-cell">{{ link.download_count || 0 }}</td>
|
||||
<td class="share-expire-cell">
|
||||
<span v-if="!link.expires_at" class="share-forever"><i class="fas fa-infinity"></i> 永久有效</span>
|
||||
<span v-else :style="{color: isExpiringSoon(link.expires_at) ? '#d97706' : isExpired(link.expires_at) ? '#dc2626' : '#2878ff'}" :title="link.expires_at"><i class="fas fa-clock"></i> {{ formatExpireTime(link.expires_at) }}</span>
|
||||
</td>
|
||||
<td><div class="share-list-actions">
|
||||
<button class="btn btn-secondary" @click.stop="openShare(link.direct_url)" title="打开"><i class="fas fa-up-right-from-square"></i></button>
|
||||
<button class="btn btn-secondary" @click.stop="copyDirectLink(link.direct_url)" title="复制链接"><i class="fas fa-copy"></i></button>
|
||||
<button class="btn btn-danger" @click.stop="requestDeleteDirectLink(link.id, $event)" title="删除"><i class="fas fa-trash"></i></button>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<table v-else class="share-list-table">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid #ddd;">
|
||||
<th style="padding: 10px; text-align: left; width: 18%;">文件名</th>
|
||||
<th style="padding: 10px; text-align: left; width: 30%;">链接地址</th>
|
||||
<th style="padding: 10px; text-align: center; width: 8%;">访问次数</th>
|
||||
<th style="padding: 10px; text-align: center; width: 8%;">下载次数</th>
|
||||
<th style="padding: 10px; text-align: center; width: 16%;">到期时间</th>
|
||||
<th style="padding: 10px; text-align: center; width: 20%;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="link in filteredDirectLinks" :key="`direct-${link.id}`" style="border-bottom: 1px solid #eee;">
|
||||
<td style="padding: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" :title="link.file_path">{{ getPathBaseName(link.file_path, link.file_name) }}</td>
|
||||
<td style="padding: 10px; overflow: hidden;">
|
||||
<span class="share-list-link-text" :title="link.direct_url">{{ link.direct_url }}</span>
|
||||
</td>
|
||||
<td style="padding: 10px; text-align: center; color: var(--text-muted);" title="直链暂不统计访问次数">—</td>
|
||||
<td style="padding: 10px; text-align: center;">{{ link.download_count || 0 }}</td>
|
||||
<td style="padding: 10px; text-align: center;">
|
||||
<span v-if="!link.expires_at" style="color: #22c55e;"><i class="fas fa-infinity"></i> 永久有效</span>
|
||||
<span v-else :style="{color: isExpiringSoon(link.expires_at) ? '#ffc107' : isExpired(link.expires_at) ? '#dc3545' : '#667eea'}" :title="link.expires_at"><i class="fas fa-clock"></i> {{ formatExpireTime(link.expires_at) }}</span>
|
||||
</td>
|
||||
<td style="padding: 10px; text-align: center;">
|
||||
<div class="share-list-actions">
|
||||
<button class="btn btn-secondary" @click.stop="openShare(link.direct_url)">
|
||||
<i class="fas fa-up-right-from-square"></i> 打开
|
||||
</button>
|
||||
<button class="btn btn-secondary" @click.stop="copyDirectLink(link.direct_url)">
|
||||
<i class="fas fa-copy"></i> 复制链接
|
||||
</button>
|
||||
<button class="btn" style="background: #ef4444; color: white;" @click.stop="requestDeleteDirectLink(link.id, $event)">
|
||||
<i class="fas fa-trash"></i> 删除
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7632,6 +7669,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="app.js?v=20260218002"></script>
|
||||
<link rel="stylesheet" href="workspace.css?v=20260721005">
|
||||
<script src="app.js?v=20260721005"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user