feat: refine web workspace UI

This commit is contained in:
237899745
2026-07-21 17:11:18 +08:00
parent 465be72579
commit d07c08c609
4 changed files with 1121 additions and 233 deletions

View File

@@ -2014,6 +2014,10 @@
</button>
</div>
</div>
<div v-if="files.length > 0 && !isDragging" class="files-drop-hint" aria-hidden="true">
<i class="fas fa-cloud-upload-alt"></i>
<span>拖放文件到此区域即可上传</span>
</div>
<!-- 拖拽提示层 -->
<div v-if="isDragging" class="drag-drop-overlay">
<div class="drag-drop-content">
@@ -2507,9 +2511,9 @@
</div>
</div>
<!-- 设置视图 -->
<!-- 设置视图 -->
<div v-if="isLoggedIn && currentView === 'settings'" class="main-container">
<div class="card">
<div class="card settings-page-card">
<div class="settings-page-head">
<h2 class="settings-page-title">
<i class="fas fa-sliders-h"></i> 设置中心
@@ -2517,8 +2521,20 @@
<p class="settings-page-subtitle">管理存储策略、主题外观与账号安全</p>
</div>
<nav class="settings-tabs" aria-label="设置分类">
<button v-if="user && !user.is_admin" class="settings-tab" :class="{ active: settingsTab === 'storage' }" @click="settingsTab = 'storage'">
<i class="fas fa-database"></i> 存储管理
</button>
<button class="settings-tab" :class="{ active: settingsTab === 'appearance' }" @click="settingsTab = 'appearance'">
<i class="fas fa-palette"></i> 外观与主题
</button>
<button class="settings-tab" :class="{ active: settingsTab === 'security' }" @click="settingsTab = 'security'">
<i class="fas fa-shield-alt"></i> 账号安全
</button>
</nav>
<!-- 存储管理 - 仅用户可选择 -->
<div v-if="user && !user.is_admin && storagePermission === 'user_choice'" class="settings-section settings-storage-choice" style="margin-bottom: 40px;">
<div v-if="user && !user.is_admin && storagePermission === 'user_choice'" v-show="settingsTab === 'storage'" class="settings-section settings-storage-choice" style="margin-bottom: 40px;">
<h3 class="settings-section-title" style="margin-bottom: 20px;">
<i class="fas fa-database"></i> 存储管理
</h3>
@@ -2660,7 +2676,7 @@
</div>
<!-- 本地存储信息 - 仅本地存储权限 -->
<div v-if="user && !user.is_admin && storagePermission === 'local_only'" class="settings-section settings-local-only" style="margin-bottom: 40px;">
<div v-if="user && !user.is_admin && storagePermission === 'local_only'" v-show="settingsTab === 'storage'" class="settings-section settings-local-only" style="margin-bottom: 40px;">
<h3 class="settings-section-title" style="margin-bottom: 20px;">
<i class="fas fa-hard-drive"></i> 本地存储
</h3>
@@ -2695,7 +2711,7 @@
</div>
<!-- OSS 概览 / 配置入口 - 仅OSS权限 -->
<div v-if="user && !user.is_admin && storagePermission === 'oss_only'" class="settings-section settings-oss-only" style="margin-bottom: 40px;">
<div v-if="user && !user.is_admin && storagePermission === 'oss_only'" v-show="settingsTab === 'storage'" class="settings-section settings-oss-only" style="margin-bottom: 40px;">
<h3 class="settings-section-title" style="margin-bottom: 20px;">
<i class="fas fa-cloud"></i> OSS存储
</h3>
@@ -2796,12 +2812,12 @@
</div>
<!-- 下载流量额度与统计 -->
<div v-if="user && !user.is_admin" class="settings-section settings-download-traffic" style="margin-bottom: 40px;">
<div v-if="user && !user.is_admin" v-show="settingsTab === 'storage'" class="settings-section settings-download-traffic" style="margin-bottom: 40px;">
<h3 class="settings-section-title" style="margin-bottom: 20px;">
<i class="fas fa-tachometer-alt"></i> 下载流量额度与统计
</h3>
<div class="settings-panel" style="background: var(--bg-card); border: 1px solid var(--glass-border); border-radius: 12px; padding: 18px;">
<div style="display: flex; justify-content: space-between; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 12px;">
<div class="settings-panel settings-traffic-panel" style="background: var(--bg-card); border: 1px solid var(--glass-border); border-radius: 12px; padding: 18px;">
<div class="settings-traffic-toolbar" style="display: flex; justify-content: space-between; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 12px;">
<div style="font-size: 13px; color: var(--text-secondary);">
管理员设置的下载流量限制会在这里实时展示,单位自动按 B/KB/MB/GB/TB 切换
</div>
@@ -2841,7 +2857,7 @@
<i class="fas fa-exclamation-triangle"></i> {{ downloadTrafficReport.error }}
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px; margin-bottom: 12px;">
<div class="traffic-kpi-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px; margin-bottom: 12px;">
<div style="padding: 12px; border-radius: 10px; background: rgba(59,130,246,0.1); border: 1px solid rgba(59,130,246,0.25);">
<div style="font-size: 12px; color: var(--text-muted); margin-bottom: 6px;">当前限制</div>
<div style="font-size: 18px; font-weight: 700; color: #3b82f6;">
@@ -2871,7 +2887,7 @@
</div>
</div>
<div v-if="!downloadTrafficIsUnlimited" style="margin-bottom: 12px;">
<div v-if="!downloadTrafficIsUnlimited" class="traffic-quota-progress" style="margin-bottom: 12px;">
<div style="display: flex; justify-content: space-between; font-size: 12px; color: var(--text-secondary); margin-bottom: 6px;">
<span>额度使用率</span>
<span>{{ downloadTrafficUsagePercentage }}%</span>
@@ -2881,7 +2897,7 @@
</div>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px; margin-bottom: 12px;">
<div class="traffic-summary-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px; margin-bottom: 12px;">
<div style="padding: 10px; border-radius: 8px; background: var(--bg-secondary); border: 1px solid var(--glass-border);">
<div style="font-size: 12px; color: var(--text-muted);">今天使用</div>
<div style="font-weight: 700; color: var(--text-primary); margin-top: 4px;">
@@ -2914,48 +2930,72 @@
</div>
</div>
<div style="border: 1px solid var(--glass-border); border-radius: 10px; overflow: hidden;">
<div style="padding: 10px 12px; background: var(--bg-secondary); border-bottom: 1px solid var(--glass-border); font-size: 13px; color: var(--text-secondary); display: flex; justify-content: space-between; align-items: center;">
<span>按天用量明细(最近 {{ downloadTrafficReport.days }} 天)</span>
<span v-if="downloadTrafficReport.summary?.peak_day">峰值: {{ formatReportDateLabel(downloadTrafficReport.summary.peak_day.date) }} / {{ formatBytes(downloadTrafficReport.summary.peak_day.bytes_used || 0) }}</span>
</div>
<div style="padding: 8px 12px; border-bottom: 1px solid var(--glass-border); font-size: 12px; color: var(--text-muted);">
说明:预览/在线播放同样计入下载流量OSS 明细来自访问日志,通常有 5-15 分钟延迟。
</div>
<div class="traffic-visual-grid">
<section class="traffic-chart-card">
<div class="traffic-card-head">
<div>
<strong>近 7 天流量趋势</strong>
<span>每天的下载流量变化</span>
</div>
<span v-if="downloadTrafficReport.summary?.peak_day" class="traffic-peak-pill">
峰值 {{ formatBytes(downloadTrafficReport.summary.peak_day.bytes_used || 0) }}
</span>
</div>
<div v-if="downloadTrafficReport.loading && downloadTrafficChartRows.length === 0" class="traffic-empty-state">
<i class="fas fa-spinner fa-spin"></i> 报表加载中...
</div>
<div v-else-if="downloadTrafficChartRows.length === 0" class="traffic-empty-state">暂无下载流量记录</div>
<div v-else class="traffic-chart-bars">
<div v-for="row in downloadTrafficChartRows" :key="`chart-${row.date}`" class="traffic-bar-column">
<span class="traffic-bar-value">{{ formatBytes(row.bytes_used || 0) }}</span>
<div class="traffic-bar-track" :title="formatReportDateLabel(row.date) + ' · ' + formatBytes(row.bytes_used || 0)">
<span :style="{ height: (Number(row.bytes_used || 0) / downloadTrafficChartMax * 100) + '%' }"></span>
</div>
<span class="traffic-bar-label">{{ formatReportDateLabel(row.date) }}</span>
</div>
</div>
</section>
<div v-if="downloadTrafficReport.loading && downloadTrafficDailyRowsDesc.length === 0" style="padding: 16px; text-align: center; color: var(--text-muted);">
<i class="fas fa-spinner fa-spin"></i> 报表加载中...
</div>
<div v-else-if="downloadTrafficDailyRowsDesc.length === 0" style="padding: 16px; text-align: center; color: var(--text-muted);">
暂无下载流量记录
</div>
<div v-else style="max-height: 280px; overflow: auto;">
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="background: rgba(148,163,184,0.12);">
<th style="padding: 10px; text-align: left; font-size: 12px; color: var(--text-secondary);">日期</th>
<th style="padding: 10px; text-align: right; font-size: 12px; color: var(--text-secondary);">下载流量</th>
<th style="padding: 10px; text-align: right; font-size: 12px; color: var(--text-secondary);">下载次数</th>
</tr>
</thead>
<tbody>
<tr v-for="row in downloadTrafficDailyRowsDesc" :key="row.date" style="border-top: 1px solid var(--glass-border);">
<td style="padding: 9px 10px; color: var(--text-primary); font-size: 13px;">{{ formatReportDateLabel(row.date) }}</td>
<td style="padding: 9px 10px; text-align: right; color: var(--text-primary); font-size: 13px; font-weight: 600;">{{ formatBytes(row.bytes_used || 0) }}</td>
<td style="padding: 9px 10px; text-align: right; color: var(--text-secondary); font-size: 13px;">{{ row.download_count || 0 }}</td>
</tr>
</tbody>
</table>
</div>
<section class="traffic-table-card">
<div class="traffic-card-head">
<div>
<strong>按天用量明细</strong>
<span>最近 {{ downloadTrafficReport.days }} 天</span>
</div>
</div>
<div v-if="downloadTrafficDailyRowsDesc.length === 0" class="traffic-empty-state">暂无下载流量记录</div>
<div v-else class="traffic-table-scroll">
<table>
<thead>
<tr>
<th>日期</th>
<th>下载流量</th>
<th>次数</th>
</tr>
</thead>
<tbody>
<tr v-for="row in downloadTrafficDailyRowsDesc.slice(0, 7)" :key="row.date">
<td>{{ formatReportDateLabel(row.date) }}</td>
<td>{{ formatBytes(row.bytes_used || 0) }}</td>
<td>{{ row.download_count || 0 }}</td>
</tr>
</tbody>
</table>
</div>
</section>
</div>
<div class="traffic-report-note">
<i class="fas fa-info-circle"></i>
预览和在线播放同样计入下载流量OSS 明细来自访问日志,通常有 5-15 分钟延迟。
</div>
</div>
</div>
<!-- 在线设备 -->
<h3 class="settings-section-title settings-section-gap" style="margin: 40px 0 20px 0;">
<h3 v-show="settingsTab === 'security'" class="settings-section-title settings-section-gap" style="margin: 40px 0 20px 0;">
<i class="fas fa-laptop-house"></i> 在线设备
</h3>
<div class="settings-panel" style="background: var(--bg-card); padding: 20px; border-radius: 12px; border: 1px solid var(--glass-border); margin-bottom: 30px;">
<div v-show="settingsTab === 'security'" class="settings-panel settings-devices-panel" style="background: var(--bg-card); padding: 20px; border-radius: 12px; border: 1px solid var(--glass-border); margin-bottom: 30px;">
<div style="display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 12px;">
<div style="color: var(--text-secondary); font-size: 13px;">
可查看当前账号已登录设备,并支持远程强制下线
@@ -3013,8 +3053,8 @@
</div>
<!-- 界面设置 -->
<h3 class="settings-section-title settings-section-gap" style="margin: 40px 0 20px 0;"><i class="fas fa-palette"></i> 界面设置</h3>
<div class="settings-panel settings-theme-panel" style="background: var(--bg-card); padding: 20px; border-radius: 12px; border: 1px solid var(--glass-border); margin-bottom: 30px;">
<h3 v-show="settingsTab === 'appearance'" class="settings-section-title settings-section-gap" style="margin: 40px 0 20px 0;"><i class="fas fa-palette"></i> 界面设置</h3>
<div v-show="settingsTab === 'appearance'" class="settings-panel settings-theme-panel" style="background: var(--bg-card); padding: 20px; border-radius: 12px; border: 1px solid var(--glass-border); margin-bottom: 30px;">
<div style="margin-bottom: 15px;">
<span style="font-weight: 600; color: var(--text-primary);">主题模式</span>
<span style="color: var(--text-secondary); font-size: 13px; margin-left: 10px;">选择你喜欢的界面风格</span>
@@ -3049,9 +3089,9 @@
</div>
<!-- 账号设置 -->
<h3 class="settings-section-title settings-section-gap" style="margin: 40px 0 20px 0;"><i class="fas fa-user-cog"></i> 账号设置</h3>
<h3 v-show="settingsTab === 'security'" class="settings-section-title settings-section-gap" style="margin: 40px 0 20px 0;"><i class="fas fa-user-cog"></i> 账号设置</h3>
<div class="settings-panel settings-account-panel">
<div v-show="settingsTab === 'security'" class="settings-panel settings-account-panel">
<!-- 管理员可以改用户名 -->
<form v-if="user && user.is_admin" @submit.prevent="updateUsername" style="margin-bottom: 30px;">
<div class="form-group">
@@ -3091,204 +3131,166 @@
<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>
<button class="btn btn-primary" @click="switchView('files')">
<i class="fas fa-plus"></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>
<nav class="share-resource-tabs" aria-label="分享资源分类">
<button :class="{ active: shareResourceTab === 'shares' }" @click="shareResourceTab = 'shares'">
<i class="fas fa-share-alt"></i> 分享链接
<span>{{ shares.length }}</span>
</button>
<button :class="{ active: shareResourceTab === 'directLinks' }" @click="shareResourceTab = 'directLinks'">
<i class="fas fa-link"></i> 文件直链
<span>{{ directLinks.length }}</span>
</button>
</nav>
<!-- 筛选/搜索 -->
<div class="share-toolbar shares-toolbar">
<div class="share-search-field">
<i class="fas fa-search"></i>
<input type="text" v-model="shareFilters.keyword" placeholder="搜索文件名、链接或分享码">
<section class="share-resource-card">
<div class="share-inline-stats">
<span><i class="fas fa-layer-group"></i> 共享资源 <strong>{{ shares.length + directLinks.length }}</strong></span>
<span><i class="fas fa-eye"></i> 累计访问 <strong>{{ shareTotalViews }}</strong></span>
<span><i class="fas fa-shield-alt"></i> 状态 <strong>运行正常</strong></span>
</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" aria-label="分享状态">
<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" aria-label="分享排序">
<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>
<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 class="share-toolbar shares-toolbar">
<div class="share-search-field">
<i class="fas fa-search"></i>
<input type="text" v-model="shareFilters.keyword" :placeholder="shareResourceTab === 'shares' ? '搜索文件名、链接或分享码' : '搜索文件名或直链地址'">
</div>
<span class="share-section-count">{{ filteredShares.length }} 项</span>
<template v-if="shareResourceTab === 'shares'">
<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" aria-label="分享状态">
<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" aria-label="分享排序">
<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>
</template>
</div>
<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 v-if="shareResourceTab === 'shares'" class="share-resource-panel">
<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>
<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>
<!-- 直链管理(独立于普通分享) -->
<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>
<span class="share-section-count">{{ filteredShares.length }} 项</span>
</div>
<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 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>
<span class="share-section-count">{{ filteredDirectLinks.length }} 项</span>
</div>
<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="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 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 v-else class="share-resource-panel 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="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="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 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>
</div>
</section>
</div>
@@ -7669,7 +7671,7 @@
}
</style>
<link rel="stylesheet" href="workspace.css?v=20260721005">
<script src="app.js?v=20260721005"></script>
<link rel="stylesheet" href="workspace.css?v=20260721006">
<script src="app.js?v=20260721006"></script>
</body>
</html>