From 57a20298477488bc885c3df5a05839ac0c8ba313 Mon Sep 17 00:00:00 2001 From: wintsa <770775984@qq.com> Date: Thu, 26 Feb 2026 18:04:36 +0800 Subject: [PATCH] =?UTF-8?q?fix=20=E6=8B=96=E5=8A=A8=E6=B5=81=E7=95=85?= =?UTF-8?q?=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ui/tooltip/TooltipContent.vue | 33 ++ src/components/ui/tooltip/index.ts | 2 + src/components/views/ContractDetailView.vue | 2 + src/components/views/Ht.vue | 419 ++++++++++++++++-- src/components/views/Xm.vue | 2 +- src/components/views/ZxFwView.vue | 5 +- src/components/views/htInfo.vue | 2 +- .../pricingView/LandScalePricingPane.vue | 10 +- src/components/views/xmInfo.vue | 22 +- src/components/views/zxFw.vue | 11 +- src/layout/tab.vue | 76 +++- src/layout/typeLine.vue | 54 ++- tsconfig.tsbuildinfo | 2 +- 13 files changed, 576 insertions(+), 64 deletions(-) create mode 100644 src/components/ui/tooltip/TooltipContent.vue create mode 100644 src/components/ui/tooltip/index.ts diff --git a/src/components/ui/tooltip/TooltipContent.vue b/src/components/ui/tooltip/TooltipContent.vue new file mode 100644 index 0000000..1533d3f --- /dev/null +++ b/src/components/ui/tooltip/TooltipContent.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/components/ui/tooltip/index.ts b/src/components/ui/tooltip/index.ts new file mode 100644 index 0000000..173a7dc --- /dev/null +++ b/src/components/ui/tooltip/index.ts @@ -0,0 +1,2 @@ +export { default as TooltipContent } from './TooltipContent.vue' +export { TooltipProvider, TooltipRoot, TooltipTrigger } from 'reka-ui' diff --git a/src/components/views/ContractDetailView.vue b/src/components/views/ContractDetailView.vue index 43a3a6c..f38bf0e 100644 --- a/src/components/views/ContractDetailView.vue +++ b/src/components/views/ContractDetailView.vue @@ -3,6 +3,8 @@ -import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue' +import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue' import draggable from 'vuedraggable' import localforage from 'localforage' import { Card, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' +import { ScrollArea } from '@/components/ui/scroll-area' +import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from '@/components/ui/tooltip' import { useTabStore } from '@/pinia/tab' -import { Edit3, Plus, Trash2, X } from 'lucide-vue-next' +import { Edit3, GripVertical, Plus, Trash2, X } from 'lucide-vue-next' import { ToastAction, ToastDescription, @@ -33,6 +35,8 @@ const tabStore = useTabStore() const contracts = ref([]) +const contractSearchKeyword = ref('') +const isListLayout = ref(false) const showCreateModal = ref(false) const contractNameInput = ref('') @@ -45,11 +49,27 @@ let dragStartX = 0 let dragStartY = 0 let baseOffsetX = 0 let baseOffsetY = 0 +const contractListScrollWrapRef = ref(null) +const contractListViewportRef = ref(null) +const isDraggingContracts = ref(false) +let contractAutoScrollRaf = 0 +let dragPointerClientY: number | null = null const buildDefaultContracts = (): ContractItem[] => [ ] +const normalizedSearchKeyword = computed(() => contractSearchKeyword.value.trim().toLowerCase()) +const filteredContracts = computed(() => { + if (!normalizedSearchKeyword.value) return contracts.value + return contracts.value.filter(item => { + const name = item.name.toLowerCase() + const id = item.id.toLowerCase() + return name.includes(normalizedSearchKeyword.value) || id.includes(normalizedSearchKeyword.value) + }) +}) +const isSearchingContracts = computed(() => Boolean(normalizedSearchKeyword.value)) + const normalizeOrder = (list: ContractItem[]): ContractItem[] => list.map((item, index) => ({ ...item, @@ -74,6 +94,23 @@ const notify = (text: string) => { }) } +const getContractListViewport = () => { + const viewport = contractListScrollWrapRef.value?.querySelector( + '[data-slot="scroll-area-viewport"]' + ) || null + contractListViewportRef.value = viewport + return viewport +} + +const scrollContractsToBottom = (behavior: ScrollBehavior = 'smooth') => { + const viewport = getContractListViewport() + if (!viewport) return + viewport.scrollTo({ + top: viewport.scrollHeight, + behavior + }) +} + const saveContracts = async () => { try { contracts.value = normalizeOrder(contracts.value) @@ -189,6 +226,8 @@ const createContract = async () => { await saveContracts() notify('新建成功') closeCreateModal() + await nextTick() + scrollContractsToBottom() } const deleteContract = async (id: string) => { @@ -206,6 +245,7 @@ const deleteContract = async (id: string) => { } const handleDragEnd = async (event: { oldIndex?: number; newIndex?: number }) => { + stopContractAutoScroll() if ( event.oldIndex == null || event.newIndex == null || @@ -218,6 +258,65 @@ const handleDragEnd = async (event: { oldIndex?: number; newIndex?: number }) => notify('排序完成') } +const updateDragPointerPosition = (event: MouseEvent | DragEvent) => { + if (!isDraggingContracts.value) return + dragPointerClientY = event.clientY +} + +const contractAutoScrollTick = () => { + if (!isDraggingContracts.value) { + contractAutoScrollRaf = 0 + return + } + + const viewport = contractListViewportRef.value || getContractListViewport() + const clientY = dragPointerClientY + if (viewport && clientY != null) { + const rect = viewport.getBoundingClientRect() + const edge = 88 + const maxStep = 22 + let delta = 0 + + if (clientY < rect.top + edge) { + const ratio = Math.max(0, Math.min(1, (rect.top + edge - clientY) / edge)) + delta = -Math.ceil(maxStep * ratio) + } else if (clientY > rect.bottom - edge) { + const ratio = Math.max(0, Math.min(1, (clientY - (rect.bottom - edge)) / edge)) + delta = Math.ceil(maxStep * ratio) + } + + if (delta !== 0) { + viewport.scrollTop = Math.max( + 0, + Math.min(viewport.scrollTop + delta, viewport.scrollHeight - viewport.clientHeight) + ) + } + } + + contractAutoScrollRaf = window.requestAnimationFrame(contractAutoScrollTick) +} + +const startContractAutoScroll = () => { + getContractListViewport() + isDraggingContracts.value = true + dragPointerClientY = null + window.addEventListener('pointermove', updateDragPointerPosition as EventListener, { passive: true }) + window.addEventListener('dragover', updateDragPointerPosition as EventListener, { passive: true }) + if (contractAutoScrollRaf) cancelAnimationFrame(contractAutoScrollRaf) + contractAutoScrollRaf = window.requestAnimationFrame(contractAutoScrollTick) +} + +const stopContractAutoScroll = () => { + isDraggingContracts.value = false + dragPointerClientY = null + window.removeEventListener('pointermove', updateDragPointerPosition as EventListener) + window.removeEventListener('dragover', updateDragPointerPosition as EventListener) + if (contractAutoScrollRaf) { + cancelAnimationFrame(contractAutoScrollRaf) + contractAutoScrollRaf = 0 + } +} + const handleCardClick = (item: ContractItem) => { tabStore.openTab({ id: `contract-${item.id}`, @@ -250,66 +349,279 @@ const startDrag = (event: MouseEvent) => { onMounted(async () => { await loadContracts() + await nextTick() + getContractListViewport() }) onBeforeUnmount(() => { stopDrag() + stopContractAutoScroll() void saveContracts() }) + + diff --git a/src/components/views/Xm.vue b/src/components/views/Xm.vue index 450dda1..0937c68 100644 --- a/src/components/views/Xm.vue +++ b/src/components/views/Xm.vue @@ -1,7 +1,7 @@