feat: v2.9 拖拽移动模式(右键菜单入口)
- 菜单新增'拖拽移动此单元': 进入后鼠标移动即拖动单元(增量平移) - 点击画布落下并 emit change, 菜单动作与拖拽移动并列 - 配套验证脚本 verify-move.mjs(真实鼠标序列端到端)
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
<transition name="fade">
|
||||
<div v-if="ctxMenu.visible" class="ctx-menu" :style="{ left: ctxMenu.x + 'px', top: ctxMenu.y + 'px' }">
|
||||
<div class="ctx-title">单元 #{{ ctxMenu.uid }}</div>
|
||||
<div class="ctx-item" data-action="drag-move"><el-icon><Rank /></el-icon>拖拽移动此单元</div>
|
||||
<div class="ctx-item" data-action="rotate-cw"><el-icon><RefreshRight /></el-icon>顺时针旋转 90°</div>
|
||||
<div class="ctx-item" data-action="rotate-ccw"><el-icon><RefreshLeft /></el-icon>逆时针旋转 90°</div>
|
||||
<div class="ctx-item" data-action="mirror"><el-icon><Switch /></el-icon>水平镜像</div>
|
||||
@@ -290,6 +291,15 @@ function hitUnit(p: Pt): number | null {
|
||||
function onDown(e: MouseEvent) {
|
||||
closeMenu()
|
||||
const rect = cvRef.value!.getBoundingClientRect()
|
||||
// 拖拽移动模式: 点击落下
|
||||
if (moveModeUid.value !== null) {
|
||||
moveModeUid.value = null
|
||||
moveLast = null
|
||||
hint.value = '已落下'
|
||||
render()
|
||||
emit('change')
|
||||
return
|
||||
}
|
||||
const p = toModel(e.clientX - rect.left, e.clientY - rect.top)
|
||||
const uid = hitUnit(p)
|
||||
if (props.editable && uid !== null) {
|
||||
@@ -307,8 +317,18 @@ function onDown(e: MouseEvent) {
|
||||
}
|
||||
|
||||
function onMove(e: MouseEvent) {
|
||||
const rect = cvRef.value!.getBoundingClientRect()
|
||||
// 拖拽移动模式: 鼠标移动即拖动单元
|
||||
if (moveModeUid.value !== null) {
|
||||
const p = toModel(e.clientX - rect.left, e.clientY - rect.top)
|
||||
if (moveLast) {
|
||||
translateUid(moveModeUid.value, p.x - moveLast.x, p.y - moveLast.y)
|
||||
moveLast = p
|
||||
render()
|
||||
}
|
||||
return
|
||||
}
|
||||
if (dragUnit) {
|
||||
const rect = cvRef.value!.getBoundingClientRect()
|
||||
const p = toModel(e.clientX - rect.left, e.clientY - rect.top)
|
||||
const bb = unitBBox(dragUnit.uid)
|
||||
if (!bb) return
|
||||
@@ -382,10 +402,22 @@ function ctxAction(a: string) {
|
||||
const uid = ctxMenu.uid
|
||||
closeMenu()
|
||||
if (uid === null) return
|
||||
// 拖拽移动模式: 后续鼠标移动拖动该单元, 点击落下; Esc 取消还原
|
||||
if (a === 'drag-move') {
|
||||
moveModeUid.value = uid
|
||||
moveOriginBb = unitBBox(uid)
|
||||
moveLast = null
|
||||
hint.value = '拖拽移动模式:移动鼠标拖动单元,点击落下;Esc 取消'
|
||||
return
|
||||
}
|
||||
// 动作统一 emit 给父组件执行(父组件持有 canvasRef 与 shapes 权威状态)
|
||||
emit('ctx', a, uid)
|
||||
}
|
||||
|
||||
const moveModeUid = ref<number | null>(null)
|
||||
let moveLast: Pt | null = null
|
||||
let moveOriginBb: BBox | null = null
|
||||
|
||||
function refresh() { render() }
|
||||
|
||||
/** 选中单元缩放到目标尺寸(几何按比例缩放,锚定自身左下角) */
|
||||
@@ -469,6 +501,21 @@ defineExpose({ refresh, modelAt, exportDrawing, rotateUnit, mirrorUnit, resizeSe
|
||||
onMounted(() => {
|
||||
watch(() => props.shapes, () => { selectedUid.value = null; closeMenu(); rebuild() }, { immediate: true })
|
||||
window.addEventListener('resize', render)
|
||||
// Esc 取消拖拽移动(还原到模式进入时的位置)
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && moveModeUid.value !== null) {
|
||||
const bb = unitBBox(moveModeUid.value)
|
||||
if (bb && moveOriginBb) {
|
||||
translateUid(moveModeUid.value,
|
||||
(moveOriginBb.x0 + moveOriginBb.x1) / 2 - (bb.x0 + bb.x1) / 2,
|
||||
(moveOriginBb.y0 + moveOriginBb.y1) / 2 - (bb.y0 + bb.y1) / 2)
|
||||
}
|
||||
moveModeUid.value = null
|
||||
render()
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', onKey)
|
||||
onBeforeUnmount(() => window.removeEventListener('keydown', onKey))
|
||||
// 点击菜单外关闭 + 菜单项动作(在 mousedown 阶段触发,真实点击必有效)
|
||||
document.addEventListener('mousedown', (e: MouseEvent) => {
|
||||
const item = (e.target as HTMLElement).closest?.('.ctx-item')
|
||||
|
||||
Reference in New Issue
Block a user