截图模块初始化
This commit is contained in:
@@ -13,7 +13,7 @@ defineProps<{
|
||||
class="flex-1"
|
||||
:style="{ backgroundColor: 'var(--effect-bg)', backdropFilter: 'var(--effect-blur)' }"
|
||||
>
|
||||
<ScrollArea class="h-full w-full">
|
||||
<ScrollArea data-main-scroll class="h-full w-full">
|
||||
<Transition name="fade-slide" mode="out-in">
|
||||
<div
|
||||
v-if="activeComponent"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { Search, Settings, ChevronRight } from '@lucide/vue'
|
||||
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { Search, Settings, ChevronRight, ArrowUp } from '@lucide/vue'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
@@ -137,6 +137,48 @@ if (tauriWindow) {
|
||||
// 初始时窗口已显示,恢复 hover
|
||||
restoreHover(300)
|
||||
|
||||
// ===== 回到顶部按钮 =====
|
||||
const showScrollTop = ref(false)
|
||||
let scrollViewport: HTMLElement | null = null
|
||||
let rafId: number | null = null
|
||||
|
||||
const handleMainScroll = () => {
|
||||
if (!scrollViewport) return
|
||||
showScrollTop.value = scrollViewport.scrollTop > 200
|
||||
}
|
||||
|
||||
const scrollToTop = () => {
|
||||
if (!scrollViewport) return
|
||||
const start = scrollViewport.scrollTop
|
||||
if (start === 0) return
|
||||
// 物理变速滚动:cubic ease-out,先快后慢
|
||||
const duration = 500
|
||||
const startTime = performance.now()
|
||||
const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3)
|
||||
if (rafId !== null) cancelAnimationFrame(rafId)
|
||||
const animate = (now: number) => {
|
||||
const elapsed = now - startTime
|
||||
const progress = Math.min(elapsed / duration, 1)
|
||||
scrollViewport!.scrollTop = start * (1 - easeOutCubic(progress))
|
||||
if (progress < 1) {
|
||||
rafId = requestAnimationFrame(animate)
|
||||
} else {
|
||||
rafId = null
|
||||
}
|
||||
}
|
||||
rafId = requestAnimationFrame(animate)
|
||||
}
|
||||
|
||||
const initScrollListener = () => {
|
||||
scrollViewport = document.querySelector<HTMLElement>(
|
||||
'[data-main-scroll] [data-reka-scroll-area-viewport]'
|
||||
)
|
||||
if (scrollViewport) {
|
||||
scrollViewport.addEventListener('scroll', handleMainScroll, { passive: true })
|
||||
handleMainScroll()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (tauriWindow) {
|
||||
try {
|
||||
@@ -152,12 +194,16 @@ onMounted(async () => {
|
||||
invoke('fix_snap_background').catch(() => {})
|
||||
}, 1000)
|
||||
}
|
||||
// 等待模块容器渲染后绑定滚动监听
|
||||
nextTick(initScrollListener)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('mousemove', handleFirstMouseMove)
|
||||
if (restoreHoverTimer) clearTimeout(restoreHoverTimer)
|
||||
if (unlistenMaximize) unlistenMaximize()
|
||||
if (scrollViewport) scrollViewport.removeEventListener('scroll', handleMainScroll)
|
||||
if (rafId !== null) cancelAnimationFrame(rafId)
|
||||
})
|
||||
|
||||
const handleBlur = () => {
|
||||
@@ -178,28 +224,41 @@ const handleBlur = () => {
|
||||
>
|
||||
<span class="font-semibold text-sm">Thing</span>
|
||||
|
||||
<!-- 浮动标签切换器:模块内 TabsList 滚出可视区时显示 -->
|
||||
<Transition name="floating-tabs">
|
||||
<div
|
||||
v-if="tabsStore.floatingVisible && tabsStore.tabs.length > 0"
|
||||
class="flex items-center gap-0.5 ml-2 pointer-events-auto"
|
||||
>
|
||||
<!-- 浮动标签切换器 + 回到顶部按钮 -->
|
||||
<div class="flex items-center gap-1 ml-2 pointer-events-auto">
|
||||
<Transition name="floating-tabs">
|
||||
<div
|
||||
v-if="tabsStore.floatingVisible && tabsStore.tabs.length > 0"
|
||||
class="flex items-center gap-0.5"
|
||||
>
|
||||
<button
|
||||
v-for="tab in tabsStore.tabs"
|
||||
:key="tab.value"
|
||||
class="px-2.5 py-1 text-xs font-medium rounded-md transition-colors"
|
||||
:class="
|
||||
tabsStore.activeTab === tab.value
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-secondary/60'
|
||||
"
|
||||
@click="tabsStore.setActiveTab(tab.value)"
|
||||
@mousedown.stop
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</div>
|
||||
</Transition>
|
||||
<Transition name="floating-tabs">
|
||||
<button
|
||||
v-for="tab in tabsStore.tabs"
|
||||
:key="tab.value"
|
||||
class="px-2.5 py-1 text-xs font-medium rounded-md transition-colors"
|
||||
:class="
|
||||
tabsStore.activeTab === tab.value
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-secondary/60'
|
||||
"
|
||||
@click="tabsStore.setActiveTab(tab.value)"
|
||||
v-if="showScrollTop"
|
||||
class="flex items-center justify-center h-6 w-6 rounded-md text-muted-foreground hover:text-foreground hover:bg-secondary/60 transition-colors"
|
||||
title="回到顶部"
|
||||
@click="scrollToTop"
|
||||
@mousedown.stop
|
||||
>
|
||||
{{ tab.label }}
|
||||
<ArrowUp class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</Transition>
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
<div class="flex-1"></div>
|
||||
<div class="relative max-w-xs mr-3 pointer-events-auto">
|
||||
@@ -310,6 +369,7 @@ const handleBlur = () => {
|
||||
.hover-suppressed button:hover,
|
||||
.hover-suppressed button.is-hovered {
|
||||
background-color: transparent !important;
|
||||
color: var(--foreground) !important;
|
||||
}
|
||||
|
||||
/* 最大化按钮:snap-layout 插件的原生子窗口遮挡了指针事件,
|
||||
|
||||
Reference in New Issue
Block a user