优化手机端体验

This commit is contained in:
root 2024-12-01 14:25:39 +08:00
parent 325bd2979d
commit 23e48a5da2
3 changed files with 256 additions and 25 deletions

View file

@ -1,14 +1,81 @@
<template> <template>
<router-view></router-view> <div class="app-container">
<router-view v-slot="{ Component }">
<transition
name="fade"
mode="out-in"
@before-leave="showLoadingMask"
@after-enter="hideLoadingMask">
<component :is="Component" />
</transition>
</router-view>
<!-- 全局加载遮罩 -->
<transition name="fade">
<div v-if="isLoading" class="global-loading-mask">
<div class="loading-spinner"></div>
</div>
</transition>
</div>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'
const isLoading = ref(false)
const showLoadingMask = () => {
isLoading.value = true
}
const hideLoadingMask = () => {
isLoading.value = false
}
</script> </script>
<style> <style>
* { .app-container {
margin: 0; height: 100vh;
padding: 0; }
box-sizing: border-box;
/* 过渡动画 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* 全局加载遮罩 */
.global-loading-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(8px);
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
}
/* 加载动画 */
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid #f3f3f3;
border-top: 3px solid #1890ff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
} }
</style> </style>

View file

@ -1,6 +1,15 @@
<template> <template>
<el-container class="layout-container"> <el-container class="layout-container">
<el-aside :width="isCollapse ? '64px' : '220px'" class="aside"> <div
v-show="isMobile && !isCollapse"
class="mobile-mask"
@click="toggleSidebar">
</div>
<el-aside
:width="isCollapse ? '64px' : '220px'"
class="aside"
:class="{ 'mobile-aside': isMobile, 'mobile-collapsed': isMobile && isCollapse }">
<div class="logo"> <div class="logo">
<img src="../assets/logo.png" alt="logo" class="logo-img"> <img src="../assets/logo.png" alt="logo" class="logo-img">
<span class="logo-text" v-show="!isCollapse">iFileProxy<br />数据管理系统</span> <span class="logo-text" v-show="!isCollapse">iFileProxy<br />数据管理系统</span>
@ -78,12 +87,13 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, onUnmounted } from 'vue' import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { useRouter } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { DataLine, List, Expand, Fold, Setting, User, SwitchButton } from '@element-plus/icons-vue' import { DataLine, List, Expand, Fold, Setting, User, SwitchButton } from '@element-plus/icons-vue'
import { ADMIN_ROUTE_BASE } from '@/config/api.config' import { ADMIN_ROUTE_BASE } from '@/config/api.config'
const router = useRouter() const router = useRouter()
const route = useRoute()
const userRole = computed(() => localStorage.getItem('userRole')) const userRole = computed(() => localStorage.getItem('userRole'))
const userInfo = computed(() => JSON.parse(localStorage.getItem('userInfo'))) const userInfo = computed(() => JSON.parse(localStorage.getItem('userInfo')))
@ -101,9 +111,34 @@ const toggleSidebar = () => {
localStorage.setItem('sidebarCollapsed', isCollapse.value) localStorage.setItem('sidebarCollapsed', isCollapse.value)
} }
onUnmounted(() => { //
localStorage.setItem('sidebarCollapsed', isCollapse.value) const isMobile = computed(() => {
return window.innerWidth <= 768
}) })
//
const resizeHandler = () => {
isMobile.value = window.innerWidth <= 768
}
onMounted(() => {
window.addEventListener('resize', resizeHandler)
})
onUnmounted(() => {
window.removeEventListener('resize', resizeHandler)
})
//
watch(
() => route.path,
() => {
if (isMobile.value && !isCollapse.value) {
isCollapse.value = true
localStorage.setItem('sidebarCollapsed', 'true')
}
}
)
</script> </script>
<style scoped> <style scoped>
@ -113,8 +148,10 @@ onUnmounted(() => {
.aside { .aside {
background-color: #001529; background-color: #001529;
transition: width 0.3s; transition: all 0.3s;
overflow: hidden; overflow: hidden;
position: relative;
z-index: 10;
} }
.logo { .logo {
@ -225,4 +262,75 @@ onUnmounted(() => {
:deep(.el-menu--collapse .el-menu-item) { :deep(.el-menu--collapse .el-menu-item) {
padding: 0 20px !important; padding: 0 20px !important;
} }
/* 遮罩层过渡动画 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* 移动端样式 */
@media screen and (max-width: 768px) {
.mobile-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.705);
z-index: 9;
backdrop-filter: blur(8px);
opacity: 1;
transition: opacity 0.3s ease;
}
.mobile-mask[style*="display: none"] {
opacity: 0;
}
.mobile-aside {
position: fixed;
left: 0;
top: 0;
bottom: 0;
z-index: 10;
box-shadow: 2px 0 8px rgba(0,0,0,0.15);
transform: translateX(0);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.mobile-collapsed {
transform: translateX(-100%);
}
/* 调整主容器在侧边栏隐藏时的样式 */
.el-container:has(.mobile-aside) + .el-container {
margin-left: 0;
}
/* 调整头部样式 */
.header {
position: sticky;
top: 0;
z-index: 8;
}
/* 调整主容器样式 */
.main-container {
padding: 10px;
min-height: calc(100vh - 60px); /* 60px 是头部高度 */
}
}
/* 平板端样式 */
@media screen and (min-width: 769px) and (max-width: 1024px) {
.aside {
position: relative;
}
}
</style> </style>

View file

@ -92,7 +92,7 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="数据库配置"> <el-tab-pane label="数据库配置">
<el-descriptions :column="2" border> <el-descriptions :column="isMobile ? 1 : 2" border>
<el-descriptions-item label="数据库主机"> <el-descriptions-item label="数据库主机">
{{ config.Database.Common.Host }} {{ config.Database.Common.Host }}
</el-descriptions-item> </el-descriptions-item>
@ -105,19 +105,21 @@
<el-descriptions-item label="密码"> <el-descriptions-item label="密码">
******** ********
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="数据库" :span="2"> <el-descriptions-item label="数据库" :span="isMobile ? 1 : 2">
<el-tag <div class="database-tags">
v-for="db in config.Database.Databases" <el-tag
:key="db.DatabaseName" v-for="db in config.Database.Databases"
class="mx-1"> :key="db.DatabaseName"
{{ db.DatabaseName }} class="database-tag">
<el-tooltip {{ db.DatabaseName }}
v-if="db.Description" <el-tooltip
:content="db.Description" v-if="db.Description"
placement="top"> :content="db.Description"
<el-icon class="ml-2"><InfoFilled /></el-icon> placement="top">
</el-tooltip> <el-icon class="ml-2"><InfoFilled /></el-icon>
</el-tag> </el-tooltip>
</el-tag>
</div>
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-tab-pane> </el-tab-pane>
@ -263,6 +265,18 @@ onMounted(() => {
margin: 4px; margin: 4px;
} }
.database-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.database-tag {
display: inline-flex;
align-items: center;
gap: 4px;
}
/* 移动端样式 */ /* 移动端样式 */
@media screen and (max-width: 768px) { @media screen and (max-width: 768px) {
.config-view { .config-view {
@ -305,6 +319,40 @@ onMounted(() => {
font-size: 11px; font-size: 11px;
padding: 0 4px; padding: 0 4px;
} }
.database-tags {
gap: 6px;
}
.database-tag {
font-size: 11px;
padding: 0 6px;
}
:deep(.el-tabs__item) {
font-size: 13px;
padding: 0 12px;
}
:deep(.el-tabs__content) {
padding: 12px;
}
:deep(.el-descriptions__cell) {
padding: 12px !important;
}
:deep(.el-descriptions__label) {
width: 90px;
min-width: 90px;
font-size: 12px;
padding-right: 12px;
}
:deep(.el-descriptions__content) {
font-size: 12px;
word-break: break-all;
}
} }
/* 平板端样式 */ /* 平板端样式 */
@ -316,6 +364,14 @@ onMounted(() => {
:deep(.el-descriptions__label) { :deep(.el-descriptions__label) {
width: 120px; width: 120px;
} }
.database-tags {
gap: 8px;
}
:deep(.el-descriptions__label) {
width: 110px;
}
} }
/* 优化标签显示 */ /* 优化标签显示 */