@@ -417,32 +417,128 @@ export default {
417417 }
418418 }
419419
420+ /**
421+ * 检查元素在画布中的可用空间
422+ * @param {number} top - 选中元素顶部位置
423+ * @param {number} selectedHeight - 选中元素高度
424+ * @param {number} canvasHeight - 画布高度
425+ * @param {number} elementHeight - 要放置元素的高度
426+ * @returns {{hasTopSpace: boolean, hasBottomSpace: boolean}}
427+ */
428+ const checkElementSpace = (top , selectedHeight , canvasHeight , elementHeight ) => {
429+ return {
430+ hasTopSpace: top >= elementHeight,
431+ hasBottomSpace: canvasHeight - top - selectedHeight >= elementHeight
432+ }
433+ }
434+
435+ /**
436+ * 根据策略决定元素应该放置在顶部还是底部
437+ * @param {number} top - 选中元素顶部位置
438+ * @param {number} elementHeight - 要放置元素的高度
439+ * @param {boolean} hasTopSpace - 顶部是否有足够空间
440+ * @param {boolean} hasBottomSpace - 底部是否有足够空间
441+ * @param {string} strategy - 放置策略 ('topFirst' | 'bottomFirst')
442+ * @returns {boolean} 是否放置在底部
443+ */
444+ const determineElementPosition = (hasTopSpace , hasBottomSpace , strategy = ' topFirst' ) => {
445+ if (strategy === ' bottomFirst' ) {
446+ // Option策略:优先底部,或顶部没空间时放底部
447+ return hasBottomSpace || ! hasTopSpace
448+ } else {
449+ // Label策略:顶部没空间且底部有空间才放底部
450+ return ! hasTopSpace && hasBottomSpace
451+ }
452+ }
453+
454+ /**
455+ * 通用的垂直对齐计算函数
456+ * @param {boolean} isAtBottom - 是否放置在底部
457+ * @param {number} elementHeight - 元素高度
458+ * @param {boolean} hasTopSpace - 顶部是否有足够空间
459+ * @param {boolean} hasBottomSpace - 底部是否有足够空间
460+ * @param {boolean} bottomFirst - 是否底部优先策略(true: Option策略, false: Label策略)
461+ * @returns {{alignTop: boolean, verticalValue: number}}
462+ */
463+ const calculateVerticalAlignment = (
464+ isAtBottom,
465+ elementHeight,
466+ hasTopSpace,
467+ hasBottomSpace,
468+ bottomFirst = false
469+ ) => {
470+ const alignTop = ! isAtBottom
471+
472+ let verticalValue
473+ if (bottomFirst) {
474+ // Option策略:不在底部 OR 底部有空间时偏移
475+ verticalValue = ! isAtBottom || hasBottomSpace ? - elementHeight : 0
476+ } else {
477+ // Label策略:在底部 OR 顶部有空间时偏移
478+ verticalValue = isAtBottom || hasTopSpace ? - elementHeight : 0
479+ }
480+
481+ return { alignTop, verticalValue }
482+ }
483+
484+ /**
485+ * 一站式元素对齐计算函数(组合了空间检查、位置决策、对齐计算)
486+ * @param {number} top - 选中元素顶部位置
487+ * @param {number} selectedHeight - 选中元素高度
488+ * @param {number} canvasHeight - 画布高度
489+ * @param {number} elementHeight - 要放置元素的高度
490+ * @param {string} strategy - 放置策略 ('topFirst' | 'bottomFirst')
491+ * @returns {{alignTop: boolean, verticalValue: number}}
492+ */
493+ const calculateElementAlignment = (top , selectedHeight , canvasHeight , elementHeight , strategy = ' topFirst' ) => {
494+ const spaceInfo = checkElementSpace (top, selectedHeight, canvasHeight, elementHeight)
495+ const isAtBottom = determineElementPosition (spaceInfo .hasTopSpace , spaceInfo .hasBottomSpace , strategy)
496+ return calculateVerticalAlignment (
497+ isAtBottom,
498+ elementHeight,
499+ spaceInfo .hasTopSpace ,
500+ spaceInfo .hasBottomSpace ,
501+ strategy === ' bottomFirst'
502+ )
503+ }
504+
420505 const getStyleValues = (selectState , canvasSize , labelWidth , optionWidth ) => {
421506 const { left , top , width , height , doc } = selectState
422507 const { width: canvasWidth , height: canvasHeight } = canvasSize
423508 // 标签宽度和工具操作条宽度之和加上间距
424509 const fullRectWidth = labelWidth + optionWidth + OPTION_SPACE
510+ const labelAlignment = calculateElementAlignment (
511+ top,
512+ height,
513+ canvasHeight,
514+ LABEL_HEIGHT ,
515+ ' topFirst' // Label策略:顶部优先
516+ )
425517
426- // 是否 将label 标签放置到底部,判断 top 距离
427- const isLabelAtBottom = top < LABEL_HEIGHT
428518 const labelAlign = new Align ({
429519 alignLeft: true ,
430520 horizontalValue: 0 ,
431- alignTop: ! isLabelAtBottom ,
432- verticalValue: - LABEL_HEIGHT
521+ alignTop: labelAlignment . alignTop ,
522+ verticalValue: labelAlignment . verticalValue
433523 })
434524
435525 if (! doc) {
436526 return {}
437527 }
438528
439- // 是否将操作栏放置到底部,判断当前选中组件底部与页面底部的距离。
440- const isOptionAtBottom = canvasHeight - top - height >= OPTION_BAR_HEIGHT
529+ const optionAlignment = calculateElementAlignment (
530+ top,
531+ height,
532+ canvasHeight,
533+ OPTION_BAR_HEIGHT ,
534+ ' bottomFirst' // Option策略:底部优先
535+ )
536+
441537 const optionAlign = new Align ({
442538 alignLeft: false ,
443539 horizontalValue: 0 ,
444- alignTop: ! isOptionAtBottom ,
445- verticalValue: - OPTION_BAR_HEIGHT
540+ alignTop: optionAlignment . alignTop ,
541+ verticalValue: optionAlignment . verticalValue
446542 })
447543
448544 const scrollBarWidth = doc .documentElement .scrollHeight > doc .documentElement .clientHeight ? SCROLL_BAR_WIDTH : 0
@@ -462,7 +558,7 @@ export default {
462558 optionAlign .align (positions .LEFT )
463559 }
464560
465- if (isLabelAtBottom === isOptionAtBottom ) {
561+ if (labelAlignment . alignTop === optionAlignment . alignTop ) {
466562 // 标签框和工具操作框都在顶部或者都在底部
467563
468564 if (left + fullRectWidth < canvasWidth) {
@@ -562,6 +658,12 @@ export default {
562658 pointer- events: none;
563659 border: 1px solid var (-- te- canvas- container- border- color- checked);
564660 z- index: 2 ;
661+ // 禁止文本选择
662+ - webkit- user- select: none;
663+ - moz- user- select: none;
664+ - ms- user- select: none;
665+ user- select: none;
666+
565667 & .absolute {
566668 pointer- events: all;
567669 }
0 commit comments