前端页面添加BackToTop图标
This commit is contained in:
parent
7132875265
commit
84e263d4eb
@ -37,6 +37,9 @@
|
|||||||
class="mobile-overlay"
|
class="mobile-overlay"
|
||||||
@click="toggleSidebar"
|
@click="toggleSidebar"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
|
<!-- 返回顶部按钮 -->
|
||||||
|
<BackToTop />
|
||||||
</n-layout>
|
</n-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -45,6 +48,7 @@ import { ref, watch, onMounted, onUnmounted } from 'vue'
|
|||||||
import { NLayout, NLayoutSider, NLayoutHeader, NLayoutContent } from 'naive-ui'
|
import { NLayout, NLayoutSider, NLayoutHeader, NLayoutContent } from 'naive-ui'
|
||||||
import AppSidebar from './AppSidebar.vue'
|
import AppSidebar from './AppSidebar.vue'
|
||||||
import AppHeader from './AppHeader.vue'
|
import AppHeader from './AppHeader.vue'
|
||||||
|
import BackToTop from './BackToTop.vue'
|
||||||
|
|
||||||
const SIDEBAR_COLLAPSE_KEY = 'app.sidebar.collapsed'
|
const SIDEBAR_COLLAPSE_KEY = 'app.sidebar.collapsed'
|
||||||
const collapsed = ref(localStorage.getItem(SIDEBAR_COLLAPSE_KEY) === 'true')
|
const collapsed = ref(localStorage.getItem(SIDEBAR_COLLAPSE_KEY) === 'true')
|
||||||
|
|||||||
206
apps/jingrow/frontend/src/app/layouts/BackToTop.vue
Normal file
206
apps/jingrow/frontend/src/app/layouts/BackToTop.vue
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
<template>
|
||||||
|
<transition name="back-to-top-fade">
|
||||||
|
<n-button
|
||||||
|
v-if="visible"
|
||||||
|
circle
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
class="back-to-top-btn"
|
||||||
|
@click="scrollToTop"
|
||||||
|
:title="t('Back to top')"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<Icon icon="tabler:arrow-up" :width="20" :height="20" />
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
|
import { NButton } from 'naive-ui'
|
||||||
|
import { Icon } from '@iconify/vue'
|
||||||
|
import { t } from '@/shared/i18n'
|
||||||
|
|
||||||
|
// 显示/隐藏状态
|
||||||
|
const visible = ref(false)
|
||||||
|
|
||||||
|
// 滚动阈值(滚动超过多少像素后显示按钮)
|
||||||
|
const SCROLL_THRESHOLD = 300
|
||||||
|
|
||||||
|
// 存储已注册的滚动容器,用于清理
|
||||||
|
const registeredContainers = ref<(Element | Window)[]>([])
|
||||||
|
|
||||||
|
// 找到所有可能的滚动容器
|
||||||
|
const findScrollContainers = (): (Element | Window)[] => {
|
||||||
|
const containers: (Element | Window)[] = [window]
|
||||||
|
|
||||||
|
// 查找 .content-wrapper 容器
|
||||||
|
const contentWrapper = document.querySelector('.content-wrapper')
|
||||||
|
if (contentWrapper) {
|
||||||
|
containers.push(contentWrapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找其他可能的滚动容器
|
||||||
|
const scrollableElements = document.querySelectorAll('[class*="scroll"], [class*="content"], [class*="wrapper"]')
|
||||||
|
scrollableElements.forEach(el => {
|
||||||
|
const style = window.getComputedStyle(el)
|
||||||
|
if (style.overflowY === 'auto' || style.overflowY === 'scroll' ||
|
||||||
|
style.overflow === 'auto' || style.overflow === 'scroll') {
|
||||||
|
if (!containers.includes(el)) {
|
||||||
|
containers.push(el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('找到的所有滚动容器:', containers.map(c =>
|
||||||
|
c === window ? 'window' : (c as Element).className || (c as Element).tagName
|
||||||
|
))
|
||||||
|
return containers
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取容器的滚动位置
|
||||||
|
const getScrollTop = (container: Element | Window): number => {
|
||||||
|
if (container === window) {
|
||||||
|
return window.pageYOffset || document.documentElement.scrollTop || 0
|
||||||
|
} else {
|
||||||
|
return (container as Element).scrollTop || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滚动到顶部
|
||||||
|
const scrollToTop = () => {
|
||||||
|
const containers = registeredContainers.value.length > 0
|
||||||
|
? registeredContainers.value
|
||||||
|
: findScrollContainers()
|
||||||
|
|
||||||
|
console.log('滚动容器前状态:', containers.map(c => ({
|
||||||
|
container: c === window ? 'window' : (c as Element).className || (c as Element).tagName,
|
||||||
|
scrollTop: getScrollTop(c)
|
||||||
|
})))
|
||||||
|
|
||||||
|
// 滚动所有容器到顶部
|
||||||
|
containers.forEach(container => {
|
||||||
|
if (container === window) {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const el = container as Element
|
||||||
|
el.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('执行了滚动操作')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理滚动事件
|
||||||
|
const handleScroll = () => {
|
||||||
|
// 使用已注册的容器,如果没有则重新查找
|
||||||
|
const containers = registeredContainers.value.length > 0
|
||||||
|
? registeredContainers.value
|
||||||
|
: findScrollContainers()
|
||||||
|
|
||||||
|
if (containers.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查所有容器,取最大滚动值
|
||||||
|
let maxScrollTop = 0
|
||||||
|
containers.forEach(container => {
|
||||||
|
const scrollTop = getScrollTop(container)
|
||||||
|
if (scrollTop > maxScrollTop) {
|
||||||
|
maxScrollTop = scrollTop
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 根据最大滚动值判断是否显示按钮
|
||||||
|
visible.value = maxScrollTop > SCROLL_THRESHOLD
|
||||||
|
|
||||||
|
// 调试日志
|
||||||
|
if (maxScrollTop > 0) {
|
||||||
|
console.log('滚动检测:', {
|
||||||
|
maxScrollTop,
|
||||||
|
visible: visible.value,
|
||||||
|
containers: containers.map(c => ({
|
||||||
|
container: c === window ? 'window' : (c as Element).className || (c as Element).tagName,
|
||||||
|
scrollTop: getScrollTop(c)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 延迟查找容器,确保 DOM 已渲染
|
||||||
|
nextTick(() => {
|
||||||
|
const containers = findScrollContainers()
|
||||||
|
|
||||||
|
// 保存容器引用
|
||||||
|
registeredContainers.value = containers
|
||||||
|
|
||||||
|
// 为所有容器添加滚动监听
|
||||||
|
containers.forEach(container => {
|
||||||
|
container.addEventListener('scroll', handleScroll, { passive: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始检查
|
||||||
|
handleScroll()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 清理所有滚动监听器
|
||||||
|
registeredContainers.value.forEach(container => {
|
||||||
|
container.removeEventListener('scroll', handleScroll)
|
||||||
|
})
|
||||||
|
registeredContainers.value = []
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.back-to-top-btn {
|
||||||
|
position: fixed;
|
||||||
|
right: 24px;
|
||||||
|
bottom: 24px;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端调整位置 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.back-to-top-btn {
|
||||||
|
right: 16px;
|
||||||
|
bottom: 16px;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 淡入淡出动画 */
|
||||||
|
.back-to-top-fade-enter-active,
|
||||||
|
.back-to-top-fade-leave-active {
|
||||||
|
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top-fade-enter-from,
|
||||||
|
.back-to-top-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8) translateY(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top-fade-enter-to,
|
||||||
|
.back-to-top-fade-leave-from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user