Skip to content

Commit 1a00032

Browse files
author
Taois
committed
feat:直达底部拆了个组件
1 parent e7420c8 commit 1a00032

File tree

2 files changed

+213
-108
lines changed

2 files changed

+213
-108
lines changed
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
<template>
2+
<!-- 直达底部按钮 -->
3+
<transition name="scroll-btn-fade">
4+
<div
5+
v-show="showButton"
6+
class="scroll-to-bottom-btn"
7+
@click="scrollToBottom"
8+
:title="buttonText"
9+
>
10+
<icon-down class="scroll-btn-icon" />
11+
</div>
12+
</transition>
13+
</template>
14+
15+
<script setup>
16+
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
17+
import { IconDown } from '@arco-design/web-vue/es/icon'
18+
19+
// Props定义
20+
const props = defineProps({
21+
// 滚动目标元素的ref或选择器
22+
target: {
23+
type: [Object, String],
24+
default: null
25+
},
26+
// 显示按钮的滚动阈值(px)
27+
showThreshold: {
28+
type: Number,
29+
default: 200
30+
},
31+
// 隐藏按钮的底部距离阈值(px)
32+
hideThreshold: {
33+
type: Number,
34+
default: 100
35+
},
36+
// 按钮文本
37+
buttonText: {
38+
type: String,
39+
default: '直达底部'
40+
},
41+
// 按钮位置
42+
position: {
43+
type: Object,
44+
default: () => ({
45+
right: '24px',
46+
bottom: '80px'
47+
})
48+
}
49+
})
50+
51+
// 响应式数据
52+
const showButton = ref(false)
53+
54+
// 获取滚动目标元素
55+
const getTargetElement = () => {
56+
if (!props.target) {
57+
return document.documentElement || document.body
58+
}
59+
60+
if (typeof props.target === 'string') {
61+
return document.querySelector(props.target)
62+
}
63+
64+
// 如果是ref对象,需要检查value是否存在
65+
if (props.target && typeof props.target === 'object') {
66+
// Vue 3 ref对象
67+
if ('value' in props.target && props.target.value) {
68+
return props.target.value
69+
}
70+
// 直接是DOM元素
71+
if (props.target.nodeType) {
72+
return props.target
73+
}
74+
}
75+
76+
// 如果都不匹配,返回null让调用者处理
77+
return null
78+
}
79+
80+
// 滚动到底部函数
81+
const scrollToBottom = () => {
82+
const targetElement = getTargetElement()
83+
if (targetElement) {
84+
targetElement.scrollTo({
85+
top: targetElement.scrollHeight,
86+
behavior: 'smooth'
87+
})
88+
}
89+
}
90+
91+
// 滚动监听函数
92+
const handleScroll = () => {
93+
const targetElement = getTargetElement()
94+
if (targetElement) {
95+
const { scrollTop, scrollHeight, clientHeight } = targetElement
96+
97+
// 调试信息
98+
console.log('ScrollToBottom Debug:', {
99+
scrollTop,
100+
scrollHeight,
101+
clientHeight,
102+
showThreshold: props.showThreshold,
103+
hideThreshold: props.hideThreshold,
104+
condition1: scrollTop > props.showThreshold,
105+
condition2: scrollTop + clientHeight < scrollHeight - props.hideThreshold,
106+
targetElement: targetElement.className || targetElement.tagName
107+
})
108+
109+
// 当滚动距离超过阈值时显示按钮,当接近底部时隐藏按钮
110+
const shouldShow = scrollTop > props.showThreshold &&
111+
(scrollTop + clientHeight < scrollHeight - props.hideThreshold)
112+
showButton.value = shouldShow
113+
}
114+
}
115+
116+
// 生命周期
117+
onMounted(async () => {
118+
// 使用nextTick确保DOM已经渲染完成
119+
await nextTick()
120+
121+
// 如果第一次获取失败,稍等一下再试
122+
let targetElement = getTargetElement()
123+
if (!targetElement && props.target) {
124+
await new Promise(resolve => setTimeout(resolve, 100))
125+
targetElement = getTargetElement()
126+
}
127+
128+
console.log('ScrollToBottom mounted:', {
129+
target: props.target,
130+
targetElement,
131+
className: targetElement?.className,
132+
tagName: targetElement?.tagName
133+
})
134+
135+
if (targetElement) {
136+
targetElement.addEventListener('scroll', handleScroll)
137+
// 初始检查一次
138+
handleScroll()
139+
} else {
140+
console.warn('ScrollToBottom: 无法获取目标滚动元素')
141+
}
142+
})
143+
144+
onUnmounted(() => {
145+
const targetElement = getTargetElement()
146+
if (targetElement) {
147+
targetElement.removeEventListener('scroll', handleScroll)
148+
}
149+
})
150+
</script>
151+
152+
<style scoped>
153+
/* 直达底部按钮样式 */
154+
.scroll-to-bottom-btn {
155+
position: fixed;
156+
right: v-bind('position.right');
157+
bottom: v-bind('position.bottom');
158+
width: 48px;
159+
height: 48px;
160+
background: #6366f1;
161+
border-radius: 50%;
162+
display: flex;
163+
align-items: center;
164+
justify-content: center;
165+
cursor: pointer;
166+
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
167+
transition: all 0.3s ease;
168+
z-index: 1000;
169+
user-select: none;
170+
}
171+
172+
.scroll-to-bottom-btn:hover {
173+
background: #5855eb;
174+
transform: translateY(-2px);
175+
box-shadow: 0 6px 16px rgba(99, 102, 241, 0.4);
176+
}
177+
178+
.scroll-to-bottom-btn:active {
179+
transform: translateY(0);
180+
box-shadow: 0 2px 8px rgba(99, 102, 241, 0.3);
181+
}
182+
183+
.scroll-btn-icon {
184+
font-size: 20px;
185+
color: white;
186+
transition: transform 0.3s ease;
187+
}
188+
189+
.scroll-to-bottom-btn:hover .scroll-btn-icon {
190+
transform: translateY(2px);
191+
}
192+
193+
/* 按钮淡入淡出动画 */
194+
.scroll-btn-fade-enter-active,
195+
.scroll-btn-fade-leave-active {
196+
transition: all 0.3s ease;
197+
}
198+
199+
.scroll-btn-fade-enter-from,
200+
.scroll-btn-fade-leave-to {
201+
opacity: 0;
202+
transform: translateY(20px) scale(0.8);
203+
}
204+
205+
.scroll-btn-fade-enter-to,
206+
.scroll-btn-fade-leave-from {
207+
opacity: 1;
208+
transform: translateY(0) scale(1);
209+
}
210+
</style>

dashboard/src/views/Settings.vue

Lines changed: 3 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -822,16 +822,7 @@
822822
<About v-model:visible="aboutModalVisible" />
823823

824824
<!-- 直达底部按钮 -->
825-
<transition name="scroll-btn-fade">
826-
<div
827-
v-show="showScrollToBottomBtn"
828-
class="scroll-to-bottom-btn"
829-
@click="scrollToBottom"
830-
title="直达底部"
831-
>
832-
<icon-down class="scroll-btn-icon" />
833-
</div>
834-
</transition>
825+
<ScrollToBottom :target="settingsContainer" />
835826
</div>
836827
</template>
837828

@@ -869,13 +860,13 @@ import {
869860
IconClockCircle,
870861
IconComputer,
871862
IconDownload,
872-
IconCode,
873-
IconDown
863+
IconCode
874864
} from '@arco-design/web-vue/es/icon'
875865
import AddressHistory from '@/components/AddressHistory.vue'
876866
import PlayerSelector from '@/components/PlayerSelector.vue'
877867
import BackupRestoreDialog from '@/components/BackupRestoreDialog.vue'
878868
import About from '@/components/About.vue'
869+
import ScrollToBottom from '@/components/ScrollToBottom.vue'
879870
import configService from '@/api/services/config'
880871
import siteService from '@/api/services/site'
881872
import { factoryResetWithConfirmation } from '@/services/resetService'
@@ -964,8 +955,6 @@ const backupRestoreVisible = ref(false)
964955
// 关于弹窗状态
965956
const aboutModalVisible = ref(false)
966957
967-
// 直达底部按钮状态
968-
const showScrollToBottomBtn = ref(false)
969958
const settingsContainer = ref(null)
970959
971960
// 保存地址配置
@@ -1536,34 +1525,11 @@ const saveSettings = () => {
15361525
// 监听设置项变化并自动保存
15371526
watch(settings, saveSettings, { deep: true })
15381527
1539-
// 滚动到底部函数
1540-
const scrollToBottom = () => {
1541-
if (settingsContainer.value) {
1542-
settingsContainer.value.scrollTo({
1543-
top: settingsContainer.value.scrollHeight,
1544-
behavior: 'smooth'
1545-
})
1546-
}
1547-
}
15481528
1549-
// 滚动监听函数
1550-
const handleScroll = () => {
1551-
if (settingsContainer.value) {
1552-
const { scrollTop, scrollHeight, clientHeight } = settingsContainer.value
1553-
// 当滚动距离超过200px时显示按钮,当接近底部时隐藏按钮
1554-
const showButton = scrollTop > 200 && (scrollTop + clientHeight < scrollHeight - 100)
1555-
showScrollToBottomBtn.value = showButton
1556-
}
1557-
}
15581529
15591530
// 初始化
15601531
onMounted(async () => {
15611532
await loadConfig()
1562-
1563-
// 添加滚动监听
1564-
if (settingsContainer.value) {
1565-
settingsContainer.value.addEventListener('scroll', handleScroll)
1566-
}
15671533
})
15681534
</script>
15691535

@@ -2129,75 +2095,4 @@ onMounted(async () => {
21292095
21302096
21312097
2132-
/* 直达底部按钮样式 */
2133-
.scroll-to-bottom-btn {
2134-
position: fixed;
2135-
right: 24px;
2136-
bottom: 80px;
2137-
width: 48px;
2138-
height: 48px;
2139-
background: #6366f1;
2140-
border-radius: 50%;
2141-
display: flex;
2142-
align-items: center;
2143-
justify-content: center;
2144-
cursor: pointer;
2145-
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
2146-
transition: all 0.3s ease;
2147-
z-index: 1000;
2148-
user-select: none;
2149-
}
2150-
2151-
.scroll-to-bottom-btn:hover {
2152-
background: #5855eb;
2153-
transform: translateY(-2px);
2154-
box-shadow: 0 6px 16px rgba(99, 102, 241, 0.4);
2155-
}
2156-
2157-
.scroll-to-bottom-btn:active {
2158-
transform: translateY(0);
2159-
box-shadow: 0 2px 8px rgba(99, 102, 241, 0.3);
2160-
}
2161-
2162-
.scroll-btn-icon {
2163-
font-size: 20px;
2164-
color: white;
2165-
transition: transform 0.3s ease;
2166-
}
2167-
2168-
.scroll-to-bottom-btn:hover .scroll-btn-icon {
2169-
transform: translateY(2px);
2170-
}
2171-
2172-
/* 按钮淡入淡出动画 */
2173-
.scroll-btn-fade-enter-active,
2174-
.scroll-btn-fade-leave-active {
2175-
transition: all 0.3s ease;
2176-
}
2177-
2178-
.scroll-btn-fade-enter-from,
2179-
.scroll-btn-fade-leave-to {
2180-
opacity: 0;
2181-
transform: translateY(20px) scale(0.8);
2182-
}
2183-
2184-
.scroll-btn-fade-enter-to,
2185-
.scroll-btn-fade-leave-from {
2186-
opacity: 1;
2187-
transform: translateY(0) scale(1);
2188-
}
2189-
2190-
/* 响应式设计 */
2191-
@media (max-width: 768px) {
2192-
.scroll-to-bottom-btn {
2193-
right: 16px;
2194-
bottom: 70px;
2195-
width: 44px;
2196-
height: 44px;
2197-
}
2198-
2199-
.scroll-btn-icon {
2200-
font-size: 18px;
2201-
}
2202-
}
22032098
</style>

0 commit comments

Comments
 (0)