feat: refine web workspace UI
This commit is contained in:
8
design-qa.md
Normal file
8
design-qa.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Design QA
|
||||
|
||||
final result: passed
|
||||
|
||||
- File workspace: verified at 1280x720 with a full-height drop target and unboxed icon layout.
|
||||
- Share center: verified resource tabs, filters, compact table, and direct-link empty state.
|
||||
- Settings center: verified storage, appearance, and security tabs plus the seven-day traffic chart.
|
||||
- Runtime: no browser console warnings or errors during the final visual pass.
|
||||
@@ -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>
|
||||
|
||||
@@ -29,6 +29,8 @@ createApp({
|
||||
isLogin: true,
|
||||
fileViewMode: 'grid', // 文件显示模式: grid 大图标, list 列表
|
||||
shareViewMode: 'list', // 分享显示模式: grid 大图标, list 列表
|
||||
shareResourceTab: 'shares', // 分享中心资源标签: shares / directLinks
|
||||
settingsTab: 'storage', // 设置中心标签: storage / appearance / security
|
||||
debugMode: false, // 调试模式(管理员可切换)
|
||||
adminTab: initialAdminTab, // 管理员页面当前标签:overview, settings, monitor, users
|
||||
|
||||
@@ -565,6 +567,24 @@ createApp({
|
||||
return [...rows].reverse();
|
||||
},
|
||||
|
||||
downloadTrafficChartRows() {
|
||||
const rows = Array.isArray(this.downloadTrafficReport?.daily)
|
||||
? this.downloadTrafficReport.daily
|
||||
: [];
|
||||
return rows.slice(-7);
|
||||
},
|
||||
|
||||
downloadTrafficChartMax() {
|
||||
return Math.max(
|
||||
1,
|
||||
...this.downloadTrafficChartRows.map(row => Number(row?.bytes_used || 0))
|
||||
);
|
||||
},
|
||||
|
||||
shareTotalViews() {
|
||||
return this.shares.reduce((total, share) => total + Number(share?.view_count || 0), 0);
|
||||
},
|
||||
|
||||
// 存储类型显示文本
|
||||
storageTypeText() {
|
||||
return this.storageType === 'local' ? '本地存储' : 'OSS存储';
|
||||
@@ -4209,6 +4229,9 @@ handleDragLeave(e) {
|
||||
}
|
||||
break;
|
||||
case 'settings':
|
||||
if (this.user?.is_admin && this.settingsTab === 'storage') {
|
||||
this.settingsTab = 'appearance';
|
||||
}
|
||||
this.loadOnlineDevices();
|
||||
if (this.user && !this.user.is_admin) {
|
||||
this.loadOssConfig();
|
||||
|
||||
@@ -1837,3 +1837,858 @@ body.enterprise-netdisk .share-list-actions .btn {
|
||||
animation-iteration-count: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Selected workspace direction: compact file canvas, tabbed shares and settings. */
|
||||
body.enterprise-netdisk .files-view-card {
|
||||
height: calc(100vh - 62px);
|
||||
min-height: 680px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-page-header {
|
||||
flex: 0 0 auto;
|
||||
padding: 22px 26px 20px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-page-header .workspace-page-heading h1 {
|
||||
font-size: 27px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-content-shell {
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 16px 24px 22px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-command-bar,
|
||||
body.enterprise-netdisk .files-search-wrap,
|
||||
body.enterprise-netdisk .files-list-heading {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-search-wrap {
|
||||
margin-top: 11px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-search-row {
|
||||
padding: 7px;
|
||||
border-radius: 11px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-search-field,
|
||||
body.enterprise-netdisk .files-search-type,
|
||||
body.enterprise-netdisk .files-search-button {
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-list-heading {
|
||||
min-height: 43px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-container {
|
||||
min-height: 360px;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 8px 10px 54px;
|
||||
border-radius: 12px;
|
||||
background:
|
||||
linear-gradient(rgba(255, 255, 255, 0.96), rgba(255, 255, 255, 0.96)),
|
||||
radial-gradient(circle at 50% 42%, rgba(40, 120, 255, 0.08), transparent 22rem);
|
||||
transition: border-color 160ms ease, background 160ms ease, box-shadow 160ms ease;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-container.drag-over {
|
||||
border-color: #72a8ff;
|
||||
background: #f5f9ff;
|
||||
box-shadow: inset 0 0 0 2px rgba(40, 120, 255, 0.12);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-content-shell .file-grid {
|
||||
min-height: 100%;
|
||||
padding: 18px 10px;
|
||||
align-content: start;
|
||||
justify-content: start;
|
||||
grid-template-columns: repeat(auto-fill, 138px);
|
||||
grid-auto-rows: 132px;
|
||||
gap: 8px 14px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-content-shell .file-grid-item {
|
||||
width: 138px;
|
||||
min-height: 132px;
|
||||
padding: 10px 8px 9px;
|
||||
justify-content: flex-start;
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-content-shell .file-grid-item:hover {
|
||||
border: 0;
|
||||
background: #f3f7fc;
|
||||
box-shadow: none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-content-shell .file-grid-item:active {
|
||||
background: #eaf2ff;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .file-icon {
|
||||
min-height: 68px;
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .file-grid-type-icon {
|
||||
font-size: 54px;
|
||||
filter: none;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .file-type-default {
|
||||
color: #6c7d96 !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .file-type-folder {
|
||||
color: #e8ae42 !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .file-type-pdf {
|
||||
color: #e15d62 !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .file-type-word {
|
||||
color: #397bd8 !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .file-type-excel {
|
||||
color: #2b9c70 !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .file-type-archive {
|
||||
color: #b07d50 !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .file-type-audio {
|
||||
color: #7886c7 !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .file-thumbnail {
|
||||
width: 68px;
|
||||
height: 62px;
|
||||
border-radius: 9px;
|
||||
box-shadow: 0 5px 14px rgba(38, 58, 90, 0.09);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-content-shell .file-name {
|
||||
min-height: 20px;
|
||||
color: #10203a;
|
||||
font-size: 13px;
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-content-shell .file-size {
|
||||
margin-top: 2px;
|
||||
color: #72839d;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-drop-hint {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 14px;
|
||||
z-index: 2;
|
||||
padding: 6px 11px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
border: 1px solid #e1e9f5;
|
||||
border-radius: 999px;
|
||||
color: #8492a8;
|
||||
background: rgba(249, 251, 254, 0.92);
|
||||
font-size: 10px;
|
||||
pointer-events: none;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-drop-hint i {
|
||||
color: #5f97ec;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .drag-drop-overlay {
|
||||
position: absolute !important;
|
||||
inset: 0 !important;
|
||||
z-index: 25 !important;
|
||||
min-height: 100% !important;
|
||||
display: grid !important;
|
||||
place-items: center !important;
|
||||
border: 0 !important;
|
||||
border-radius: 11px !important;
|
||||
background: rgba(240, 247, 255, 0.96) !important;
|
||||
backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .drag-drop-content {
|
||||
padding: 26px 34px;
|
||||
text-align: center;
|
||||
border: 1px dashed #79aaff;
|
||||
border-radius: 18px;
|
||||
background: #fff;
|
||||
box-shadow: 0 18px 40px rgba(32, 84, 165, 0.12);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .drag-drop-content > i {
|
||||
margin-bottom: 12px !important;
|
||||
color: var(--ww-blue) !important;
|
||||
font-size: 44px !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .drag-drop-content > div:first-of-type {
|
||||
margin-bottom: 4px !important;
|
||||
font-size: 17px !important;
|
||||
}
|
||||
|
||||
/* Shares */
|
||||
body.enterprise-netdisk .shares-page-card {
|
||||
min-height: calc(100vh - 62px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .shares-page-head {
|
||||
padding: 24px 28px 20px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-tabs {
|
||||
min-height: 55px;
|
||||
padding: 0 28px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 26px;
|
||||
border-bottom: 1px solid var(--ww-line);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-tabs button {
|
||||
position: relative;
|
||||
min-height: 54px;
|
||||
padding: 0 2px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border: 0;
|
||||
color: #74839a;
|
||||
background: transparent;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-tabs button::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
height: 3px;
|
||||
border-radius: 3px 3px 0 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-tabs button.active {
|
||||
color: var(--ww-blue);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-tabs button.active::after {
|
||||
background: var(--ww-blue);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-tabs button > span {
|
||||
min-width: 21px;
|
||||
height: 20px;
|
||||
padding: 0 6px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 999px;
|
||||
color: #718097;
|
||||
background: #edf2f8;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-tabs button.active > span {
|
||||
color: #1763e6;
|
||||
background: #e8f1ff;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-card {
|
||||
min-height: 0;
|
||||
margin: 22px 28px 28px;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--ww-line);
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-inline-stats {
|
||||
min-height: 52px;
|
||||
padding: 0 17px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
border-bottom: 1px solid var(--ww-line);
|
||||
color: #74839a;
|
||||
background: #fbfcfe;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-inline-stats > span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-inline-stats i {
|
||||
color: #7896c4;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-inline-stats strong {
|
||||
color: #23344f;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-card .share-toolbar {
|
||||
margin: 14px 16px;
|
||||
padding: 7px;
|
||||
border-radius: 11px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-card .share-search-field,
|
||||
body.enterprise-netdisk .share-resource-card .share-toolbar select {
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-card .share-search-field input,
|
||||
body.enterprise-netdisk .share-resource-card .share-search-field input:focus {
|
||||
min-height: 34px;
|
||||
padding: 0 !important;
|
||||
border: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
outline: 0 !important;
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-panel {
|
||||
min-height: 260px;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
border-top: 1px solid var(--ww-line);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-panel .share-section-head {
|
||||
min-height: 58px;
|
||||
padding: 9px 16px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-panel .share-section-icon {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
flex-basis: 34px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-panel .share-list-table td {
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
/* Settings */
|
||||
body.enterprise-netdisk .settings-page-card {
|
||||
min-height: calc(100vh - 62px);
|
||||
padding: 0 !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-page-head {
|
||||
padding: 25px 30px 21px;
|
||||
border-bottom: 1px solid var(--ww-line);
|
||||
background:
|
||||
radial-gradient(circle at 92% 0%, rgba(40, 120, 255, 0.07), transparent 19rem),
|
||||
#fff;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-page-title {
|
||||
margin: 0;
|
||||
color: var(--ww-ink);
|
||||
font-size: 27px;
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-page-title i {
|
||||
margin-right: 8px;
|
||||
color: var(--ww-blue);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-page-subtitle {
|
||||
margin: 6px 0 0;
|
||||
color: var(--ww-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-tabs {
|
||||
min-height: 57px;
|
||||
padding: 0 30px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 28px;
|
||||
border-bottom: 1px solid var(--ww-line);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-tab {
|
||||
position: relative;
|
||||
min-height: 56px;
|
||||
padding: 0 2px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border: 0;
|
||||
color: #74839a;
|
||||
background: transparent;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-tab::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
height: 3px;
|
||||
border-radius: 3px 3px 0 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-tab.active {
|
||||
color: var(--ww-blue);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-tab.active::after {
|
||||
background: var(--ww-blue);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-page-card > .settings-section,
|
||||
body.enterprise-netdisk .settings-page-card > .settings-section-title,
|
||||
body.enterprise-netdisk .settings-page-card > .settings-panel {
|
||||
margin-right: 28px !important;
|
||||
margin-left: 28px !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-page-card > .settings-section {
|
||||
margin-top: 24px !important;
|
||||
margin-bottom: 28px !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-page-card > .settings-section-title {
|
||||
margin-top: 24px !important;
|
||||
margin-bottom: 13px !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-section-title {
|
||||
color: #24354f;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-section-title i {
|
||||
width: 19px;
|
||||
color: var(--ww-blue);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-storage-choice > .settings-section-title,
|
||||
body.enterprise-netdisk .settings-download-traffic > .settings-section-title,
|
||||
body.enterprise-netdisk .settings-local-only > .settings-section-title,
|
||||
body.enterprise-netdisk .settings-oss-only > .settings-section-title {
|
||||
margin-bottom: 13px !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-storage-panel,
|
||||
body.enterprise-netdisk .settings-local-panel,
|
||||
body.enterprise-netdisk .settings-oss-panel,
|
||||
body.enterprise-netdisk .settings-traffic-panel,
|
||||
body.enterprise-netdisk .settings-devices-panel,
|
||||
body.enterprise-netdisk .settings-theme-panel,
|
||||
body.enterprise-netdisk .settings-account-panel {
|
||||
border: 1px solid var(--ww-line) !important;
|
||||
border-radius: 14px !important;
|
||||
background: #fff !important;
|
||||
box-shadow: 0 10px 30px rgba(31, 49, 78, 0.06) !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-storage-panel {
|
||||
padding: 18px !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-storage-switch {
|
||||
padding: 9px !important;
|
||||
border-color: #e6ebf3 !important;
|
||||
background: #f8fafd !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-storage-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
|
||||
gap: 12px !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-storage-option {
|
||||
padding: 15px !important;
|
||||
border-color: var(--ww-line) !important;
|
||||
border-radius: 11px !important;
|
||||
background: #fbfcfe !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-storage-option:has(button:disabled) {
|
||||
border-color: #b9d1f6 !important;
|
||||
background: #f6f9ff !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-inline-tip {
|
||||
border: 0 !important;
|
||||
color: #65758d !important;
|
||||
background: #f7f9fc !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-traffic-panel {
|
||||
padding: 18px !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-traffic-toolbar {
|
||||
margin-bottom: 14px !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .download-traffic-range-actions {
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .download-traffic-range-btn {
|
||||
min-height: 32px;
|
||||
padding: 0 10px;
|
||||
border-radius: 8px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .download-traffic-range-btn-active {
|
||||
color: #fff !important;
|
||||
border-color: var(--ww-blue) !important;
|
||||
background: var(--ww-blue) !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-kpi-grid {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
|
||||
gap: 9px !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-kpi-grid > div {
|
||||
min-height: 84px;
|
||||
padding: 12px 13px !important;
|
||||
border-radius: 10px !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-quota-progress {
|
||||
padding: 8px 2px 4px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-summary-grid {
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr)) !important;
|
||||
gap: 8px !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-summary-grid > div {
|
||||
padding: 9px 10px !important;
|
||||
border-color: #e5eaf2 !important;
|
||||
background: #fafbfd !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-visual-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.55fr) minmax(320px, 0.8fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-chart-card,
|
||||
body.enterprise-netdisk .traffic-table-card {
|
||||
min-width: 0;
|
||||
padding: 13px;
|
||||
border: 1px solid var(--ww-line);
|
||||
border-radius: 11px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-card-head {
|
||||
min-height: 39px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-card-head > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-card-head strong {
|
||||
color: #263750;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-card-head span {
|
||||
color: #8a98ab;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-peak-pill {
|
||||
padding: 4px 7px;
|
||||
border-radius: 999px;
|
||||
color: #1763e6 !important;
|
||||
background: #eaf2ff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-chart-bars {
|
||||
height: 178px;
|
||||
padding: 15px 4px 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, minmax(30px, 1fr));
|
||||
align-items: end;
|
||||
gap: 8px;
|
||||
border-bottom: 1px solid #e6ebf2;
|
||||
background: repeating-linear-gradient(to bottom, transparent 0, transparent 43px, #f0f3f8 44px);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-bar-column {
|
||||
height: 160px;
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
grid-template-rows: 18px 116px 20px;
|
||||
align-items: end;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-bar-value,
|
||||
body.enterprise-netdisk .traffic-bar-label {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
color: #8795a9;
|
||||
font-size: 8px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-bar-track {
|
||||
width: min(34px, 68%);
|
||||
height: 112px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
border-radius: 6px 6px 0 0;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-bar-track > span {
|
||||
width: 100%;
|
||||
min-height: 4px;
|
||||
display: block;
|
||||
border-radius: 6px 6px 2px 2px;
|
||||
background: linear-gradient(180deg, #4f91ff 0%, #2878ff 100%);
|
||||
box-shadow: 0 5px 12px rgba(40, 120, 255, 0.16);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-table-scroll {
|
||||
max-height: 193px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-table-scroll table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-table-scroll th,
|
||||
body.enterprise-netdisk .traffic-table-scroll td {
|
||||
height: 29px;
|
||||
padding: 0 7px;
|
||||
border-bottom: 1px solid #edf1f6;
|
||||
color: #53637a;
|
||||
font-size: 9px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-table-scroll th {
|
||||
color: #8a98ab;
|
||||
background: #fafbfd;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-table-scroll th:first-child,
|
||||
body.enterprise-netdisk .traffic-table-scroll td:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-table-scroll td:nth-child(2) {
|
||||
color: #263750;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-empty-state {
|
||||
min-height: 168px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 7px;
|
||||
color: var(--ww-muted);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-report-note {
|
||||
margin-top: 9px;
|
||||
color: #8492a7;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-report-note i {
|
||||
margin-right: 5px;
|
||||
color: #6797df;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-theme-panel,
|
||||
body.enterprise-netdisk .settings-devices-panel,
|
||||
body.enterprise-netdisk .settings-account-panel {
|
||||
padding: 18px !important;
|
||||
margin-bottom: 28px !important;
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
body.enterprise-netdisk .traffic-kpi-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-summary-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-visual-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
body.enterprise-netdisk .files-view-card,
|
||||
body.enterprise-netdisk .shares-page-card,
|
||||
body.enterprise-netdisk .settings-page-card {
|
||||
height: auto;
|
||||
min-height: calc(100vh - 94px);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-content-shell .file-grid {
|
||||
grid-template-columns: repeat(auto-fill, 128px);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-content-shell .file-grid-item {
|
||||
width: 128px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-storage-grid {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
body.enterprise-netdisk .files-page-header,
|
||||
body.enterprise-netdisk .shares-page-head,
|
||||
body.enterprise-netdisk .settings-page-head {
|
||||
padding: 19px 17px 17px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-content-shell {
|
||||
padding: 13px 14px 16px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-content-shell .file-grid {
|
||||
padding-inline: 0;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-content-shell .file-grid-item {
|
||||
width: auto;
|
||||
min-height: 126px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .files-drop-hint {
|
||||
width: max-content;
|
||||
max-width: calc(100% - 28px);
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-tabs,
|
||||
body.enterprise-netdisk .settings-tabs {
|
||||
padding: 0 17px;
|
||||
gap: 18px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-card {
|
||||
margin: 16px 14px 18px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-inline-stats {
|
||||
min-height: 48px;
|
||||
gap: 12px;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-card .share-toolbar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .share-resource-card .share-toolbar select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .settings-page-card > .settings-section,
|
||||
body.enterprise-netdisk .settings-page-card > .settings-section-title,
|
||||
body.enterprise-netdisk .settings-page-card > .settings-panel {
|
||||
margin-right: 14px !important;
|
||||
margin-left: 14px !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-kpi-grid,
|
||||
body.enterprise-netdisk .traffic-summary-grid {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-chart-bars {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
body.enterprise-netdisk .traffic-bar-value {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user