mirror of
https://gitee.com/dromara/go-view.git
synced 2026-07-14 03:21:06 +08:00
Merge branch 'dev'
This commit is contained in:
commit
4ce5435ef1
@ -1,3 +0,0 @@
|
|||||||
import EditAlignLine from './index.vue'
|
|
||||||
|
|
||||||
export { EditAlignLine }
|
|
||||||
@ -1,285 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="go-edit-align-line">
|
|
||||||
<div
|
|
||||||
class="line"
|
|
||||||
v-for="item in line.lineArr"
|
|
||||||
:key="item"
|
|
||||||
:class="[item.includes('row') ? 'row' : 'col', line['select'].has(item) && 'visible']"
|
|
||||||
:style="useComponentStyle(line['select'].get(item))"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { reactive, computed, watch } from 'vue'
|
|
||||||
import { useDesignStore } from '@/store/modules/designStore/designStore'
|
|
||||||
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
|
|
||||||
import { EditCanvasTypeEnum } from '@/store/modules/chartEditStore/chartEditStore.d'
|
|
||||||
import { useSettingStore } from '@/store/modules/settingStore/settingStore'
|
|
||||||
import { CreateComponentType, CreateComponentGroupType } from '@/packages/index.d'
|
|
||||||
import { setComponentPosition } from '@/utils'
|
|
||||||
import throttle from 'lodash/throttle'
|
|
||||||
import cloneDeep from 'lodash/cloneDeep'
|
|
||||||
// 全局颜色
|
|
||||||
const designStore = useDesignStore()
|
|
||||||
|
|
||||||
const chartEditStore = useChartEditStore()
|
|
||||||
const settingStore = useSettingStore()
|
|
||||||
|
|
||||||
// * 线条集合
|
|
||||||
const line = reactive({
|
|
||||||
// 行横向row(上中下),列竖线col(左中右)
|
|
||||||
lineArr: ['rowt', 'rowc', 'rowb', 'coll', 'colc', 'colr'],
|
|
||||||
// 展示线
|
|
||||||
select: new Map(),
|
|
||||||
// 已经吸附
|
|
||||||
sorptioned: {
|
|
||||||
x: false,
|
|
||||||
y: false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// * 位置计算
|
|
||||||
const useComponentStyle = (attr?: Partial<{ x: number; y: number }>) => {
|
|
||||||
if (!attr) return {}
|
|
||||||
const componentStyle = {
|
|
||||||
left: `${attr.x ? attr.x : 0}px`,
|
|
||||||
top: `${attr.y ? attr.y : 0}px`
|
|
||||||
}
|
|
||||||
return componentStyle
|
|
||||||
}
|
|
||||||
|
|
||||||
// 颜色
|
|
||||||
const themeColor = computed(() => {
|
|
||||||
return designStore.getAppTheme
|
|
||||||
})
|
|
||||||
|
|
||||||
// * 吸附距离
|
|
||||||
const minDistance = computed(() => {
|
|
||||||
return settingStore.getChartAlignRange
|
|
||||||
})
|
|
||||||
|
|
||||||
// * 是否开始计算
|
|
||||||
const isComputedLine = computed(() => {
|
|
||||||
// IS_DRAG 移动时为 true,Drag Hook里设置
|
|
||||||
const isDrag = chartEditStore.getEditCanvas[EditCanvasTypeEnum.IS_DRAG]
|
|
||||||
return isDrag
|
|
||||||
})
|
|
||||||
|
|
||||||
// * 吸附判定
|
|
||||||
const isSorption = (selectValue: number, componentValue: number) => {
|
|
||||||
const isSorption = Math.abs(selectValue - componentValue) <= minDistance.value
|
|
||||||
return isSorption
|
|
||||||
}
|
|
||||||
|
|
||||||
// * 当前目标
|
|
||||||
const selectId = computed(() => chartEditStore.getTargetChart.selectId)
|
|
||||||
const selectTarget = computed(() => chartEditStore.getComponentList[chartEditStore.fetchTargetIndex()])
|
|
||||||
const selectAttr = computed(() => selectTarget.value?.attr || {})
|
|
||||||
|
|
||||||
// * 画布坐标
|
|
||||||
const canvasPositionList = computed(() => {
|
|
||||||
return {
|
|
||||||
id: '0',
|
|
||||||
attr: {
|
|
||||||
w: cloneDeep(chartEditStore.getEditCanvasConfig.width),
|
|
||||||
h: cloneDeep(chartEditStore.getEditCanvasConfig.height),
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
offsetX: 0,
|
|
||||||
offsetY: 0,
|
|
||||||
zIndex: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// * 监听鼠标移动
|
|
||||||
watch(
|
|
||||||
() => chartEditStore.getMousePosition,
|
|
||||||
throttle(() => {
|
|
||||||
try {
|
|
||||||
if (!isComputedLine.value || selectId.value.length !== 1) return
|
|
||||||
// 获取目标组件数据
|
|
||||||
|
|
||||||
const selectW = selectAttr.value.w
|
|
||||||
const selectH = selectAttr.value.h
|
|
||||||
|
|
||||||
// 距离左侧
|
|
||||||
const selectLeftX = selectAttr.value.x
|
|
||||||
const selectHalfX = selectLeftX + selectW / 2
|
|
||||||
const selectRightX = selectLeftX + selectW
|
|
||||||
const seletX = [selectLeftX, selectHalfX, selectRightX]
|
|
||||||
|
|
||||||
// 距离顶部
|
|
||||||
const selectTopY = selectAttr.value.y
|
|
||||||
const selectHalfY = selectTopY + selectH / 2
|
|
||||||
const selectBottomY = selectTopY + selectH
|
|
||||||
const selectY = [selectTopY, selectHalfY, selectBottomY]
|
|
||||||
|
|
||||||
line.select.clear()
|
|
||||||
line.sorptioned.y = false
|
|
||||||
// 循环查询所有组件数据
|
|
||||||
const componentList = chartEditStore.getComponentList.map((e: CreateComponentType | CreateComponentGroupType) => {
|
|
||||||
return {
|
|
||||||
id: e.id,
|
|
||||||
attr: e.attr
|
|
||||||
}
|
|
||||||
})
|
|
||||||
componentList.push(canvasPositionList.value)
|
|
||||||
// 传入画布数据
|
|
||||||
line.lineArr.forEach(lineItem => {
|
|
||||||
componentList.forEach((component: typeof canvasPositionList.value) => {
|
|
||||||
// 排除自身
|
|
||||||
if (selectId.value[0] === component.id) return
|
|
||||||
const componentW = component.attr.w
|
|
||||||
const componentH = component.attr.h
|
|
||||||
|
|
||||||
// 距离左侧
|
|
||||||
const componentLeftX = component.attr.x
|
|
||||||
const componentHalfX = componentLeftX + componentW / 2
|
|
||||||
const componentRightX = componentLeftX + componentW
|
|
||||||
const componentX = [componentLeftX, componentHalfX, componentRightX]
|
|
||||||
|
|
||||||
// 距离顶部
|
|
||||||
const componentTopY = component.attr.y
|
|
||||||
const componentHalfY = componentTopY + componentH / 2
|
|
||||||
const componentBottomY = componentTopY + componentH
|
|
||||||
const componentY = [componentTopY, componentHalfY, componentBottomY]
|
|
||||||
|
|
||||||
// 横线对比的是 Y
|
|
||||||
if (lineItem.includes('rowt')) {
|
|
||||||
// 顶部
|
|
||||||
if (isSorption(selectTopY, componentTopY)) {
|
|
||||||
line.select.set(lineItem, { y: componentTopY })
|
|
||||||
setComponentPosition(selectTarget.value, selectLeftX, componentTopY)
|
|
||||||
}
|
|
||||||
if (isSorption(selectTopY, componentHalfY)) {
|
|
||||||
line.select.set(lineItem, { y: componentHalfY })
|
|
||||||
setComponentPosition(selectTarget.value, selectLeftX, componentHalfY)
|
|
||||||
}
|
|
||||||
if (isSorption(selectTopY, componentBottomY)) {
|
|
||||||
line.select.set(lineItem, { y: componentBottomY })
|
|
||||||
setComponentPosition(selectTarget.value, selectLeftX, componentBottomY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lineItem.includes('rowc')) {
|
|
||||||
// 顶部
|
|
||||||
if (isSorption(selectHalfY, componentTopY)) {
|
|
||||||
line.select.set(lineItem, { y: componentTopY })
|
|
||||||
setComponentPosition(selectTarget.value, selectLeftX, componentTopY - selectH / 2)
|
|
||||||
}
|
|
||||||
if (isSorption(selectHalfY, componentHalfY)) {
|
|
||||||
line.select.set(lineItem, { y: componentHalfY })
|
|
||||||
setComponentPosition(selectTarget.value, selectLeftX, componentHalfY - selectH / 2)
|
|
||||||
}
|
|
||||||
if (isSorption(selectHalfY, componentBottomY)) {
|
|
||||||
line.select.set(lineItem, { y: componentBottomY })
|
|
||||||
setComponentPosition(selectTarget.value, selectLeftX, componentBottomY - selectH / 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lineItem.includes('rowb')) {
|
|
||||||
// 顶部
|
|
||||||
if (isSorption(selectBottomY, componentTopY)) {
|
|
||||||
line.select.set(lineItem, { y: componentTopY })
|
|
||||||
setComponentPosition(selectTarget.value, selectLeftX, componentTopY - selectH)
|
|
||||||
}
|
|
||||||
if (isSorption(selectBottomY, componentHalfY)) {
|
|
||||||
line.select.set(lineItem, { y: componentHalfY })
|
|
||||||
setComponentPosition(selectTarget.value, selectLeftX, componentHalfY - selectH)
|
|
||||||
}
|
|
||||||
if (isSorption(selectBottomY, componentBottomY)) {
|
|
||||||
line.select.set(lineItem, { y: componentBottomY })
|
|
||||||
setComponentPosition(selectTarget.value, selectLeftX, componentBottomY - selectH)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 纵线对比的是 X
|
|
||||||
if (lineItem.includes('coll')) {
|
|
||||||
if (isSorption(selectLeftX, componentLeftX)) {
|
|
||||||
line.select.set(lineItem, { x: componentLeftX })
|
|
||||||
setComponentPosition(selectTarget.value, componentLeftX, selectTopY)
|
|
||||||
}
|
|
||||||
if (isSorption(selectLeftX, componentHalfX)) {
|
|
||||||
line.select.set(lineItem, { x: componentHalfX })
|
|
||||||
setComponentPosition(selectTarget.value, componentHalfX, selectTopY)
|
|
||||||
}
|
|
||||||
if (isSorption(selectLeftX, componentRightX)) {
|
|
||||||
line.select.set(lineItem, { x: componentRightX })
|
|
||||||
setComponentPosition(selectTarget.value, componentRightX, selectTopY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lineItem.includes('colc')) {
|
|
||||||
if (isSorption(selectHalfX, componentLeftX)) {
|
|
||||||
line.select.set(lineItem, { x: componentLeftX })
|
|
||||||
setComponentPosition(selectTarget.value, componentLeftX - selectW / 2, selectTopY)
|
|
||||||
}
|
|
||||||
if (isSorption(selectHalfX, componentHalfX)) {
|
|
||||||
line.select.set(lineItem, { x: componentHalfX })
|
|
||||||
setComponentPosition(selectTarget.value, componentHalfX - selectW / 2, selectTopY)
|
|
||||||
}
|
|
||||||
if (isSorption(selectHalfX, componentRightX)) {
|
|
||||||
line.select.set(lineItem, { x: componentRightX })
|
|
||||||
setComponentPosition(selectTarget.value, componentRightX - selectW / 2, selectTopY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lineItem.includes('colr')) {
|
|
||||||
if (isSorption(selectRightX, componentLeftX)) {
|
|
||||||
line.select.set(lineItem, { x: componentLeftX })
|
|
||||||
setComponentPosition(selectTarget.value, componentLeftX - selectW, selectTopY)
|
|
||||||
}
|
|
||||||
if (isSorption(selectRightX, componentHalfX)) {
|
|
||||||
line.select.set(lineItem, { x: componentHalfX })
|
|
||||||
setComponentPosition(selectTarget.value, componentHalfX - selectW, selectTopY)
|
|
||||||
}
|
|
||||||
if (isSorption(selectRightX, componentRightX)) {
|
|
||||||
line.select.set(lineItem, { x: componentRightX })
|
|
||||||
setComponentPosition(selectTarget.value, componentRightX - selectW, selectTopY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
}
|
|
||||||
}, 200),
|
|
||||||
{
|
|
||||||
deep: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// * 取消对齐线
|
|
||||||
watch(
|
|
||||||
() => isComputedLine.value,
|
|
||||||
(val: boolean) => {
|
|
||||||
if (!val) {
|
|
||||||
line.select.clear()
|
|
||||||
line.sorptioned.y = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@include go(edit-align-line) {
|
|
||||||
.line {
|
|
||||||
z-index: 1;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
display: none;
|
|
||||||
border-width: 1px;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: v-bind('themeColor');
|
|
||||||
opacity: 0.7;
|
|
||||||
&.visible {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.row {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.col {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -3,10 +3,6 @@
|
|||||||
<slot></slot>
|
<slot></slot>
|
||||||
<!-- 水印 -->
|
<!-- 水印 -->
|
||||||
<edit-watermark></edit-watermark>
|
<edit-watermark></edit-watermark>
|
||||||
<!-- 拖拽时的辅助线 -->
|
|
||||||
<edit-align-line></edit-align-line>
|
|
||||||
<!-- 框选时的样式框 -->
|
|
||||||
<edit-select></edit-select>
|
|
||||||
<!-- 拖拽时的遮罩 -->
|
<!-- 拖拽时的遮罩 -->
|
||||||
<div class="go-edit-range-model" :style="rangeModelStyle"></div>
|
<div class="go-edit-range-model" :style="rangeModelStyle"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -18,9 +14,7 @@ import { useSizeStyle } from '../../hooks/useStyle.hook'
|
|||||||
import { canvasModelIndex } from '@/settings/designSetting'
|
import { canvasModelIndex } from '@/settings/designSetting'
|
||||||
import { mousedownBoxSelect } from '../../hooks/useDrag.hook'
|
import { mousedownBoxSelect } from '../../hooks/useDrag.hook'
|
||||||
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
|
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
|
||||||
import { EditAlignLine } from '../EditAlignLine'
|
|
||||||
import { EditWatermark } from '../EditWatermark'
|
import { EditWatermark } from '../EditWatermark'
|
||||||
import { EditSelect } from '../EditSelect'
|
|
||||||
|
|
||||||
const chartEditStore = useChartEditStore()
|
const chartEditStore = useChartEditStore()
|
||||||
|
|
||||||
@ -47,7 +41,7 @@ const rangeModelStyle = computed(() => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
transform-origin: left top;
|
transform-origin: left top;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
@include fetch-border-color('hover-border-color');
|
@include fetch-border-color('hover-border-color');
|
||||||
@include fetch-bg-color('background-color2');
|
@include fetch-bg-color('background-color2');
|
||||||
@include go(edit-range-model) {
|
@include go(edit-range-model) {
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
import EditSelect from './index.vue'
|
|
||||||
|
|
||||||
export { EditSelect }
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="go-edit-select" v-if="isSelect" :style="positionStyle">
|
|
||||||
<div class="select-background"></div>
|
|
||||||
<div class="select-border"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, toRefs, watch, computed } from 'vue'
|
|
||||||
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
|
|
||||||
import { useDesignStore } from '@/store/modules/designStore/designStore'
|
|
||||||
import { useSizeStyle, useComponentStyle } from '../../hooks/useStyle.hook'
|
|
||||||
import { selectBoxIndex } from '@/settings/designSetting'
|
|
||||||
|
|
||||||
// 全局颜色
|
|
||||||
const designStore = useDesignStore()
|
|
||||||
const chartEditStore = useChartEditStore()
|
|
||||||
const { isSelect, scale } = toRefs(chartEditStore.getEditCanvas)
|
|
||||||
|
|
||||||
const themeColor = computed(() => {
|
|
||||||
return designStore.getAppTheme
|
|
||||||
})
|
|
||||||
|
|
||||||
// 位置
|
|
||||||
const positionStyle = ref()
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => chartEditStore.getMousePosition,
|
|
||||||
positionInfo => {
|
|
||||||
if (!isSelect.value) return
|
|
||||||
|
|
||||||
// 这里的 x,y 是已经计算过的相对位移值
|
|
||||||
const { startX, startY, x, y } = positionInfo
|
|
||||||
|
|
||||||
const attr = {
|
|
||||||
zIndex: selectBoxIndex,
|
|
||||||
// left
|
|
||||||
x: 0,
|
|
||||||
// top
|
|
||||||
y: 0,
|
|
||||||
// 宽
|
|
||||||
w: 0,
|
|
||||||
// 高
|
|
||||||
h: 0,
|
|
||||||
// 偏移
|
|
||||||
offsetX: 0,
|
|
||||||
offsetY: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理位置
|
|
||||||
if (x > startX && y > startY) {
|
|
||||||
// 右下方向
|
|
||||||
attr.x = startX
|
|
||||||
attr.y = startY
|
|
||||||
attr.w = Math.round((x - startX) / scale.value)
|
|
||||||
attr.h = Math.round((y - startY) / scale.value)
|
|
||||||
} else if (x > startX && y < startY) {
|
|
||||||
// 右上方向
|
|
||||||
attr.x = startX
|
|
||||||
attr.w = Math.round((x - startX) / scale.value)
|
|
||||||
attr.h = Math.round((startY - y) / scale.value)
|
|
||||||
attr.y = startY - attr.h
|
|
||||||
} else if (x < startX && y > startY) {
|
|
||||||
// 左下方向
|
|
||||||
attr.y = startY
|
|
||||||
attr.w = Math.round((startX - x) / scale.value)
|
|
||||||
attr.h = Math.round((y - startY) / scale.value)
|
|
||||||
attr.x = startX - attr.w
|
|
||||||
} else {
|
|
||||||
// 左上方向
|
|
||||||
attr.w = Math.round((startX - x) / scale.value)
|
|
||||||
attr.h = Math.round((startY - y) / scale.value)
|
|
||||||
attr.x = startX - attr.w
|
|
||||||
attr.y = startY - attr.h
|
|
||||||
}
|
|
||||||
|
|
||||||
positionStyle.value = {
|
|
||||||
...useComponentStyle(attr, selectBoxIndex),
|
|
||||||
...useSizeStyle(attr)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deep: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@include go('edit-select') {
|
|
||||||
position: absolute;
|
|
||||||
.select-border,
|
|
||||||
.select-background {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.select-border {
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
opacity: 1;
|
|
||||||
border-width: 1px;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: v-bind('themeColor');
|
|
||||||
}
|
|
||||||
.select-background {
|
|
||||||
top: 2px;
|
|
||||||
left: 2px;
|
|
||||||
opacity: 0.08;
|
|
||||||
background-color: v-bind('themeColor');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -5,7 +5,10 @@ import { ConfigType } from '@/packages/index.d'
|
|||||||
import { CreateComponentType, CreateComponentGroupType, PickCreateComponentType } from '@/packages/index.d'
|
import { CreateComponentType, CreateComponentGroupType, PickCreateComponentType } from '@/packages/index.d'
|
||||||
import { useContextMenu } from '@/views/chart/hooks/useContextMenu.hook'
|
import { useContextMenu } from '@/views/chart/hooks/useContextMenu.hook'
|
||||||
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
|
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
|
||||||
|
import { useDesignStore } from '@/store/modules/designStore/designStore'
|
||||||
|
import { useSettingStore } from '@/store/modules/settingStore/settingStore'
|
||||||
import { EditCanvasTypeEnum } from '@/store/modules/chartEditStore/chartEditStore.d'
|
import { EditCanvasTypeEnum } from '@/store/modules/chartEditStore/chartEditStore.d'
|
||||||
|
import { selectBoxIndex } from '@/settings/designSetting'
|
||||||
import { loadingStart, loadingFinish, loadingError, setComponentPosition, JSONParse } from '@/utils'
|
import { loadingStart, loadingFinish, loadingError, setComponentPosition, JSONParse } from '@/utils'
|
||||||
import { throttle, cloneDeep } from 'lodash'
|
import { throttle, cloneDeep } from 'lodash'
|
||||||
|
|
||||||
@ -73,6 +76,23 @@ export const mousedownBoxSelect = (e: MouseEvent, item?: CreateComponentType | C
|
|||||||
|
|
||||||
mousedownHandleUnStop(e)
|
mousedownHandleUnStop(e)
|
||||||
|
|
||||||
|
// 框选容器(.go-edit-range),框选框会 append 到这里
|
||||||
|
const container = e.currentTarget as HTMLElement
|
||||||
|
// 主题色(每次框选时读取,跟随主题切换)
|
||||||
|
const themeColor = useDesignStore().getAppTheme
|
||||||
|
|
||||||
|
// 创建框选框 DOM(鼠标抬起时销毁,不走 store 响应式,避免卡顿)
|
||||||
|
// 将主题色(hex)转为带透明度的背景色
|
||||||
|
const hexToRgba = (hex: string, alpha: number) => {
|
||||||
|
const r = parseInt(hex.slice(1, 3), 16)
|
||||||
|
const g = parseInt(hex.slice(3, 5), 16)
|
||||||
|
const b = parseInt(hex.slice(5, 7), 16)
|
||||||
|
return `rgba(${r},${g},${b},${alpha})`
|
||||||
|
}
|
||||||
|
const selectEl = document.createElement('div')
|
||||||
|
selectEl.style.cssText = `position:absolute;left:0;top:0;z-index:${selectBoxIndex};pointer-events:none;border:1px dashed ${themeColor};background:${hexToRgba(themeColor, 0.06)};`
|
||||||
|
container.appendChild(selectEl)
|
||||||
|
|
||||||
// 记录点击初始位置
|
// 记录点击初始位置
|
||||||
const startOffsetX = e.offsetX
|
const startOffsetX = e.offsetX
|
||||||
const startOffsetY = e.offsetY
|
const startOffsetY = e.offsetY
|
||||||
@ -82,8 +102,6 @@ export const mousedownBoxSelect = (e: MouseEvent, item?: CreateComponentType | C
|
|||||||
// 记录缩放
|
// 记录缩放
|
||||||
const scale = chartEditStore.getEditCanvas.scale
|
const scale = chartEditStore.getEditCanvas.scale
|
||||||
|
|
||||||
chartEditStore.setMousePosition(undefined, undefined, startOffsetX, startOffsetY)
|
|
||||||
|
|
||||||
// 移动框选
|
// 移动框选
|
||||||
const mousemove = throttle((moveEvent: MouseEvent) => {
|
const mousemove = throttle((moveEvent: MouseEvent) => {
|
||||||
// 取消当前选中
|
// 取消当前选中
|
||||||
@ -93,7 +111,6 @@ export const mousedownBoxSelect = (e: MouseEvent, item?: CreateComponentType | C
|
|||||||
// 这里先把相对值算好,不然组件无法获取 startScreenX 和 startScreenY 的值
|
// 这里先把相对值算好,不然组件无法获取 startScreenX 和 startScreenY 的值
|
||||||
const currX = startOffsetX + moveEvent.screenX - startScreenX
|
const currX = startOffsetX + moveEvent.screenX - startScreenX
|
||||||
const currY = startOffsetY + moveEvent.screenY - startScreenY
|
const currY = startOffsetY + moveEvent.screenY - startScreenY
|
||||||
chartEditStore.setMousePosition(currX, currY)
|
|
||||||
|
|
||||||
// 计算框选的左上角和右下角
|
// 计算框选的左上角和右下角
|
||||||
const selectAttr = {
|
const selectAttr = {
|
||||||
@ -130,11 +147,20 @@ export const mousedownBoxSelect = (e: MouseEvent, item?: CreateComponentType | C
|
|||||||
selectAttr.y2 = startOffsetY
|
selectAttr.y2 = startOffsetY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 直接操作 DOM 绘制框选框,绕开 store 响应式
|
||||||
|
const left = Math.min(selectAttr.x1, selectAttr.x2)
|
||||||
|
const top = Math.min(selectAttr.y1, selectAttr.y2)
|
||||||
|
const width = Math.abs(selectAttr.x2 - selectAttr.x1)
|
||||||
|
const height = Math.abs(selectAttr.y2 - selectAttr.y1)
|
||||||
|
selectEl.style.left = `${left}px`
|
||||||
|
selectEl.style.top = `${top}px`
|
||||||
|
selectEl.style.width = `${width}px`
|
||||||
|
selectEl.style.height = `${height}px`
|
||||||
|
|
||||||
// 遍历组件
|
// 遍历组件
|
||||||
chartEditStore.getComponentList.forEach(item => {
|
chartEditStore.getComponentList.forEach(item => {
|
||||||
if (!chartEditStore.getTargetChart.selectId.includes(item.id)) {
|
if (!chartEditStore.getTargetChart.selectId.includes(item.id)) {
|
||||||
// 处理左上角
|
// 处理左上角
|
||||||
let isSelect = false
|
|
||||||
const { x, y, w, h } = item.attr
|
const { x, y, w, h } = item.attr
|
||||||
const targetAttr = {
|
const targetAttr = {
|
||||||
// 左上角
|
// 左上角
|
||||||
@ -144,16 +170,15 @@ export const mousedownBoxSelect = (e: MouseEvent, item?: CreateComponentType | C
|
|||||||
x2: x + w,
|
x2: x + w,
|
||||||
y2: y + h
|
y2: y + h
|
||||||
}
|
}
|
||||||
// 全包含则选中
|
// 部分相交即选中
|
||||||
if (
|
if (
|
||||||
targetAttr.x1 - selectAttr.x1 >= 0 &&
|
targetAttr.x1 < selectAttr.x2 &&
|
||||||
targetAttr.y1 - selectAttr.y1 >= 0 &&
|
targetAttr.x2 > selectAttr.x1 &&
|
||||||
targetAttr.x2 - selectAttr.x2 <= 0 &&
|
targetAttr.y1 < selectAttr.y2 &&
|
||||||
targetAttr.y2 - selectAttr.y2 <= 0 &&
|
targetAttr.y2 > selectAttr.y1 &&
|
||||||
!item.status.lock &&
|
!item.status.lock &&
|
||||||
!item.status.hide
|
!item.status.hide
|
||||||
) {
|
) {
|
||||||
isSelect = true
|
|
||||||
chartEditStore.setTargetSelectChart(item.id, true)
|
chartEditStore.setTargetSelectChart(item.id, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,6 +191,8 @@ export const mousedownBoxSelect = (e: MouseEvent, item?: CreateComponentType | C
|
|||||||
mousemove.cancel()
|
mousemove.cancel()
|
||||||
chartEditStore.setEditCanvas(EditCanvasTypeEnum.IS_SELECT, false)
|
chartEditStore.setEditCanvas(EditCanvasTypeEnum.IS_SELECT, false)
|
||||||
chartEditStore.setMousePosition(0, 0, 0, 0)
|
chartEditStore.setMousePosition(0, 0, 0, 0)
|
||||||
|
// 销毁框选框 DOM
|
||||||
|
selectEl.remove()
|
||||||
document.removeEventListener('mousemove', mousemove)
|
document.removeEventListener('mousemove', mousemove)
|
||||||
document.removeEventListener('mouseup', mouseup)
|
document.removeEventListener('mouseup', mouseup)
|
||||||
}
|
}
|
||||||
@ -173,6 +200,128 @@ export const mousedownBoxSelect = (e: MouseEvent, item?: CreateComponentType | C
|
|||||||
document.addEventListener('mouseup', mouseup)
|
document.addEventListener('mouseup', mouseup)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 对齐线 DOM 操作(纯命令式,不走 Vue 响应式)----
|
||||||
|
|
||||||
|
let _alignEls: HTMLElement[] = []
|
||||||
|
|
||||||
|
const _clearAlignLines = () => {
|
||||||
|
_alignEls.forEach(el => el.remove())
|
||||||
|
_alignEls = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const _drawAlignLine = (
|
||||||
|
container: HTMLElement,
|
||||||
|
isRow: boolean,
|
||||||
|
fixedCoord: number,
|
||||||
|
spanStart: number,
|
||||||
|
spanEnd: number,
|
||||||
|
label: string,
|
||||||
|
color: string
|
||||||
|
) => {
|
||||||
|
if (spanStart >= spanEnd) return
|
||||||
|
const el = document.createElement('div')
|
||||||
|
if (isRow) {
|
||||||
|
// 横线:固定 top,left~width 表示水平范围
|
||||||
|
el.style.cssText = `position:absolute;pointer-events:none;z-index:9999;background:${color};left:${spanStart}px;top:${fixedCoord}px;width:${spanEnd - spanStart}px;height:1px;`
|
||||||
|
} else {
|
||||||
|
// 竖线:固定 left,top~height 表示垂直范围
|
||||||
|
el.style.cssText = `position:absolute;pointer-events:none;z-index:9999;background:${color};left:${fixedCoord}px;top:${spanStart}px;width:1px;height:${spanEnd - spanStart}px;`
|
||||||
|
}
|
||||||
|
const tag = document.createElement('span')
|
||||||
|
tag.textContent = label
|
||||||
|
tag.style.cssText = `position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);background:${color};color:#fff;font-size:11px;line-height:1;padding:2px 5px;border-radius:2px;white-space:nowrap;`
|
||||||
|
el.appendChild(tag)
|
||||||
|
container.appendChild(el)
|
||||||
|
_alignEls.push(el)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlignRef = { id: string; attr: { x: number; y: number; w: number; h: number } }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 吸附对齐:检测 sel 与所有 refs 的边界对齐,返回吸附后坐标并绘制对齐线。
|
||||||
|
* 对齐线只绘制两个对象之间的覆盖范围,中间显示吸附前偏差 px。
|
||||||
|
*/
|
||||||
|
const _runAlignSnap = (
|
||||||
|
container: HTMLElement,
|
||||||
|
selectId: string,
|
||||||
|
sel: { x: number; y: number; w: number; h: number },
|
||||||
|
refs: AlignRef[],
|
||||||
|
color: string,
|
||||||
|
minDist: number
|
||||||
|
): { x: number; y: number } => {
|
||||||
|
_clearAlignLines()
|
||||||
|
|
||||||
|
const { x: sx, y: sy, w: sw, h: sh } = sel
|
||||||
|
|
||||||
|
type Hit = { coord: number; newOrig: number; delta: number; ref: AlignRef }
|
||||||
|
let bestX: Hit | null = null
|
||||||
|
let bestY: Hit | null = null
|
||||||
|
|
||||||
|
for (const ref of refs) {
|
||||||
|
if (ref.id === selectId) continue
|
||||||
|
const { x: cx, y: cy, w: cw, h: ch } = ref.attr
|
||||||
|
const cR = cx + cw, cB = cy + ch
|
||||||
|
const cCx = cx + cw / 2, cCy = cy + ch / 2
|
||||||
|
|
||||||
|
// X 对齐检测 → 竖线
|
||||||
|
// [sel边, ref边, 吸附后的新 sx]
|
||||||
|
const xChecks: [number, number, number][] = [
|
||||||
|
[sx, cx, cx], // 左-左
|
||||||
|
[sx, cCx, cCx], // 左-中
|
||||||
|
[sx, cR, cR], // 左-右
|
||||||
|
[sx+sw/2, cx, cx-sw/2], // 中-左
|
||||||
|
[sx+sw/2, cCx, cCx-sw/2], // 中-中
|
||||||
|
[sx+sw/2, cR, cR-sw/2], // 中-右
|
||||||
|
[sx+sw, cx, cx-sw], // 右-左
|
||||||
|
[sx+sw, cCx, cCx-sw], // 右-中
|
||||||
|
[sx+sw, cR, cR-sw], // 右-右
|
||||||
|
]
|
||||||
|
for (const [a, b, newSx] of xChecks) {
|
||||||
|
const delta = Math.abs(a - b)
|
||||||
|
if (delta <= minDist && (!bestX || delta < bestX.delta)) {
|
||||||
|
bestX = { coord: b, newOrig: newSx, delta, ref }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Y 对齐检测 → 横线
|
||||||
|
const yChecks: [number, number, number][] = [
|
||||||
|
[sy, cy, cy],
|
||||||
|
[sy, cCy, cCy],
|
||||||
|
[sy, cB, cB],
|
||||||
|
[sy+sh/2, cy, cy-sh/2],
|
||||||
|
[sy+sh/2, cCy, cCy-sh/2],
|
||||||
|
[sy+sh/2, cB, cB-sh/2],
|
||||||
|
[sy+sh, cy, cy-sh],
|
||||||
|
[sy+sh, cCy, cCy-sh],
|
||||||
|
[sy+sh, cB, cB-sh],
|
||||||
|
]
|
||||||
|
for (const [a, b, newSy] of yChecks) {
|
||||||
|
const delta = Math.abs(a - b)
|
||||||
|
if (delta <= minDist && (!bestY || delta < bestY.delta)) {
|
||||||
|
bestY = { coord: b, newOrig: newSy, delta, ref }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalX = bestX ? Math.round(bestX.newOrig) : sx
|
||||||
|
const finalY = bestY ? Math.round(bestY.newOrig) : sy
|
||||||
|
|
||||||
|
if (bestX) {
|
||||||
|
const { y: ry, h: rh } = bestX.ref.attr
|
||||||
|
const spanStart = Math.min(finalY, ry)
|
||||||
|
const spanEnd = Math.max(finalY + sh, ry + rh)
|
||||||
|
_drawAlignLine(container, false, bestX.coord, spanStart, spanEnd, `${Math.round(bestX.delta)}px`, color)
|
||||||
|
}
|
||||||
|
if (bestY) {
|
||||||
|
const { x: rx, w: rw } = bestY.ref.attr
|
||||||
|
const spanStart = Math.min(finalX, rx)
|
||||||
|
const spanEnd = Math.max(finalX + sw, rx + rw)
|
||||||
|
_drawAlignLine(container, true, bestY.coord, spanStart, spanEnd, `${Math.round(bestY.delta)}px`, color)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { x: finalX, y: finalY }
|
||||||
|
}
|
||||||
|
|
||||||
// * 鼠标事件
|
// * 鼠标事件
|
||||||
export const useMouseHandle = () => {
|
export const useMouseHandle = () => {
|
||||||
// * Click 事件, 松开鼠标触发
|
// * Click 事件, 松开鼠标触发
|
||||||
@ -212,8 +361,9 @@ export const useMouseHandle = () => {
|
|||||||
if (e.buttons === MouseEventButton.RIGHT) return
|
if (e.buttons === MouseEventButton.RIGHT) return
|
||||||
|
|
||||||
const scale = chartEditStore.getEditCanvas.scale
|
const scale = chartEditStore.getEditCanvas.scale
|
||||||
const canvasWidth = chartEditStore.getEditCanvasConfig.width
|
// 读取对齐配置(在 mousedown 时快照,避免拖拽过程中反复读 store)
|
||||||
const canvasHeight = chartEditStore.getEditCanvasConfig.height
|
const themeColor = useDesignStore().getAppTheme
|
||||||
|
const minDist = useSettingStore().getChartAlignRange
|
||||||
|
|
||||||
// 记录图表初始位置和大小
|
// 记录图表初始位置和大小
|
||||||
const targetMap = new Map()
|
const targetMap = new Map()
|
||||||
@ -233,57 +383,76 @@ export const useMouseHandle = () => {
|
|||||||
let prevComponentInstance: Array<CreateComponentType | CreateComponentGroupType> = []
|
let prevComponentInstance: Array<CreateComponentType | CreateComponentGroupType> = []
|
||||||
chartEditStore.getTargetChart.selectId.forEach(id => {
|
chartEditStore.getTargetChart.selectId.forEach(id => {
|
||||||
if (!targetMap.has(id)) return
|
if (!targetMap.has(id)) return
|
||||||
|
|
||||||
const index = chartEditStore.fetchTargetIndex(id)
|
const index = chartEditStore.fetchTargetIndex(id)
|
||||||
// 拿到初始位置数据
|
|
||||||
prevComponentInstance.push(cloneDeep(chartEditStore.getComponentList[index]))
|
prevComponentInstance.push(cloneDeep(chartEditStore.getComponentList[index]))
|
||||||
})
|
})
|
||||||
|
|
||||||
// 记录初始位置
|
// 记录初始位置
|
||||||
chartEditStore.setMousePosition(undefined, undefined, startX, startY)
|
chartEditStore.setMousePosition(undefined, undefined, startX, startY)
|
||||||
|
|
||||||
|
// 对齐线直接绘制到画布容器
|
||||||
|
const alignContainer = document.querySelector('.go-edit-range') as HTMLElement | null
|
||||||
|
|
||||||
// 移动-计算偏移量
|
// 移动-计算偏移量
|
||||||
const mousemove = throttle((moveEvent: MouseEvent) => {
|
const mousemove = throttle((moveEvent: MouseEvent) => {
|
||||||
chartEditStore.setEditCanvas(EditCanvasTypeEnum.IS_DRAG, true)
|
chartEditStore.setEditCanvas(EditCanvasTypeEnum.IS_DRAG, true)
|
||||||
chartEditStore.setMousePosition(moveEvent.screenX, moveEvent.screenY)
|
chartEditStore.setMousePosition(moveEvent.screenX, moveEvent.screenY)
|
||||||
|
|
||||||
// 当前偏移量,处理 scale 比例问题
|
const offsetX = (moveEvent.screenX - startX) / scale
|
||||||
let offsetX = (moveEvent.screenX - startX) / scale
|
const offsetY = (moveEvent.screenY - startY) / scale
|
||||||
let offsetY = (moveEvent.screenY - startY) / scale
|
|
||||||
|
|
||||||
chartEditStore.getTargetChart.selectId.forEach(id => {
|
const currentSelectIds = chartEditStore.getTargetChart.selectId
|
||||||
|
|
||||||
|
if (currentSelectIds.length === 1) {
|
||||||
|
// 单选:做对齐吸附
|
||||||
|
const id = currentSelectIds[0]
|
||||||
if (!targetMap.has(id)) return
|
if (!targetMap.has(id)) return
|
||||||
|
|
||||||
const index = chartEditStore.fetchTargetIndex(id)
|
|
||||||
// 拿到初始位置数据
|
|
||||||
const { x, y, w, h } = targetMap.get(id)
|
const { x, y, w, h } = targetMap.get(id)
|
||||||
const componentInstance = chartEditStore.getComponentList[index]
|
|
||||||
|
|
||||||
let currX = Math.round(x + offsetX)
|
let currX = Math.round(x + offsetX)
|
||||||
let currY = Math.round(y + offsetY)
|
let currY = Math.round(y + offsetY)
|
||||||
|
|
||||||
// 要预留的距离
|
if (alignContainer) {
|
||||||
const distance = 50
|
// 构造参照列表(所有其他组件 + 画布)
|
||||||
|
// 直接引用 attr,避免每帧构造新对象
|
||||||
// 基于左上角位置检测
|
const refs: AlignRef[] = (chartEditStore.getComponentList as Array<CreateComponentType | CreateComponentGroupType>).map(c => ({
|
||||||
currX = currX < -w + distance ? -w + distance : currX
|
id: c.id,
|
||||||
currY = currY < -h + distance ? -h + distance : currY
|
attr: c.attr as { x: number; y: number; w: number; h: number }
|
||||||
|
}))
|
||||||
// 基于右下角位置检测
|
refs.push({
|
||||||
currX = currX > canvasWidth - distance ? canvasWidth - distance : currX
|
id: '__canvas__',
|
||||||
currY = currY > canvasHeight - distance ? canvasHeight - distance : currY
|
attr: { x: 0, y: 0, w: chartEditStore.getEditCanvasConfig.width, h: chartEditStore.getEditCanvasConfig.height }
|
||||||
if (componentInstance) {
|
|
||||||
componentInstance.attr = Object.assign(componentInstance.attr, {
|
|
||||||
x: currX,
|
|
||||||
y: currY
|
|
||||||
})
|
})
|
||||||
|
const snapped = _runAlignSnap(alignContainer, id, { x: currX, y: currY, w, h }, refs, themeColor, minDist)
|
||||||
|
currX = snapped.x
|
||||||
|
currY = snapped.y
|
||||||
}
|
}
|
||||||
})
|
|
||||||
return
|
const index = chartEditStore.fetchTargetIndex(id)
|
||||||
|
const componentInstance = chartEditStore.getComponentList[index]
|
||||||
|
if (componentInstance) {
|
||||||
|
componentInstance.attr = Object.assign(componentInstance.attr, { x: currX, y: currY })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 多选:无对齐,直接移动
|
||||||
|
_clearAlignLines()
|
||||||
|
currentSelectIds.forEach(id => {
|
||||||
|
if (!targetMap.has(id)) return
|
||||||
|
const index = chartEditStore.fetchTargetIndex(id)
|
||||||
|
const { x, y } = targetMap.get(id)
|
||||||
|
const componentInstance = chartEditStore.getComponentList[index]
|
||||||
|
if (componentInstance) {
|
||||||
|
componentInstance.attr = Object.assign(componentInstance.attr, {
|
||||||
|
x: Math.round(x + offsetX),
|
||||||
|
y: Math.round(y + offsetY)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}, 20)
|
}, 20)
|
||||||
|
|
||||||
const mouseup = () => {
|
const mouseup = () => {
|
||||||
try {
|
try {
|
||||||
|
_clearAlignLines()
|
||||||
chartEditStore.setMousePosition(0, 0, 0, 0)
|
chartEditStore.setMousePosition(0, 0, 0, 0)
|
||||||
chartEditStore.setEditCanvas(EditCanvasTypeEnum.IS_DRAG, false)
|
chartEditStore.setEditCanvas(EditCanvasTypeEnum.IS_DRAG, false)
|
||||||
// 加入历史栈
|
// 加入历史栈
|
||||||
|
|||||||
@ -205,7 +205,6 @@ onMounted(() => {
|
|||||||
@include background-image('background-point');
|
@include background-image('background-point');
|
||||||
|
|
||||||
@include goId('chart-edit-content') {
|
@include goId('chart-edit-content') {
|
||||||
overflow: hidden;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@extend .go-transition;
|
@extend .go-transition;
|
||||||
@include fetch-theme('box-shadow');
|
@include fetch-theme('box-shadow');
|
||||||
|
|||||||
@ -51,6 +51,7 @@ export const getEditCanvasConfigStyle = (canvas: EditCanvasConfigType) => {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
position: 'relative' as const,
|
position: 'relative' as const,
|
||||||
|
overflow: 'hidden' as const,
|
||||||
width: canvas.width ? `${canvas.width || 100}px` : '100%',
|
width: canvas.width ? `${canvas.width || 100}px` : '100%',
|
||||||
height: canvas.height ? `${canvas.height}px` : '100%',
|
height: canvas.height ? `${canvas.height}px` : '100%',
|
||||||
...computedBackground
|
...computedBackground
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user