feat(map-editor): 站点/路径视口样式实时编辑与画布快捷键联动
- reflection API 新增 viewport-style GET/PATCH 及 ARGB 无符号化工具 - 属性面板「类型默认」重构为选中对象的视口样式实时编辑(防抖 + 序号防竞态) - 画布 iframe 聚焦时经 SSE 转发撤销/重做/删除,移除冗余的坐标添加菜单 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -243,6 +243,12 @@ async function del<T>(path: string): Promise<T> {
|
||||
return data.data as T
|
||||
}
|
||||
|
||||
async function patchJson<T>(path: string, body: unknown): Promise<T> {
|
||||
const { data } = await http.patch<ReflectionEnvelope<T>>(`${BASE}${path}`, body)
|
||||
if (!data?.success) throw new Error(data?.message ?? `reflection PATCH ${path} failed`)
|
||||
return data.data as T
|
||||
}
|
||||
|
||||
export const reflectionApi = {
|
||||
listKinds: () => MOCK
|
||||
? Promise.resolve(mockReflectionKinds())
|
||||
@@ -500,6 +506,55 @@ export const reflectionApi = {
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
// 车型样式(WorkspaceCarStylesByType / WorkspaceAlarmColorScheme)
|
||||
// /viewport-style GET 站点/路径视口 Painter 样式
|
||||
// /viewport-style PATCH 部分更新并立即重绘画布
|
||||
|
||||
getViewportStyle: () => MOCK
|
||||
? Promise.resolve({
|
||||
site: {
|
||||
drawScale: 2.5,
|
||||
dotRadiusNormal: 4,
|
||||
dotRadiusSelected: 6,
|
||||
selectionRingM: 0.14,
|
||||
colorNormalArgb: 0xffffffff,
|
||||
colorSelectedArgb: 0xffff0000,
|
||||
labelColorNormalArgb: 0xffffffff,
|
||||
labelColorSelectedArgb: 0xffff0000
|
||||
},
|
||||
track: {
|
||||
drawScale: 1.5,
|
||||
widthNormal: 1.2,
|
||||
widthSelected: 2.5,
|
||||
showDirectionArrows: true,
|
||||
colorNormalArgb: 0xff00ffff,
|
||||
colorSelectedArgb: 0xffff0000
|
||||
}
|
||||
} satisfies ViewportStylePayload)
|
||||
: get<ViewportStylePayload>('/viewport-style'),
|
||||
|
||||
patchViewportStyle: (patch: ViewportStylePatch) => MOCK
|
||||
? Promise.resolve({
|
||||
site: {
|
||||
drawScale: 2.5,
|
||||
dotRadiusNormal: 4,
|
||||
dotRadiusSelected: 6,
|
||||
selectionRingM: 0.14,
|
||||
colorNormalArgb: 0xffffffff,
|
||||
colorSelectedArgb: 0xffff0000,
|
||||
labelColorNormalArgb: 0xffffffff,
|
||||
labelColorSelectedArgb: 0xffff0000
|
||||
},
|
||||
track: {
|
||||
drawScale: 1.5,
|
||||
widthNormal: 1.2,
|
||||
widthSelected: 2.5,
|
||||
showDirectionArrows: true,
|
||||
colorNormalArgb: 0xff00ffff,
|
||||
colorSelectedArgb: 0xffff0000
|
||||
}
|
||||
} satisfies ViewportStylePayload)
|
||||
: patchJson<ViewportStylePayload>('/viewport-style', sanitizeViewportPatch(patch)),
|
||||
|
||||
// /car-style/types GET 列出所有 Car 子类型样式
|
||||
// /car-style/{typeFullName} GET 单个车型当前样式
|
||||
// /car-style/{typeFullName} POST body JSON 全量覆盖
|
||||
@@ -551,6 +606,117 @@ export const reflectionApi = {
|
||||
: post<{ path: string }>('/car-style/save')
|
||||
}
|
||||
|
||||
/** 与 SimpleLite 视口样式对话框 / Configuration.conf.viewport 同步。 */
|
||||
export interface ViewportStyleSite {
|
||||
drawScale: number
|
||||
dotRadiusNormal: number
|
||||
dotRadiusSelected: number
|
||||
selectionRingM: number
|
||||
colorNormalArgb: number
|
||||
colorSelectedArgb: number
|
||||
labelColorNormalArgb: number
|
||||
labelColorSelectedArgb: number
|
||||
}
|
||||
|
||||
export interface ViewportStyleTrack {
|
||||
drawScale: number
|
||||
widthNormal: number
|
||||
widthSelected: number
|
||||
showDirectionArrows: boolean
|
||||
colorNormalArgb: number
|
||||
colorSelectedArgb: number
|
||||
}
|
||||
|
||||
export interface ViewportStylePayload {
|
||||
site: ViewportStyleSite
|
||||
track: ViewportStyleTrack
|
||||
}
|
||||
|
||||
export type ViewportStylePatch = {
|
||||
site?: Partial<ViewportStyleSite>
|
||||
track?: Partial<ViewportStyleTrack>
|
||||
}
|
||||
|
||||
/** 将 ARGB 规范为无符号 32 位,避免 JSON 序列化成负数导致后端 UInt32 反序列化失败。 */
|
||||
export function normalizeArgb(argb: number): number {
|
||||
if (!Number.isFinite(argb)) return 0
|
||||
return argb >>> 0
|
||||
}
|
||||
|
||||
export function argbToHex(argb: number): string {
|
||||
const u = normalizeArgb(argb)
|
||||
const a = (u >>> 24) & 0xff
|
||||
const r = (u >>> 16) & 0xff
|
||||
const g = (u >>> 8) & 0xff
|
||||
const b = u & 0xff
|
||||
if (a >= 255) return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
|
||||
return `#${a.toString(16).padStart(2, '0')}${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
export function hexToArgb(hex: string): number {
|
||||
const h = hex.replace('#', '').trim()
|
||||
if (h.length === 6) {
|
||||
const r = parseInt(h.slice(0, 2), 16)
|
||||
const g = parseInt(h.slice(2, 4), 16)
|
||||
const b = parseInt(h.slice(4, 6), 16)
|
||||
return normalizeArgb((0xff << 24) | (r << 16) | (g << 8) | b)
|
||||
}
|
||||
if (h.length === 8) {
|
||||
const a = parseInt(h.slice(0, 2), 16)
|
||||
const r = parseInt(h.slice(2, 4), 16)
|
||||
const g = parseInt(h.slice(4, 6), 16)
|
||||
const b = parseInt(h.slice(6, 8), 16)
|
||||
return normalizeArgb((a << 24) | (r << 16) | (g << 8) | b)
|
||||
}
|
||||
return 0xffffffff
|
||||
}
|
||||
|
||||
const VIEWPORT_SITE_ARGB_KEYS = [
|
||||
'colorNormalArgb',
|
||||
'colorSelectedArgb',
|
||||
'labelColorNormalArgb',
|
||||
'labelColorSelectedArgb'
|
||||
] as const
|
||||
|
||||
const VIEWPORT_TRACK_ARGB_KEYS = ['colorNormalArgb', 'colorSelectedArgb'] as const
|
||||
|
||||
/** PATCH 前把视口样式中的 ARGB 字段转为 UInt32 可接受的非负整数。 */
|
||||
export function sanitizeViewportPatch(patch: ViewportStylePatch): ViewportStylePatch {
|
||||
const out: ViewportStylePatch = {}
|
||||
if (patch.site) {
|
||||
const site = { ...patch.site }
|
||||
for (const k of VIEWPORT_SITE_ARGB_KEYS) {
|
||||
if (site[k] != null) site[k] = normalizeArgb(site[k]!)
|
||||
}
|
||||
out.site = site
|
||||
}
|
||||
if (patch.track) {
|
||||
const track = { ...patch.track }
|
||||
for (const k of VIEWPORT_TRACK_ARGB_KEYS) {
|
||||
if (track[k] != null) track[k] = normalizeArgb(track[k]!)
|
||||
}
|
||||
out.track = track
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
export function normalizeViewportPayload(raw: ViewportStylePayload): ViewportStylePayload {
|
||||
return {
|
||||
site: {
|
||||
...raw.site,
|
||||
colorNormalArgb: normalizeArgb(raw.site.colorNormalArgb),
|
||||
colorSelectedArgb: normalizeArgb(raw.site.colorSelectedArgb),
|
||||
labelColorNormalArgb: normalizeArgb(raw.site.labelColorNormalArgb),
|
||||
labelColorSelectedArgb: normalizeArgb(raw.site.labelColorSelectedArgb)
|
||||
},
|
||||
track: {
|
||||
...raw.track,
|
||||
colorNormalArgb: normalizeArgb(raw.track.colorNormalArgb),
|
||||
colorSelectedArgb: normalizeArgb(raw.track.colorSelectedArgb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface CarStyleDto {
|
||||
bodyLengthM: number
|
||||
bodyWidthM: number
|
||||
|
||||
Reference in New Issue
Block a user