@@ -2345,9 +2345,18 @@ const handleMouseDown = (event) => {
23452345
23462346 // 如果点击的是空白区域,记录起始位置但不立即创建选择框
23472347 if (tokensContainer && ! tokenItem) {
2348+ // 重置所有相关状态变量,确保开始新的框选操作前状态是干净的
2349+ isPotentialBoxSelection .value = false
2350+ isSelecting .value = false
2351+ isUpdatingSelectionBox .value = false
2352+ isUpdatingSelectedTokens .value = false
2353+
23482354 // 在开始新的框选前,先关闭任何已存在的操作菜单
23492355 closeSelectionActions ()
23502356
2357+ // 移除任何可能残留的选择框
2358+ removeSelectionBox ()
2359+
23512360 // 获取容器的位置,将选择框限制在容器内
23522361 const containerRect = tokensContainer .getBoundingClientRect ()
23532362 selectionStart .value = {
@@ -2425,7 +2434,8 @@ const handleMouseMove = (event) => {
24252434
24262435 // 分离选择框更新和标签选中状态更新
24272436 // 1. 选择框更新:使用requestAnimationFrame保持流畅动画,不使用节流
2428- if (! isUpdatingSelectionBox .value ) {
2437+ // 添加防御性检查,确保即使isUpdatingSelectionBox被卡住也能继续更新
2438+ if (! isUpdatingSelectionBox .value || (Date .now () - lastUpdateTime .value > 100 )) {
24292439 isUpdatingSelectionBox .value = true
24302440 requestAnimationFrame (() => {
24312441 updateSelectionBox ()
@@ -2455,8 +2465,11 @@ const handleMouseUp = () => {
24552465 // 先保存选中的标签数量,然后才结束框选模式
24562466 const selectedCount = selectedTokens .value .length
24572467
2468+ // 确保所有状态变量都被正确重置
24582469 isSelecting .value = false
24592470 isBoxSelectMode .value = false // 退出框选模式
2471+ isUpdatingSelectionBox .value = false
2472+ isUpdatingSelectedTokens .value = false
24602473
24612474 // 移除选择框
24622475 removeSelectionBox ()
@@ -2470,66 +2483,84 @@ const handleMouseUp = () => {
24702483
24712484// 创建选择框元素 - 持久稳定版
24722485const createSelectionBox = () => {
2473- // 先检查是否已经有选择框存在
2474- let selectionBox = document .getElementById (selectionBoxId)
2475- if (selectionBox) {
2476- // 如果存在,先移除它
2477- document .body .removeChild (selectionBox)
2486+ try {
2487+ // 先检查是否已经有选择框存在
2488+ let selectionBox = document .getElementById (selectionBoxId)
2489+ if (selectionBox) {
2490+ // 如果存在,先移除它
2491+ document .body .removeChild (selectionBox)
2492+ }
2493+
2494+ // 创建新的选择框
2495+ selectionBox = document .createElement (' div' )
2496+ selectionBox .id = selectionBoxId
2497+
2498+ // 增强选择框的可见性,使用更醒目的样式
2499+ selectionBox .style .cssText = `
2500+ position: fixed;
2501+ background-color: rgba(66, 133, 244, 0.4);
2502+ border: 2px dashed #4285f4;
2503+ box-shadow: 0 0 12px rgba(66, 133, 244, 0.6);
2504+ pointer-events: none;
2505+ z-index: 99999;
2506+ transition: none;
2507+ opacity: 1;
2508+ display: block;
2509+ `
2510+
2511+ document .body .appendChild (selectionBox)
2512+ console .log (' 选择框已创建' )
2513+ updateSelectionBox ()
2514+ } catch (error) {
2515+ console .error (' 创建选择框失败:' , error)
2516+ // 重置状态,确保后续操作不受影响
2517+ isUpdatingSelectionBox .value = false
24782518 }
2479-
2480- // 创建新的选择框
2481- selectionBox = document .createElement (' div' )
2482- selectionBox .id = selectionBoxId
2483-
2484- // 增强选择框的可见性,使用更醒目的样式
2485- selectionBox .style .cssText = `
2486- position: fixed;
2487- background-color: rgba(66, 133, 244, 0.4);
2488- border: 2px dashed #4285f4;
2489- box-shadow: 0 0 12px rgba(66, 133, 244, 0.6);
2490- pointer-events: none;
2491- z-index: 99999;
2492- transition: none;
2493- opacity: 1;
2494- display: block;
2495- `
2496-
2497- document .body .appendChild (selectionBox)
2498- console .log (' 选择框已创建' )
2499- updateSelectionBox ()
25002519}
25012520
25022521// 更新选择框位置和大小 - 增强版
25032522const updateSelectionBox = () => {
2504- let selectionBox = document .getElementById (selectionBoxId)
2505- // 如果选择框不存在,重新创建它
2506- if (! selectionBox && isSelecting .value ) {
2507- createSelectionBox ()
2508- selectionBox = document .getElementById (selectionBoxId)
2509- if (! selectionBox) return
2523+ try {
2524+ let selectionBox = document .getElementById (selectionBoxId)
2525+ // 如果选择框不存在,重新创建它
2526+ if (! selectionBox && isSelecting .value ) {
2527+ createSelectionBox ()
2528+ selectionBox = document .getElementById (selectionBoxId)
2529+ if (! selectionBox) return
2530+ }
2531+
2532+ const left = Math .min (selectionStart .value .x , selectionEnd .value .x )
2533+ const top = Math .min (selectionStart .value .y , selectionEnd .value .y )
2534+ const width = Math .abs (selectionEnd .value .x - selectionStart .value .x )
2535+ const height = Math .abs (selectionEnd .value .y - selectionStart .value .y )
2536+
2537+ // 确保选择框有足够的大小可见
2538+ const minSize = 5
2539+ const effectiveWidth = Math .max (width, minSize)
2540+ const effectiveHeight = Math .max (height, minSize)
2541+
2542+ if (selectionBox) {
2543+ selectionBox .style .left = ` ${ left} px`
2544+ selectionBox .style .top = ` ${ top} px`
2545+ selectionBox .style .width = ` ${ effectiveWidth} px`
2546+ selectionBox .style .height = ` ${ effectiveHeight} px`
2547+ }
2548+ } catch (error) {
2549+ console .error (' 更新选择框失败:' , error)
2550+ // 重置状态,确保后续操作不受影响
2551+ isUpdatingSelectionBox .value = false
25102552 }
2511-
2512- const left = Math .min (selectionStart .value .x , selectionEnd .value .x )
2513- const top = Math .min (selectionStart .value .y , selectionEnd .value .y )
2514- const width = Math .abs (selectionEnd .value .x - selectionStart .value .x )
2515- const height = Math .abs (selectionEnd .value .y - selectionStart .value .y )
2516-
2517- // 确保选择框有足够的大小可见
2518- const minSize = 5
2519- const effectiveWidth = Math .max (width, minSize)
2520- const effectiveHeight = Math .max (height, minSize)
2521-
2522- selectionBox .style .left = ` ${ left} px`
2523- selectionBox .style .top = ` ${ top} px`
2524- selectionBox .style .width = ` ${ effectiveWidth} px`
2525- selectionBox .style .height = ` ${ effectiveHeight} px`
25262553}
25272554
25282555// 移除选择框元素
25292556const removeSelectionBox = () => {
2530- const selectionBox = document .getElementById (selectionBoxId)
2531- if (selectionBox) {
2532- document .body .removeChild (selectionBox)
2557+ try {
2558+ const selectionBox = document .getElementById (selectionBoxId)
2559+ if (selectionBox) {
2560+ document .body .removeChild (selectionBox)
2561+ }
2562+ } catch (error) {
2563+ console .error (' 移除选择框失败:' , error)
25332564 }
25342565}
25352566
@@ -2583,14 +2614,14 @@ const applySelectedStyle = () => {
25832614
25842615 // 批量更新DOM,减少重排重绘
25852616 toSelect .forEach (box => {
2586- // 为整个标签容器添加视觉反馈
2587- box .style .border = ' 2px solid #4285f4'
2588- box .style .boxShadow = ' 0 0 5px rgba(66, 133, 244, 0.5)'
2589- box .style .backgroundColor = ' rgba(66, 133, 244, 0.1)'
2617+ // 为整个标签容器添加选中类,通过CSS优先级确保显示在禁用样式之上
2618+ box .classList .add (' token-item-box-selected' )
25902619 })
25912620
25922621 toDeselect .forEach (box => {
25932622 // 移除视觉反馈
2623+ box .classList .remove (' token-item-box-selected' )
2624+ // 清除之前可能设置的内联样式
25942625 box .style .border = ' '
25952626 box .style .boxShadow = ' '
25962627 box .style .backgroundColor = box .dataset .originalBgColor || ' '
@@ -2743,19 +2774,54 @@ const bulkDeleteSelectedTokens = () => {
27432774
27442775// 清除选中的标签
27452776const clearSelectedTokens = () => {
2746- // 只处理有选中样式的标签,减少DOM操作量
2747- const styledBoxes = document .querySelectorAll (' .token-item-box[style*="border: 2px solid"]' )
2748- styledBoxes .forEach (box => {
2777+ // 处理所有带有选中类的标签,确保移除所有选中样式
2778+ const selectedBoxes = document .querySelectorAll (' .token-item-box-selected' )
2779+ selectedBoxes .forEach (box => {
2780+ // 移除选中类
2781+ box .classList .remove (' token-item-box-selected' )
2782+ // 清除可能的内联样式
27492783 box .style .border = ' '
27502784 box .style .boxShadow = ' '
27512785 box .style .backgroundColor = box .dataset .originalBgColor || ' '
27522786 })
2787+
2788+ // 清空选中状态数组
27532789 selectedTokens .value = []
2790+
2791+ // 如果操作菜单可见,也关闭它
2792+ if (showSelectionActions .value ) {
2793+ showSelectionActions .value = false
2794+ document .removeEventListener (' click' , closeSelectionActionsOnClickOutside)
2795+ }
2796+ }
2797+
2798+ // 处理点击空白区域取消框选状态
2799+ const handleClickToClearSelection = (event ) => {
2800+ // 只有当有选中项时才需要处理
2801+ if (selectedTokens .value .length === 0 ) return ;
2802+
2803+ // 检查点击目标是否在标签容器内且不是标签本身、控制元素或操作菜单
2804+ const tokenItem = event .target .closest (' .token-item-box, .token-item, .token-controls, .delete-btn, .weight-control, .bracket-btn, .translate-button, .tag-tips-box' );
2805+ const tokensContainer = tokensContainerRef .value ;
2806+
2807+ // 如果点击的是文本框或完全在标签容器外部的空白区域,则清除选中状态
2808+ if (event .target === inputAreaRef .value ||
2809+ (! tokenItem && (! tokensContainer || ! tokensContainer .contains (event .target )))) {
2810+ clearSelectedTokens ();
2811+ }
27542812}
27552813
2814+ // 组件挂载时添加事件监听
2815+ onMounted (() => {
2816+ // 添加点击事件监听,点击空白处或文本框时清除框选状态
2817+ document .addEventListener (' click' , handleClickToClearSelection);
2818+ })
2819+
27562820// 组件卸载时清理事件监听
27572821onUnmounted (() => {
27582822 document .removeEventListener (' click' , handleClickOutside)
2823+ // 移除点击清除框选的事件监听
2824+ document .removeEventListener (' click' , handleClickToClearSelection)
27592825 if (historyTimer .value ) {
27602826 clearTimeout (historyTimer .value );
27612827 }
0 commit comments