优化手机端体验
This commit is contained in:
parent
325bd2979d
commit
23e48a5da2
3 changed files with 256 additions and 25 deletions
77
src/App.vue
77
src/App.vue
|
@ -1,14 +1,81 @@
|
|||
<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>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const isLoading = ref(false)
|
||||
|
||||
const showLoadingMask = () => {
|
||||
isLoading.value = true
|
||||
}
|
||||
|
||||
const hideLoadingMask = () => {
|
||||
isLoading.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
.app-container {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* 过渡动画 */
|
||||
.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>
|
|
@ -1,6 +1,15 @@
|
|||
<template>
|
||||
<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">
|
||||
<img src="../assets/logo.png" alt="logo" class="logo-img">
|
||||
<span class="logo-text" v-show="!isCollapse">iFileProxy<br />数据管理系统</span>
|
||||
|
@ -78,12 +87,13 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { DataLine, List, Expand, Fold, Setting, User, SwitchButton } from '@element-plus/icons-vue'
|
||||
import { ADMIN_ROUTE_BASE } from '@/config/api.config'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const userRole = computed(() => localStorage.getItem('userRole'))
|
||||
const userInfo = computed(() => JSON.parse(localStorage.getItem('userInfo')))
|
||||
|
||||
|
@ -101,9 +111,34 @@ const toggleSidebar = () => {
|
|||
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>
|
||||
|
||||
<style scoped>
|
||||
|
@ -113,8 +148,10 @@ onUnmounted(() => {
|
|||
|
||||
.aside {
|
||||
background-color: #001529;
|
||||
transition: width 0.3s;
|
||||
transition: all 0.3s;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.logo {
|
||||
|
@ -225,4 +262,75 @@ onUnmounted(() => {
|
|||
:deep(.el-menu--collapse .el-menu-item) {
|
||||
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>
|
|
@ -92,7 +92,7 @@
|
|||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="数据库配置">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions :column="isMobile ? 1 : 2" border>
|
||||
<el-descriptions-item label="数据库主机">
|
||||
{{ config.Database.Common.Host }}
|
||||
</el-descriptions-item>
|
||||
|
@ -105,11 +105,12 @@
|
|||
<el-descriptions-item label="密码">
|
||||
********
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="数据库" :span="2">
|
||||
<el-descriptions-item label="数据库" :span="isMobile ? 1 : 2">
|
||||
<div class="database-tags">
|
||||
<el-tag
|
||||
v-for="db in config.Database.Databases"
|
||||
:key="db.DatabaseName"
|
||||
class="mx-1">
|
||||
class="database-tag">
|
||||
{{ db.DatabaseName }}
|
||||
<el-tooltip
|
||||
v-if="db.Description"
|
||||
|
@ -118,6 +119,7 @@
|
|||
<el-icon class="ml-2"><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
|
@ -263,6 +265,18 @@ onMounted(() => {
|
|||
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) {
|
||||
.config-view {
|
||||
|
@ -305,6 +319,40 @@ onMounted(() => {
|
|||
font-size: 11px;
|
||||
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) {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.database-tags {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:deep(.el-descriptions__label) {
|
||||
width: 110px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 优化标签显示 */
|
||||
|
|
Loading…
Reference in a new issue