import type { BatchOp } from '@/api/mapEdit' /** * 对齐算法:纯前端计算每个选中对象的新坐标,再以 batch ops(patch)回写到反射 API。 * 仅支持有 (x, y) 坐标的对象(site / image / text / model 等 UIHelper 子类); * track 是端点连线,没有自己的坐标,alignment 不影响 track。 */ export interface AlignTarget { kind: string id: number x: number y: number width?: number height?: number } export type AlignMode = | 'left' | 'right' | 'top' | 'bottom' | 'centerH' | 'centerV' | 'center' | 'distributeH' | 'distributeV' | 'toFirst' | 'toLast' /** * 根据对齐模式计算 patch ops。返回值为 mapEdit.batch 直接消费的 BatchOp 数组。 * 输入要求至少 2 个对象(distribute 需要 ≥ 3 才有意义)。 */ export function buildAlignOps(targets: AlignTarget[], mode: AlignMode): BatchOp[] { if (targets.length < 2) return [] const ops: BatchOp[] = [] const xs = targets.map((t) => t.x) const ys = targets.map((t) => t.y) const minX = Math.min(...xs) const maxX = Math.max(...xs) const minY = Math.min(...ys) const maxY = Math.max(...ys) const avgX = xs.reduce((a, b) => a + b, 0) / xs.length const avgY = ys.reduce((a, b) => a + b, 0) / ys.length switch (mode) { case 'left': for (const t of targets) ops.push(patch(t, { x: minX })) break case 'right': for (const t of targets) ops.push(patch(t, { x: maxX })) break case 'top': // 工程坐标 y 向上为正:top 取 max for (const t of targets) ops.push(patch(t, { y: maxY })) break case 'bottom': for (const t of targets) ops.push(patch(t, { y: minY })) break case 'centerH': for (const t of targets) ops.push(patch(t, { x: avgX })) break case 'centerV': for (const t of targets) ops.push(patch(t, { y: avgY })) break case 'center': for (const t of targets) ops.push(patch(t, { x: avgX, y: avgY })) break case 'distributeH': { const sorted = [...targets].sort((a, b) => a.x - b.x) const step = (sorted[sorted.length - 1]!.x - sorted[0]!.x) / (sorted.length - 1) sorted.forEach((t, i) => { const x = sorted[0]!.x + step * i if (Math.abs(x - t.x) > 0.5) ops.push(patch(t, { x })) }) break } case 'distributeV': { const sorted = [...targets].sort((a, b) => a.y - b.y) const step = (sorted[sorted.length - 1]!.y - sorted[0]!.y) / (sorted.length - 1) sorted.forEach((t, i) => { const y = sorted[0]!.y + step * i if (Math.abs(y - t.y) > 0.5) ops.push(patch(t, { y })) }) break } case 'toFirst': { const ref = targets[0]! for (const t of targets.slice(1)) ops.push(patch(t, { x: ref.x, y: ref.y })) break } case 'toLast': { const ref = targets[targets.length - 1]! for (const t of targets.slice(0, -1)) ops.push(patch(t, { x: ref.x, y: ref.y })) break } } return ops } function patch(t: AlignTarget, data: Record): BatchOp { return { action: 'patch', kind: t.kind, id: t.id, data } }