style: 修改一些UI和UE,提升用户体验
This commit is contained in:
1 parent
1c1d34df02
commit
c634f0727e
11 files changed
+67
-47
No files matched your search
@@ -38,40 +38,35 @@ const router = createRouter({
|
||||
})
|
||||
|
||||
// 路由守卫:拦截未登录用户的访问
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
router.beforeEach(async (to) => {
|
||||
const authUserStore = useAuthUserStore()
|
||||
|
||||
// 1. 检查内存中是否有用户信息 (乐观检查)
|
||||
let isAuthenticated = !!(authUserStore.userInfo && authUserStore.userInfo.id)
|
||||
|
||||
// 2. 如果内存无状态,但目标页需要登录 OR 是仅游客页面(如登录页),尝试通过 Cookie 恢复会话
|
||||
// (Option 1B: 即使访问登录页,也先确认是否已登录,防止误判)
|
||||
if (!isAuthenticated && (to.meta.requiresAuth || to.meta.guestOnly)) {
|
||||
try {
|
||||
await authUserStore.fetchUserInfo()
|
||||
isAuthenticated = !!(authUserStore.userInfo && authUserStore.userInfo.id)
|
||||
} catch (e) {
|
||||
// Handle the case where the application environment is unstable/not healthy
|
||||
if (e.isSecurity || e.message === 'Security Check Failed') {
|
||||
next('/health')
|
||||
return
|
||||
return '/health'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 处理需要登录的页面
|
||||
if (to.meta.requiresAuth && !isAuthenticated) {
|
||||
next('/login')
|
||||
return
|
||||
return '/login'
|
||||
}
|
||||
|
||||
// 4. 处理仅限游客的页面 (Option 2B: 已登录用户访问登录页则跳转首页)
|
||||
// 4. 处理仅限游客的页面
|
||||
if (to.meta.guestOnly && isAuthenticated) {
|
||||
next('/')
|
||||
return
|
||||
return '/'
|
||||
}
|
||||
|
||||
next()
|
||||
return true
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -148,7 +148,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 20px 20px 0;
|
||||
padding: 20px;
|
||||
background-color: none;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 12px;
|
||||
@@ -210,14 +210,14 @@
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.ecosystem-collapse :deep(.el-collapse),
|
||||
.ecosystem-collapse :deep(.el-collapse-item),
|
||||
.ecosystem-collapse :deep(.el-collapse-item__header),
|
||||
.ecosystem-collapse :deep(.el-collapse-item__wrap) {
|
||||
.ecosystem-collapse,
|
||||
.ecosystem-collapse .el-collapse-item,
|
||||
.ecosystem-collapse .el-collapse-item__header,
|
||||
.ecosystem-collapse .el-collapse-item__wrap {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.ecosystem-collapse :deep(.el-collapse-item__header) {
|
||||
.ecosystem-collapse .el-collapse-item__header {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
@@ -226,11 +226,11 @@
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.ecosystem-collapse :deep(.el-collapse-item__wrap) {
|
||||
.ecosystem-collapse .el-collapse-item__wrap {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.ecosystem-collapse :deep(.el-collapse-item__content) {
|
||||
.ecosystem-collapse .el-collapse-item__content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@@ -321,6 +321,6 @@ html.dark .ecosystem-item:hover {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.migration-tabs :deep(.el-tabs__nav-wrap::after) {
|
||||
.migration-tabs .el-tabs__nav-wrap::after {
|
||||
height: 1px;
|
||||
}
|
||||
@@ -99,11 +99,6 @@
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
html.dark .global-offline-banner {
|
||||
background-color: rgba(230, 162, 60, 0.1);
|
||||
border-bottom-color: rgba(230, 162, 60, 0.2);
|
||||
}
|
||||
|
||||
/* 3. QrScanner Component */
|
||||
.qr-scanner-container {
|
||||
width: 100%;
|
||||
|
||||
@@ -262,7 +262,7 @@
|
||||
|
||||
/* AddVaultManual.vue */
|
||||
.vault-manual-form-wrapper {
|
||||
padding: 10px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.vault-manual-submit-btn {
|
||||
|
||||
@@ -242,7 +242,14 @@ const handleDelete = async (row) => {
|
||||
|
||||
const formatDate = (timestamp) => {
|
||||
if (!timestamp) return '-'
|
||||
return new Date(timestamp).toLocaleString()
|
||||
const date = new Date(timestamp)
|
||||
const Y = date.getFullYear()
|
||||
const M = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const D = String(date.getDate()).padStart(2, '0')
|
||||
const h = String(date.getHours()).padStart(2, '0')
|
||||
const m = String(date.getMinutes()).padStart(2, '0')
|
||||
const s = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${Y}-${M}-${D} ${h}:${m}:${s}`
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<div class="card-content">
|
||||
<p class="backup-status-text">
|
||||
{{ $t('backup.last_backup') }}
|
||||
<span v-if="provider.lastBackupAt">{{ new Date(provider.lastBackupAt).toLocaleString() }}</span>
|
||||
<span v-if="provider.lastBackupAt">{{ formatDate(provider.lastBackupAt) }}</span>
|
||||
<span v-else>{{ $t('backup.never_backed_up') }}</span>
|
||||
<el-tag v-if="provider.lastBackupStatus" size="small" :type="provider.lastBackupStatus === 'success' ? 'success' : 'danger'" class="ml-5">
|
||||
{{ provider.lastBackupStatus }}
|
||||
@@ -513,7 +513,11 @@
|
||||
{{ formatSize(scope.row.size) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="lastModified" :label="$t('backup.time')" width="180" />
|
||||
<el-table-column :label="$t('backup.time')" width="180">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.lastModified) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('backup.action')" width="100">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="selectRestoreFile(scope.row)">{{ $t('backup.restore') }}</el-button>
|
||||
@@ -597,14 +601,14 @@ const handleSmtpSecureChange = (val) => {
|
||||
|
||||
const getProviderTypeTag = (type) => {
|
||||
const map = {
|
||||
webdav: 'primary',
|
||||
webdav: 'danger',
|
||||
s3: 'warning',
|
||||
telegram: 'success',
|
||||
gdrive: 'danger',
|
||||
onedrive: 'success',
|
||||
baidu: 'warning',
|
||||
telegram: 'primary',
|
||||
gdrive: 'success',
|
||||
onedrive: 'primary',
|
||||
baidu: 'primary',
|
||||
dropbox: 'primary',
|
||||
email: 'info'
|
||||
email: 'success'
|
||||
}
|
||||
return map[type] || 'info'
|
||||
}
|
||||
@@ -616,4 +620,16 @@ const formatSize = (bytes) => {
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return '-'
|
||||
const date = new Date(dateStr)
|
||||
const Y = date.getFullYear()
|
||||
const M = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const D = String(date.getDate()).padStart(2, '0')
|
||||
const h = String(date.getHours()).padStart(2, '0')
|
||||
const m = String(date.getMinutes()).padStart(2, '0')
|
||||
const s = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${Y}-${M}-${D} ${h}:${m}:${s}`
|
||||
}
|
||||
</script>
|
||||
@@ -64,9 +64,9 @@
|
||||
<el-collapse class="ecosystem-collapse">
|
||||
<el-collapse-item name="1">
|
||||
<template #title>
|
||||
<h4 class="group-title">
|
||||
<el-icon class="mr-8"><QuestionFilled /></el-icon> <strong>{{ $t('migration.ga_ms_import_guide') }}</strong>
|
||||
</h4>
|
||||
<div class="migration-guide">
|
||||
<strong>{{ $t('migration.ga_ms_import_guide') }}</strong>
|
||||
</div>
|
||||
</template>
|
||||
<div class="migration-ga-tip">
|
||||
<span><el-icon><iconGoogleAuth /></el-icon> Google Authenticator</span>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<div class="sidebar-footer" :class="{ 'is-collapsed': isCollapse }">
|
||||
<el-button circle :icon="isCollapse ? Expand : Fold" @click="toggleCollapse" :title="isCollapse ? '展开' : '折叠'" />
|
||||
<el-button circle :icon="themeStore.isDark ? Sunny : Moon" @click="themeStore.toggleTheme" />
|
||||
<el-button circle size="medium" :icon="iconLocales" :title="$i18n.locale === 'zh-CN' ? 'English' : '切换语言'" @click="toggleLanguage" />
|
||||
<el-button circle size="default" :icon="iconLocales" :title="$i18n.locale === 'zh-CN' ? 'English' : '切换语言'" @click="toggleLanguage" />
|
||||
<el-button circle @click="handleLogout" :title="$t('menu.logout')">
|
||||
<el-icon><SwitchButton /></el-icon>
|
||||
</el-button>
|
||||
@@ -128,9 +128,9 @@
|
||||
</el-menu>
|
||||
|
||||
<div class="sidebar-footer" style="display: flex; gap: 15px; justify-content: center; padding: 20px 0;">
|
||||
<el-button circle size="medium" :icon="themeStore.isDark ? Sunny : Moon" @click="themeStore.toggleTheme" />
|
||||
<el-button circle size="medium" :icon="iconLocales" :title="$i18n.locale === 'zh-CN' ? 'English' : '切换语言'" @click="toggleLanguage" />
|
||||
<el-button circle size="medium" @click="handleLogout" :title="$t('menu.logout')">
|
||||
<el-button circle size="default" :icon="themeStore.isDark ? Sunny : Moon" @click="themeStore.toggleTheme" />
|
||||
<el-button circle size="default" :icon="iconLocales" :title="$i18n.locale === 'zh-CN' ? 'English' : '切换语言'" @click="toggleLanguage" />
|
||||
<el-button circle size="default" @click="handleLogout" :title="$t('menu.logout')">
|
||||
<el-icon><SwitchButton /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -170,7 +170,7 @@ const themeStore = useThemeStore()
|
||||
const authUserStore = useAuthUserStore()
|
||||
const router = useRouter()
|
||||
|
||||
const isCollapse = ref(localStorage.getItem('sidebar_collapse') === 'true')
|
||||
const isCollapse = ref(localStorage.getItem('sidebar_collapse') !== 'false')
|
||||
|
||||
const toggleCollapse = () => {
|
||||
isCollapse.value = !isCollapse.value
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"backup": "Cloud Backup",
|
||||
"migration": "Migration",
|
||||
"tools": "Toolbox",
|
||||
"security": "PassKey",
|
||||
"security": "Passkey",
|
||||
"settings": "Settings",
|
||||
"logout": "Sign Out"
|
||||
},
|
||||
@@ -733,7 +733,7 @@
|
||||
"connection_failed": "Cannot connect to server for health check"
|
||||
},
|
||||
"security": {
|
||||
"management": "Passkey Management",
|
||||
"management": "Passkey List",
|
||||
"passkey_title": "Passkey Management",
|
||||
"passkey_desc": "Enable passwordless login using biometrics such as fingerprint or face recognition for a more secure and convenient access experience.",
|
||||
"passkey_list": "Registered Passkeys",
|
||||
|
||||
@@ -734,7 +734,7 @@
|
||||
},
|
||||
"security": {
|
||||
"management": "通行密钥管理",
|
||||
"passkey_title": "通行密钥管理",
|
||||
"passkey_title": "通行密钥",
|
||||
"passkey_desc": "通过生物识别(如指纹或面容)实现无密码登录,为您提供更安全、更快速的账户访问体验。",
|
||||
"passkey_list": "已绑定的通行密钥",
|
||||
"add_passkey": "添加通行密钥",
|
||||
|
||||
@@ -92,7 +92,14 @@ export default defineConfig({
|
||||
clientsClaim: true,
|
||||
skipWaiting: true,
|
||||
navigateFallback: '/index.html',
|
||||
navigateFallbackDenylist: [/^\/api/],
|
||||
// 排除 API 和 Vite 开发环境下的内部路径,防止 Service Worker 拦截干扰
|
||||
navigateFallbackDenylist: [
|
||||
/^\/api/,
|
||||
/\/@vite\//,
|
||||
/\/@fs\//,
|
||||
/\/@id\//,
|
||||
/node_modules/
|
||||
],
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: ({ url }) => url.pathname.startsWith('/api'),
|
||||
|
||||
Reference in new issue
Block a user