Compare commits
5 Commits
bd46227e49
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0535c7208 | ||
|
|
c92ad8b096 | ||
|
|
e809de892d | ||
|
|
8095b2391e | ||
|
|
c529e6c51b |
@@ -1802,7 +1802,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- 导航栏 -->
|
<!-- 导航栏 -->
|
||||||
|
|
||||||
<div class="navbar" v-if="isLoggedIn">
|
<nav class="navbar" v-if="isLoggedIn" aria-label="主导航">
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<span class="brand-mark"><i class="fas fa-cloud"></i></span>
|
<span class="brand-mark"><i class="fas fa-cloud"></i></span>
|
||||||
<span class="brand-copy">
|
<span class="brand-copy">
|
||||||
@@ -1812,19 +1812,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="nav-section-label">工作台</div>
|
<div class="nav-section-label">工作台</div>
|
||||||
<div v-if="user && !user.is_admin" class="nav-item" :class="{active: currentView === 'files'}" @click="switchView('files')">
|
<button v-if="user && !user.is_admin" type="button" class="nav-item" :class="{active: currentView === 'files'}" :aria-current="currentView === 'files' ? 'page' : null" @click="switchView('files')">
|
||||||
<i class="fas fa-folder"></i> 我的文件
|
<i class="fas fa-folder"></i><span class="nav-item-label">文件</span>
|
||||||
</div>
|
</button>
|
||||||
<div v-if="user && !user.is_admin" class="nav-item" :class="{active: currentView === 'shares'}" @click="switchView('shares')">
|
<button v-if="user && !user.is_admin" type="button" class="nav-item" :class="{active: currentView === 'shares'}" :aria-current="currentView === 'shares' ? 'page' : null" @click="switchView('shares')">
|
||||||
<i class="fas fa-share-alt"></i> 我的分享
|
<i class="fas fa-share-alt"></i><span class="nav-item-label">分享</span>
|
||||||
</div>
|
</button>
|
||||||
<div v-if="user && user.is_admin" class="nav-item" :class="{active: currentView === 'admin'}" @click="switchView('admin')">
|
<button v-if="user && user.is_admin" type="button" class="nav-item" :class="{active: currentView === 'admin'}" :aria-current="currentView === 'admin' ? 'page' : null" @click="switchView('admin')">
|
||||||
<i class="fas fa-user-shield"></i> 管理员
|
<i class="fas fa-user-shield"></i><span class="nav-item-label">管理</span>
|
||||||
</div>
|
</button>
|
||||||
<div class="nav-section-label nav-service-label">服务</div>
|
<div class="nav-section-label nav-service-label">服务</div>
|
||||||
<div class="nav-item" :class="{active: currentView === 'settings'}" @click="switchView('settings')">
|
<button type="button" class="nav-item" :class="{active: currentView === 'settings'}" :aria-current="currentView === 'settings' ? 'page' : null" @click="switchView('settings')">
|
||||||
<i class="fas fa-cog"></i> 设置
|
<i class="fas fa-cog"></i><span class="nav-item-label">设置</span>
|
||||||
</div>
|
</button>
|
||||||
<button class="btn btn-secondary navbar-download-btn" :disabled="desktopClientDownloading" @click="downloadDesktopClient">
|
<button class="btn btn-secondary navbar-download-btn" :disabled="desktopClientDownloading" @click="downloadDesktopClient">
|
||||||
<i :class="desktopClientDownloading ? 'fas fa-spinner fa-spin' : 'fas fa-download'"></i>
|
<i :class="desktopClientDownloading ? 'fas fa-spinner fa-spin' : 'fas fa-download'"></i>
|
||||||
{{ desktopClientDownloading ? '准备中...' : '下载客户端' }}
|
{{ desktopClientDownloading ? '准备中...' : '下载客户端' }}
|
||||||
@@ -1837,15 +1837,15 @@
|
|||||||
<strong>{{ user.username }}</strong>
|
<strong>{{ user.username }}</strong>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-danger navbar-logout-btn" @click="logout" title="退出登录">
|
<button class="btn btn-danger navbar-logout-btn" @click="logout" title="退出登录" aria-label="退出登录">
|
||||||
<i class="fas fa-power-off"></i> 退出
|
<i class="fas fa-power-off"></i><span class="nav-item-label">退出</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</nav>
|
||||||
|
|
||||||
<!-- 文件视图 -->
|
<!-- 文件视图 -->
|
||||||
<div v-if="isLoggedIn && currentView === 'files'" class="main-container">
|
<div v-if="isLoggedIn && currentView === 'files'" class="main-container files-main-container">
|
||||||
<div class="card files-view-card">
|
<div class="card files-view-card">
|
||||||
|
|
||||||
<header class="workspace-page-header files-page-header">
|
<header class="workspace-page-header files-page-header">
|
||||||
@@ -1909,19 +1909,22 @@
|
|||||||
<!-- 文件列表 -->
|
<!-- 文件列表 -->
|
||||||
<div v-else class="files-content-shell">
|
<div v-else class="files-content-shell">
|
||||||
<div class="files-command-bar">
|
<div class="files-command-bar">
|
||||||
<div class="files-command-primary">
|
<div class="files-command-primary files-command-primary-desktop">
|
||||||
<button class="btn btn-primary" @click="$refs.fileUploadInput.click()">
|
<button class="btn btn-primary" @click="$refs.fileUploadInput.click()">
|
||||||
<i class="fas fa-upload"></i> 上传文件
|
<i class="fas fa-upload"></i><span class="btn-label">上传文件</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-secondary" @click="showCreateFolderModal = true">
|
<button class="btn btn-secondary" @click="showCreateFolderModal = true">
|
||||||
<i class="fas fa-folder-plus"></i> 新建文件夹
|
<i class="fas fa-folder-plus"></i><span class="btn-label">新建文件夹</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<button type="button" class="btn btn-primary mobile-file-create-trigger" @click="openMobileCreateSheet($event)" aria-label="新建或上传">
|
||||||
|
<i class="fas fa-plus"></i><span>新建</span>
|
||||||
|
</button>
|
||||||
<div class="view-toggle-group files-view-toggle" aria-label="文件视图切换">
|
<div class="view-toggle-group files-view-toggle" aria-label="文件视图切换">
|
||||||
<button class="btn" :class="fileViewMode === 'grid' ? 'btn-primary' : 'btn-secondary'" @click="fileViewMode = 'grid'" title="大图标视图">
|
<button class="btn" :class="fileViewMode === 'grid' ? 'btn-primary' : 'btn-secondary'" @click="setFileViewMode('grid')" :aria-pressed="fileViewMode === 'grid'" aria-label="大图标视图" title="大图标视图">
|
||||||
<i class="fas fa-th-large"></i><span>大图标</span>
|
<i class="fas fa-th-large"></i><span>大图标</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" :class="fileViewMode === 'list' ? 'btn-primary' : 'btn-secondary'" @click="fileViewMode = 'list'" title="列表视图">
|
<button class="btn" :class="fileViewMode === 'list' ? 'btn-primary' : 'btn-secondary'" @click="setFileViewMode('list')" :aria-pressed="fileViewMode === 'list'" aria-label="列表视图" title="列表视图">
|
||||||
<i class="fas fa-list"></i><span>列表</span>
|
<i class="fas fa-list"></i><span>列表</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1932,14 +1935,15 @@
|
|||||||
<div class="files-search-field">
|
<div class="files-search-field">
|
||||||
<i class="fas fa-search"></i>
|
<i class="fas fa-search"></i>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="search"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
v-model="globalSearchKeyword"
|
v-model="globalSearchKeyword"
|
||||||
@input="triggerGlobalSearch"
|
@input="triggerGlobalSearch"
|
||||||
@focus="globalSearchVisible = !!globalSearchKeyword"
|
@focus="globalSearchVisible = !!globalSearchKeyword"
|
||||||
placeholder="搜索全部目录中的文件或文件夹">
|
aria-label="搜索文件或文件夹"
|
||||||
|
placeholder="搜索文件或文件夹">
|
||||||
</div>
|
</div>
|
||||||
<select class="form-input files-search-type" v-model="globalSearchType" @change="runGlobalSearch">
|
<select class="form-input files-search-type" v-model="globalSearchType" @change="runGlobalSearch" aria-label="搜索类型">
|
||||||
<option value="all">全部</option>
|
<option value="all">全部</option>
|
||||||
<option value="file">仅文件</option>
|
<option value="file">仅文件</option>
|
||||||
<option value="directory">仅文件夹</option>
|
<option value="directory">仅文件夹</option>
|
||||||
@@ -2114,6 +2118,7 @@
|
|||||||
@touchstart.stop
|
@touchstart.stop
|
||||||
@touchmove.stop
|
@touchmove.stop
|
||||||
@touchend.stop
|
@touchend.stop
|
||||||
|
aria-label="更多操作"
|
||||||
title="更多操作">
|
title="更多操作">
|
||||||
<i class="fas fa-ellipsis-h"></i>
|
<i class="fas fa-ellipsis-h"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -2510,7 +2515,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 设置视图 -->
|
<!-- 设置视图 -->
|
||||||
<div v-if="isLoggedIn && currentView === 'settings'" class="main-container">
|
<div v-if="isLoggedIn && currentView === 'settings'" class="main-container settings-main-container">
|
||||||
<div class="card settings-page-card">
|
<div class="card settings-page-card">
|
||||||
<div class="settings-page-head">
|
<div class="settings-page-head">
|
||||||
<h2 class="settings-page-title">
|
<h2 class="settings-page-title">
|
||||||
@@ -3120,7 +3125,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 分享视图 -->
|
<!-- 分享视图 -->
|
||||||
<div v-if="isLoggedIn && currentView === 'shares'" class="main-container">
|
<div v-if="isLoggedIn && currentView === 'shares'" class="main-container shares-main-container">
|
||||||
<div class="card shares-page-card">
|
<div class="card shares-page-card">
|
||||||
<header class="workspace-page-header shares-page-head">
|
<header class="workspace-page-header shares-page-head">
|
||||||
<div class="workspace-page-heading">
|
<div class="workspace-page-heading">
|
||||||
@@ -3129,21 +3134,21 @@
|
|||||||
<p>集中查看分享状态、访问数据和文件直链。</p>
|
<p>集中查看分享状态、访问数据和文件直链。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="shares-page-actions">
|
<div class="shares-page-actions">
|
||||||
<button class="btn btn-secondary" @click="refreshShareResources">
|
<button type="button" class="btn btn-secondary share-refresh-btn" @click="refreshShareResources" aria-label="刷新分享资源">
|
||||||
<i class="fas fa-sync-alt"></i> 刷新
|
<i class="fas fa-sync-alt"></i><span class="share-action-label">刷新</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-primary" @click="switchView('files')">
|
<button type="button" class="btn btn-primary share-create-btn" @click="switchView('files')" aria-label="创建分享" title="创建分享">
|
||||||
<i class="fas fa-plus"></i> 创建分享
|
<i class="fas fa-plus"></i><span class="share-action-label">创建分享</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<nav class="share-resource-tabs" aria-label="分享资源分类">
|
<nav class="share-resource-tabs" aria-label="分享资源分类">
|
||||||
<button :class="{ active: shareResourceTab === 'shares' }" @click="shareResourceTab = 'shares'">
|
<button type="button" :class="{ active: shareResourceTab === 'shares' }" @click="shareResourceTab = 'shares'">
|
||||||
<i class="fas fa-share-alt"></i> 分享链接
|
<i class="fas fa-share-alt"></i> 分享链接
|
||||||
<span>{{ shares.length }}</span>
|
<span>{{ shares.length }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button :class="{ active: shareResourceTab === 'directLinks' }" @click="shareResourceTab = 'directLinks'">
|
<button type="button" :class="{ active: shareResourceTab === 'directLinks' }" @click="shareResourceTab = 'directLinks'">
|
||||||
<i class="fas fa-link"></i> 文件直链
|
<i class="fas fa-link"></i> 文件直链
|
||||||
<span>{{ directLinks.length }}</span>
|
<span>{{ directLinks.length }}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -3156,12 +3161,12 @@
|
|||||||
<span><i class="fas fa-shield-alt"></i> 状态 <strong>运行正常</strong></span>
|
<span><i class="fas fa-shield-alt"></i> 状态 <strong>运行正常</strong></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="share-toolbar shares-toolbar">
|
<div class="share-toolbar shares-toolbar" :class="{ 'share-toolbar-direct': shareResourceTab === 'directLinks' }">
|
||||||
<div class="share-search-field">
|
<div class="share-search-field">
|
||||||
<i class="fas fa-search"></i>
|
<i class="fas fa-search"></i>
|
||||||
<input type="text" v-model="shareFilters.keyword" :placeholder="shareResourceTab === 'shares' ? '搜索文件名、链接或分享码' : '搜索文件名或直链地址'">
|
<input type="text" v-model="shareFilters.keyword" :placeholder="shareResourceTab === 'shares' ? '搜索文件名、链接或分享码' : '搜索文件名或直链地址'">
|
||||||
</div>
|
</div>
|
||||||
<template v-if="shareResourceTab === 'shares'">
|
<div v-if="shareResourceTab === 'shares'" class="share-filter-controls">
|
||||||
<select v-model="shareFilters.type" aria-label="分享类型">
|
<select v-model="shareFilters.type" aria-label="分享类型">
|
||||||
<option value="all">全部类型</option>
|
<option value="all">全部类型</option>
|
||||||
<option value="file">文件</option>
|
<option value="file">文件</option>
|
||||||
@@ -3183,7 +3188,7 @@
|
|||||||
<option value="downloads_desc">下载最多</option>
|
<option value="downloads_desc">下载最多</option>
|
||||||
<option value="expire_asc">最早到期</option>
|
<option value="expire_asc">最早到期</option>
|
||||||
</select>
|
</select>
|
||||||
</template>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="shareResourceTab === 'shares'" class="share-resource-panel">
|
<div v-if="shareResourceTab === 'shares'" class="share-resource-panel">
|
||||||
@@ -3295,7 +3300,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 管理员视图 -->
|
<!-- 管理员视图 -->
|
||||||
<div v-if="isLoggedIn && currentView === 'admin' && user && user.is_admin" class="main-container">
|
<div v-if="isLoggedIn && currentView === 'admin' && user && user.is_admin" class="main-container admin-main-container">
|
||||||
<!-- 管理员标签页导航 -->
|
<!-- 管理员标签页导航 -->
|
||||||
<div class="card admin-tabs-card" style="margin-bottom: 20px; padding: 0;">
|
<div class="card admin-tabs-card" style="margin-bottom: 20px; padding: 0;">
|
||||||
<div class="admin-tabs-nav">
|
<div class="admin-tabs-nav">
|
||||||
@@ -3315,9 +3320,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ========== 概览标签页 ========== -->
|
<!-- ========== 概览标签页 ========== -->
|
||||||
<div v-show="adminTab === 'overview'">
|
<div v-show="adminTab === 'overview'" class="admin-overview-page">
|
||||||
<!-- 调试模式开关 -->
|
<!-- 调试模式开关 -->
|
||||||
<div class="card" style="margin-bottom: 30px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
<div class="card admin-debug-card" style="margin-bottom: 30px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||||
<div class="admin-debug-row" style="display: flex; align-items: center; justify-content: space-between;">
|
<div class="admin-debug-row" style="display: flex; align-items: center; justify-content: space-between;">
|
||||||
<div>
|
<div>
|
||||||
<h3 style="margin-bottom: 10px; color: white;">
|
<h3 style="margin-bottom: 10px; color: white;">
|
||||||
@@ -3335,7 +3340,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 服务器存储统计 -->
|
<!-- 服务器存储统计 -->
|
||||||
<div class="card" style="margin-bottom: 30px;">
|
<div class="card admin-storage-card" style="margin-bottom: 30px;">
|
||||||
<h3 style="margin-bottom: 20px;">
|
<h3 style="margin-bottom: 20px;">
|
||||||
<i class="fas fa-hdd"></i> 服务器存储统计
|
<i class="fas fa-hdd"></i> 服务器存储统计
|
||||||
</h3>
|
</h3>
|
||||||
@@ -4030,12 +4035,23 @@
|
|||||||
</div><!-- 监控标签页结束 -->
|
</div><!-- 监控标签页结束 -->
|
||||||
|
|
||||||
<!-- ========== 用户标签页 ========== -->
|
<!-- ========== 用户标签页 ========== -->
|
||||||
<div v-show="adminTab === 'users'">
|
<div v-show="adminTab === 'users'" class="admin-users-page">
|
||||||
<div class="card">
|
<div class="card admin-users-card">
|
||||||
<div class="admin-users-header">
|
<div class="admin-users-header">
|
||||||
<h3 style="margin: 0;">用户管理</h3>
|
<h3 style="margin: 0;">用户管理</h3>
|
||||||
<button class="btn btn-secondary" @click="loadUsers" :disabled="adminUsersLoading" style="min-width: 92px;">
|
<button
|
||||||
<i :class="adminUsersLoading ? 'fas fa-spinner fa-spin' : 'fas fa-rotate'"></i> 刷新
|
type="button"
|
||||||
|
class="mobile-admin-users-reset-btn"
|
||||||
|
:disabled="activeAdminUserFilterCount === 0"
|
||||||
|
@click="resetAdminUserFilters"
|
||||||
|
aria-label="重置用户筛选"
|
||||||
|
title="重置筛选"
|
||||||
|
>
|
||||||
|
<i class="fas fa-filter-circle-xmark"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-secondary admin-users-refresh-btn" @click="loadUsers" :disabled="adminUsersLoading" style="min-width: 92px;" aria-label="刷新用户列表">
|
||||||
|
<i :class="adminUsersLoading ? 'fas fa-spinner fa-spin' : 'fas fa-rotate'"></i>
|
||||||
|
<span>刷新</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -4046,16 +4062,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="admin-users-filter">
|
<div class="admin-users-filter">
|
||||||
<label>角色</label>
|
<label>角色</label>
|
||||||
<select v-model="adminUserFilters.role" @change="handleAdminUsersFilterChange">
|
<select v-model="adminUserFilters.role" @change="handleAdminUsersFilterChange" aria-label="用户角色">
|
||||||
<option value="all">全部</option>
|
<option value="all">全部角色</option>
|
||||||
<option value="admin">管理员</option>
|
<option value="admin">管理员</option>
|
||||||
<option value="user">普通用户</option>
|
<option value="user">普通用户</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="admin-users-filter">
|
<div class="admin-users-filter">
|
||||||
<label>状态</label>
|
<label>状态</label>
|
||||||
<select v-model="adminUserFilters.status" @change="handleAdminUsersFilterChange">
|
<select v-model="adminUserFilters.status" @change="handleAdminUsersFilterChange" aria-label="用户状态">
|
||||||
<option value="all">全部</option>
|
<option value="all">全部状态</option>
|
||||||
<option value="active">正常</option>
|
<option value="active">正常</option>
|
||||||
<option value="banned">已封禁</option>
|
<option value="banned">已封禁</option>
|
||||||
<option value="unverified">未激活</option>
|
<option value="unverified">未激活</option>
|
||||||
@@ -4064,8 +4080,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="admin-users-filter">
|
<div class="admin-users-filter">
|
||||||
<label>存储</label>
|
<label>存储</label>
|
||||||
<select v-model="adminUserFilters.storage" @change="handleAdminUsersFilterChange">
|
<select v-model="adminUserFilters.storage" @change="handleAdminUsersFilterChange" aria-label="用户存储类型">
|
||||||
<option value="all">全部</option>
|
<option value="all">全部存储</option>
|
||||||
<option value="local">当前本地</option>
|
<option value="local">当前本地</option>
|
||||||
<option value="oss">当前OSS</option>
|
<option value="oss">当前OSS</option>
|
||||||
<option value="local_only">仅本地</option>
|
<option value="local_only">仅本地</option>
|
||||||
@@ -4075,7 +4091,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="admin-users-filter">
|
<div class="admin-users-filter">
|
||||||
<label>排序</label>
|
<label>排序</label>
|
||||||
<select v-model="adminUserFilters.sort" @change="handleAdminUsersFilterChange">
|
<select v-model="adminUserFilters.sort" @change="handleAdminUsersFilterChange" aria-label="用户排序方式">
|
||||||
<option value="created_desc">注册时间(新到旧)</option>
|
<option value="created_desc">注册时间(新到旧)</option>
|
||||||
<option value="created_asc">注册时间(旧到新)</option>
|
<option value="created_asc">注册时间(旧到新)</option>
|
||||||
<option value="username_asc">用户名(A-Z)</option>
|
<option value="username_asc">用户名(A-Z)</option>
|
||||||
@@ -4086,10 +4102,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="admin-users-filter admin-users-filter-page-size">
|
<div class="admin-users-filter admin-users-filter-page-size">
|
||||||
<label>每页</label>
|
<label>每页</label>
|
||||||
<select v-model.number="adminUsersPageSize" @change="handleAdminUsersPageSizeChange">
|
<select v-model.number="adminUsersPageSize" @change="handleAdminUsersPageSizeChange" aria-label="每页用户数量">
|
||||||
<option :value="20">20</option>
|
<option :value="20">20 条</option>
|
||||||
<option :value="50">50</option>
|
<option :value="50">50 条</option>
|
||||||
<option :value="100">100</option>
|
<option :value="100">100 条</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="admin-users-filter admin-users-filter-reset">
|
<div class="admin-users-filter admin-users-filter-reset">
|
||||||
@@ -4474,9 +4490,30 @@
|
|||||||
<i class="fas fa-trash"></i> 删除
|
<i class="fas fa-trash"></i> 删除
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 移动端新建面板 -->
|
||||||
|
<div v-if="showMobileCreateSheet" class="modal-overlay mobile-file-sheet-overlay mobile-create-sheet-overlay" @click="closeMobileCreateSheet">
|
||||||
|
<section class="mobile-file-sheet mobile-create-sheet" role="dialog" aria-modal="true" aria-labelledby="mobile-create-title" @click.stop>
|
||||||
|
<div class="mobile-file-sheet-handle"></div>
|
||||||
|
<div id="mobile-create-title" class="mobile-file-sheet-title mobile-create-sheet-title">新建</div>
|
||||||
|
<div class="mobile-create-sheet-actions">
|
||||||
|
<button type="button" class="mobile-create-option" @click="mobileCreateAction('upload')">
|
||||||
|
<span class="mobile-create-option-icon upload"><i class="fas fa-cloud-upload-alt"></i></span>
|
||||||
|
<span class="mobile-create-option-copy"><strong>上传文件</strong><small>从手机选择一个或多个文件</small></span>
|
||||||
|
<i class="fas fa-chevron-right mobile-create-option-arrow"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="mobile-create-option" @click="mobileCreateAction('folder')">
|
||||||
|
<span class="mobile-create-option-icon folder"><i class="fas fa-folder-plus"></i></span>
|
||||||
|
<span class="mobile-create-option-copy"><strong>新建文件夹</strong><small>创建目录并整理云端文件</small></span>
|
||||||
|
<i class="fas fa-chevron-right mobile-create-option-arrow"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="mobile-file-sheet-btn cancel mobile-create-cancel" @click="closeMobileCreateSheet">取消</button>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 移动端文件操作面板 -->
|
<!-- 移动端文件操作面板 -->
|
||||||
<div v-if="showMobileFileActionSheet && mobileActionFile" class="modal-overlay mobile-file-sheet-overlay" @click="closeMobileFileActionSheet">
|
<div v-if="showMobileFileActionSheet && mobileActionFile" class="modal-overlay mobile-file-sheet-overlay" @click="closeMobileFileActionSheet">
|
||||||
<div class="mobile-file-sheet" @click.stop>
|
<section class="mobile-file-sheet" role="dialog" aria-modal="true" aria-label="文件操作" @click.stop>
|
||||||
<div class="mobile-file-sheet-handle"></div>
|
<div class="mobile-file-sheet-handle"></div>
|
||||||
<div class="mobile-file-sheet-title" :title="getFileDisplayName(mobileActionFile)">
|
<div class="mobile-file-sheet-title" :title="getFileDisplayName(mobileActionFile)">
|
||||||
{{ getFileDisplayName(mobileActionFile) }}
|
{{ getFileDisplayName(mobileActionFile) }}
|
||||||
@@ -4508,7 +4545,7 @@
|
|||||||
<i class="fas fa-times"></i> 取消
|
<i class="fas fa-times"></i> 取消
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 管理员:编辑用户存储权限模态框 -->
|
<!-- 管理员:编辑用户存储权限模态框 -->
|
||||||
@@ -4915,7 +4952,7 @@
|
|||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 769px) {
|
@media (min-width: 901px) {
|
||||||
.mobile-file-sheet-overlay {
|
.mobile-file-sheet-overlay {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,19 @@ createApp({
|
|||||||
return (saved && ['overview', 'settings', 'monitor', 'users'].includes(saved)) ? saved : 'overview';
|
return (saved && ['overview', 'settings', 'monitor', 'users'].includes(saved)) ? saved : 'overview';
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
const initialFileViewMode = (() => {
|
||||||
|
try {
|
||||||
|
const saved = localStorage.getItem('fileViewMode');
|
||||||
|
if (saved && ['grid', 'list'].includes(saved)) {
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[视图] 无法读取文件视图偏好:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return window.matchMedia('(max-width: 900px)').matches ? 'list' : 'grid';
|
||||||
|
})();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// API配置
|
// API配置
|
||||||
// API配置 - 通过nginx代理访问
|
// API配置 - 通过nginx代理访问
|
||||||
@@ -29,7 +42,7 @@ createApp({
|
|||||||
// 视图状态
|
// 视图状态
|
||||||
currentView: 'files',
|
currentView: 'files',
|
||||||
isLogin: true,
|
isLogin: true,
|
||||||
fileViewMode: 'grid', // 文件显示模式: grid 大图标, list 列表
|
fileViewMode: initialFileViewMode, // 移动端默认列表视图,并记住用户选择
|
||||||
shareViewMode: 'list', // 分享显示模式: grid 大图标, list 列表
|
shareViewMode: 'list', // 分享显示模式: grid 大图标, list 列表
|
||||||
shareResourceTab: 'shares', // 分享中心资源标签: shares / directLinks
|
shareResourceTab: 'shares', // 分享中心资源标签: shares / directLinks
|
||||||
settingsTab: 'storage', // 设置中心标签: storage / appearance / security
|
settingsTab: 'storage', // 设置中心标签: storage / appearance / security
|
||||||
@@ -141,7 +154,6 @@ createApp({
|
|||||||
status: 'all', // all/active/expiring/expired/protected/public
|
status: 'all', // all/active/expiring/expired/protected/public
|
||||||
sort: 'created_desc' // created_desc/created_asc/views_desc/downloads_desc/expire_asc
|
sort: 'created_desc' // created_desc/created_asc/views_desc/downloads_desc/expire_asc
|
||||||
},
|
},
|
||||||
|
|
||||||
// 文件重命名
|
// 文件重命名
|
||||||
showRenameModal: false,
|
showRenameModal: false,
|
||||||
renameForm: {
|
renameForm: {
|
||||||
@@ -349,6 +361,7 @@ createApp({
|
|||||||
contextMenuX: 0,
|
contextMenuX: 0,
|
||||||
contextMenuY: 0,
|
contextMenuY: 0,
|
||||||
contextMenuFile: null,
|
contextMenuFile: null,
|
||||||
|
showMobileCreateSheet: false,
|
||||||
showMobileFileActionSheet: false,
|
showMobileFileActionSheet: false,
|
||||||
mobileActionFile: null,
|
mobileActionFile: null,
|
||||||
|
|
||||||
@@ -715,6 +728,17 @@ createApp({
|
|||||||
return list;
|
return list;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
activeAdminUserFilterCount() {
|
||||||
|
let count = 0;
|
||||||
|
if (String(this.adminUserFilters.keyword || '').trim()) count += 1;
|
||||||
|
if (this.adminUserFilters.role !== 'all') count += 1;
|
||||||
|
if (this.adminUserFilters.status !== 'all') count += 1;
|
||||||
|
if (this.adminUserFilters.storage !== 'all') count += 1;
|
||||||
|
if (this.adminUserFilters.sort !== 'created_desc') count += 1;
|
||||||
|
if (Number(this.adminUsersPageSize) !== 20) count += 1;
|
||||||
|
return count;
|
||||||
|
},
|
||||||
|
|
||||||
adminUsersFilteredCount() {
|
adminUsersFilteredCount() {
|
||||||
return Math.max(0, Number(this.adminUsersTotalCount) || 0);
|
return Math.max(0, Number(this.adminUsersTotalCount) || 0);
|
||||||
},
|
},
|
||||||
@@ -2237,7 +2261,7 @@ handleDragLeave(e) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
isMobileViewport() {
|
isMobileViewport() {
|
||||||
return window.matchMedia('(max-width: 768px)').matches;
|
return window.matchMedia('(max-width: 900px)').matches;
|
||||||
},
|
},
|
||||||
|
|
||||||
setMobileSheetScrollLock(locked) {
|
setMobileSheetScrollLock(locked) {
|
||||||
@@ -2245,6 +2269,46 @@ handleDragLeave(e) {
|
|||||||
document.body.style.overflow = locked ? 'hidden' : '';
|
document.body.style.overflow = locked ? 'hidden' : '';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setFileViewMode(mode) {
|
||||||
|
if (!['grid', 'list'].includes(mode)) return;
|
||||||
|
this.fileViewMode = mode;
|
||||||
|
|
||||||
|
try {
|
||||||
|
localStorage.setItem('fileViewMode', mode);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[视图] 无法保存文件视图偏好:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
openMobileCreateSheet(event) {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.closeMobileFileActionSheet();
|
||||||
|
this.showMobileCreateSheet = true;
|
||||||
|
this.setMobileSheetScrollLock(true);
|
||||||
|
},
|
||||||
|
|
||||||
|
closeMobileCreateSheet() {
|
||||||
|
this.showMobileCreateSheet = false;
|
||||||
|
this.setMobileSheetScrollLock(false);
|
||||||
|
},
|
||||||
|
|
||||||
|
mobileCreateAction(action) {
|
||||||
|
this.closeMobileCreateSheet();
|
||||||
|
|
||||||
|
if (action === 'upload') {
|
||||||
|
this.$nextTick(() => this.$refs.fileUploadInput?.click());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'folder') {
|
||||||
|
this.showCreateFolderModal = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
openMobileFileActionSheet(file, event) {
|
openMobileFileActionSheet(file, event) {
|
||||||
if (event) {
|
if (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -5176,11 +5240,12 @@ handleDragLeave(e) {
|
|||||||
this.isDragging = false;
|
this.isDragging = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 添加 ESC 键监听(按 ESC 关闭拖拽覆盖层)
|
// 添加 ESC 键监听(按 ESC 关闭当前浮层)
|
||||||
window.addEventListener("keydown", (e) => {
|
window.addEventListener("keydown", (e) => {
|
||||||
if (e.key === "Escape" && this.isDragging) {
|
if (e.key !== "Escape") return;
|
||||||
this.isDragging = false;
|
if (this.isDragging) this.isDragging = false;
|
||||||
}
|
if (this.showMobileCreateSheet) this.closeMobileCreateSheet();
|
||||||
|
if (this.showMobileFileActionSheet) this.closeMobileFileActionSheet();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 设置axios响应拦截器,处理401错误(token过期/失效)
|
// 设置axios响应拦截器,处理401错误(token过期/失效)
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import { copyFileSync, existsSync, mkdirSync, readdirSync } from 'node:fs';
|
import { createHash } from 'node:crypto';
|
||||||
|
import {
|
||||||
|
copyFileSync,
|
||||||
|
existsSync,
|
||||||
|
mkdirSync,
|
||||||
|
readFileSync,
|
||||||
|
readdirSync,
|
||||||
|
writeFileSync
|
||||||
|
} from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
@@ -32,3 +40,26 @@ for (const entry of ['libs', 'favicon.ico']) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vite hoists linked stylesheets into <head>, ahead of legacy inline styles in
|
||||||
|
// app.html. Re-emit the workspace layer after those styles so production and
|
||||||
|
// development use the same cascade order.
|
||||||
|
const workspaceSourcePath = path.join(frontendRoot, 'workspace.css');
|
||||||
|
const workspaceContents = readFileSync(workspaceSourcePath);
|
||||||
|
const workspaceHash = createHash('sha256').update(workspaceContents).digest('hex').slice(0, 12);
|
||||||
|
const workspaceFileName = `workspace-${workspaceHash}.css`;
|
||||||
|
copyFileSync(workspaceSourcePath, path.join(outputDirectory, workspaceFileName));
|
||||||
|
|
||||||
|
const builtAppPath = path.join(outputDirectory, 'app.html');
|
||||||
|
const builtAppHtml = readFileSync(builtAppPath, 'utf8');
|
||||||
|
const runtimeStylesheet = ` <link rel="stylesheet" href="/${workspaceFileName}" data-workspace-runtime>\n`;
|
||||||
|
const nextBuiltAppHtml = builtAppHtml.includes('data-workspace-runtime')
|
||||||
|
? builtAppHtml.replace(/\s*<link[^>]+data-workspace-runtime[^>]*>\s*/u, `\n${runtimeStylesheet}`)
|
||||||
|
: builtAppHtml.replace('</body>', `${runtimeStylesheet}</body>`);
|
||||||
|
|
||||||
|
if (!nextBuiltAppHtml.includes('data-workspace-runtime')) {
|
||||||
|
throw new Error('Unable to inject the runtime workspace stylesheet into dist/app.html');
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFileSync(builtAppPath, nextBuiltAppHtml, 'utf8');
|
||||||
|
console.log(`Injected runtime workspace stylesheet: ${workspaceFileName}`);
|
||||||
|
|||||||
@@ -32,4 +32,21 @@ for (const fileName of ['app.html', 'share.html']) {
|
|||||||
assert.strictEqual(/vue\.global(?:\.prod)?\.js|axios\.min\.js/.test(html), false, `${fileName} still loads global Vue/Axios`);
|
assert.strictEqual(/vue\.global(?:\.prod)?\.js|axios\.min\.js/.test(html), false, `${fileName} still loads global Vue/Axios`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const builtAppHtml = readFileSync(path.join(outputDirectory, 'app.html'), 'utf8');
|
||||||
|
const runtimeWorkspaceMatch = builtAppHtml.match(
|
||||||
|
/<link rel="stylesheet" href="\/(workspace-[a-f0-9]{12}\.css)" data-workspace-runtime>/
|
||||||
|
);
|
||||||
|
assert.ok(runtimeWorkspaceMatch, 'app.html is missing the hashed runtime workspace stylesheet');
|
||||||
|
|
||||||
|
const runtimeWorkspacePath = path.join(outputDirectory, runtimeWorkspaceMatch[1]);
|
||||||
|
assert.ok(existsSync(runtimeWorkspacePath), 'runtime workspace stylesheet is missing');
|
||||||
|
assert.ok(
|
||||||
|
builtAppHtml.indexOf(runtimeWorkspaceMatch[0]) > builtAppHtml.lastIndexOf('</style>'),
|
||||||
|
'runtime workspace stylesheet must load after every legacy inline style'
|
||||||
|
);
|
||||||
|
|
||||||
|
const runtimeWorkspaceCss = readFileSync(runtimeWorkspacePath, 'utf8');
|
||||||
|
assert.ok(runtimeWorkspaceCss.includes('.mobile-file-create-trigger'), 'runtime workspace stylesheet is incomplete');
|
||||||
|
assert.ok(runtimeWorkspaceCss.includes('@media (max-width: 900px)'), 'runtime workspace mobile breakpoint is missing');
|
||||||
|
|
||||||
console.log(`Verified ${htmlFiles.length} HTML entries and their emitted assets.`);
|
console.log(`Verified ${htmlFiles.length} HTML entries and their emitted assets.`);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user