feat: 迁入 MiGu.Server、平台前端与车辆列表 reflection 回退

从 Simple-FR 拆出 Platform.Server 并重命名为 MiGu.Server;frontends 源码与构建脚本迁入本仓库。地图监控在 projection/cars 失败或为空时回退 reflection 车辆列表;Simple 仓库已移除旧 Platform.Server。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zhaowei.huang
2026-05-29 18:16:34 +08:00
co-authored by Cursor
parent 804aa68ade
commit 42978930ca
280 changed files with 30046 additions and 8 deletions
@@ -0,0 +1,106 @@
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<string, unknown>): BatchOp {
return { action: 'patch', kind: t.kind, id: t.id, data }
}