Browse Source

StateManager.update 重构

master
修宁 6 months ago
parent
commit
48510e2837
  1. 44
      doc/物流模型总体介绍.md
  2. 347
      src/core/ModelUtils.ts
  3. 110
      src/core/base/BaseInteraction.ts
  4. 21
      src/core/controls/DragControl.ts
  5. 450
      src/core/manager/StateManager.ts
  6. 169
      src/editor/widgets/property/PropertyPanel.vue
  7. 264
      src/editor/widgets/property/PropertyView.vue

44
doc/物流模型总体介绍.md

@ -193,10 +193,15 @@ export default class WorldModel {
主要功能包括:
1. 管理场景数据的读取、保存, 以及临时保存、临时读取等功能
2. 管理撤销、重做功能
3. 交互控制组件通过如下步骤修改数据
- 1. 调用 beginStateUpdate 开始修改数据
- 2. 直接修改 vdata 数据
- 3. 调用 endStateUpdate 完成数据修改
3. 交互控制组件通过 update() 方法修改数据
this.viewport.stateManager.update(({ getEntity, putEntity, deleteEntity, addEntity }) => {
const entity = getEntity(id) // 获取实体
entity.abc = 123
putEntity(entity) // 提交修改
deleteEntity(id) // 删除实体
addEntity(newEntity) // 添加新实体
})
4. 内部如果进行了撤销、还原等操作,会通过 syncDataState() 方法将 vdata 数据与 EntityManager 进行同步
5. 注意,如果正在读取中,需要设置 isLoading = true,外部需要等待加载完成后再进行操作
@ -258,16 +263,6 @@ export default class StateManager {
constructor(id: string, viewport: Viewport, maxHistorySteps = 20)
/**
* 开始用户操作(创建数据快照)
*/
beginStateUpdate()
/**
* 结束用户操作(计算差异并保存)
*/
endStateUpdate(): void
/**
* 将当前数据 与 entityManager 进行同步, 对比出不同的部分,分别进行更新
* - 调用 entityManager.beginEntityUpdate() 开始更新
* - 调用 entityManager.createOrUpdateEntity(vdataItem) 添加场景中新的实体
@ -294,7 +289,12 @@ export default class StateManager {
/**
* 重做
*/
redo()
redo()
/**
* 使用 updater 函数更新状态, 并同步转换为实体和渲染
*/
update(updaterFn: (updater: StateUpdater) => void): void
/**
* 保存到本地存储 浏览器indexDb(防止数据丢失)
@ -311,6 +311,18 @@ export default class StateManager {
*/
async removeLocalstore()
}
// 状态更新器接口
interface StateUpdater {
getEntity(id: string): ItemJson
putEntity(entity: ItemJson): void // 修改已有实体
deleteEntity(id: string): boolean // 删除实体
addEntity(entity: ItemJson): void // 添加新实体
}
```
@ -868,4 +880,4 @@ StateManager -> EntityManager -> xxx.renderer -> InstancePool
interaction 完成临时辅助对象的创建, 辅助线 / 临时单元 是不会被持久化的
点之间的关系不会非常复杂, 通常是比较稀疏的, 可能一个点最多有6个连线, 绝大部分点 只有1~2个关系连线.
点之间的关系不会非常复杂, 通常是比较稀疏的, 可能一个点最多有6个连线, 绝大部分点 只有1~2个关系连线.

347
src/core/ModelUtils.ts

@ -137,50 +137,47 @@ export function deletePointByKeyboard() {
return
}
// 删除当前选中的实体
const entityId = viewport.state.selectedEntityId
if (!entityId) {
// 删除多选实体
const multiSelectedEntityIds = viewport.state.multiSelectedEntityIds
if (!multiSelectedEntityIds && multiSelectedEntityIds.length === 0) {
system.msg('请选中要删除的实体', 'error')
return
}
const stateManager = viewport.stateManager
stateManager.beginStateUpdate()
const deleteItems = _.remove(stateManager.vdata.items, (item) => multiSelectedEntityIds.includes(item.id))
stateManager.endStateUpdate()
if (deleteItems.length === 0) {
let deleteCount = 0
viewport.stateManager.update(({ getEntity, putEntity, deleteEntity }) => {
for (const entityId of multiSelectedEntityIds) {
deleteEntity(entityId) && deleteCount++
}
})
if (deleteCount === 0) {
system.msg('没有找到要删除的实体', 'error')
} else {
system.msg('删除了 ' + deleteItems.length + ' 个实体')
system.msg('删除了 ' + deleteCount + ' 个实体')
}
for (const deleteEntityId of multiSelectedEntityIds) {
EventBus.dispatch('entityDeleted', {
deleteEntityId: deleteEntityId
})
viewport.selectInspect.clearRedSelectionBoxes()
}
viewport.selectInspect.cancelMultiSelect()
return
}
const stateManager = viewport.stateManager
viewport.stateManager.beginStateUpdate()
_.remove(stateManager.vdata.items, (item) => item.id === entityId)
viewport.stateManager.endStateUpdate()
if (viewport.state.selectedEntityId === entityId) {
viewport.state.selectedObject = undefined
viewport.state.selectedItem = undefined
viewport.state.selectedEntityId = undefined
viewport.state.selectedObjectSetter = undefined
}
EventBus.dispatch('entityDeleted', {
deleteEntityId: entityId
viewport.stateManager.update(({ getEntity, putEntity, deleteEntity }) => {
// 删除实体
if (deleteEntity(entityId)) {
system.msg('删除实体 [' + entityId + '] 成功')
EventBus.dispatch('entityDeleted', {
deleteEntityId: entityId
})
viewport.selectInspect.cancelSelect()
}
})
system.msg('删除 [' + entityId + ']')
viewport.selectInspect.clearSelectionBox()
}
export function escByKeyboard() {
@ -210,38 +207,6 @@ export function escByKeyboard() {
system.msg('操作已取消')
}
/**
* stateManager item
* , beginStateUpdate
* @param viewport
* @param point x,z
* @param distance
*/
export function findStateItemsByDistance(viewport: Viewport, point: Vector2, distance: number): ItemJson[] {
const result: ItemJson[] = []
for (const item of viewport.stateManager.vdata.items) {
// 安全校验 tf 结构
if (!item.tf || !Array.isArray(item.tf) || item.tf[0].length < 3) {
continue
}
const [x, , z] = item.tf[0]
const itemPoint = new Vector2(x, z)
const dist = itemPoint.distanceTo(point)
if (dist <= distance) {
result.push(item)
}
}
// 按距离升序排序(近距离优先)
return result.sort((a, b) => {
const aPos = new Vector2(a.tf[0][0], a.tf[0][2])
const bPos = new Vector2(b.tf[0][0], b.tf[0][2])
return aPos.distanceTo(point) - bPos.distanceTo(point)
})
}
export function moveSelectedItem(direct: '↑' | '↓' | '←' | '→') {
// 获取当前是否按住了 Shift
@ -266,14 +231,11 @@ export function moveSelectedItem(direct: '↑' | '↓' | '←' | '→') {
}
// 群体移动
const stateManager = viewport.stateManager
stateManager.beginStateUpdate()
for (const item of stateManager.vdata.items) {
if (multiSelectedEntityIds.includes(item.id)) {
// 根据方向移动
viewport.stateManager.update(({ getEntity, putEntity, deleteEntity }) => {
for (const id of multiSelectedEntityIds) {
const item = getEntity(id)
switch (direct) {
case '↑':
console.log('向上移动', item.tf[0][2], '-=', delta)
item.tf[0][2] -= delta // 向上移动
break
case '↓':
@ -286,9 +248,9 @@ export function moveSelectedItem(direct: '↑' | '↓' | '←' | '→') {
item.tf[0][0] += delta // 向右移动
break
}
putEntity(item)
}
}
stateManager.endStateUpdate()
})
EventBus.dispatch('multiselectedObjectChanged', {
multiSelectedObjects: viewport.state.multiSelectedObjects
})
@ -296,28 +258,30 @@ export function moveSelectedItem(direct: '↑' | '↓' | '←' | '→') {
return
}
const stateManager = viewport.stateManager
viewport.stateManager.beginStateUpdate()
for (const item of stateManager.vdata.items) {
if (item.id === entityId) {
// 根据方向移动
switch (direct) {
case '↑':
item.tf[0][2] -= delta // 向上移动
break
case '↓':
item.tf[0][2] += delta // 向下移动
break
case '←':
item.tf[0][0] -= delta // 向左移动
break
case '→':
item.tf[0][0] += delta // 向右移动
break
}
viewport.stateManager.update(({ getEntity, putEntity, deleteEntity }) => {
const item = getEntity(entityId)
if (!item) {
system.msg('没有找到选中的实体', 'error')
return
}
}
viewport.stateManager.endStateUpdate()
// 根据方向移动
switch (direct) {
case '↑':
item.tf[0][2] -= delta // 向上移动
break
case '↓':
item.tf[0][2] += delta // 向下移动
break
case '←':
item.tf[0][0] -= delta // 向左移动
break
case '→':
item.tf[0][0] += delta // 向右移动
break
}
putEntity(item)
})
EventBus.dispatch('multiselectedObjectChanged', {
multiSelectedObjects: viewport.state.multiSelectedObjects
})
@ -336,115 +300,18 @@ export function quickCopyByMouse() {
const x = CurrentMouseInfo.x
const z = CurrentMouseInfo.z
const viewport: Viewport = CurrentMouseInfo.viewport
// const point: THREE.Vector2 = currentMouseInfo.mouse
//
// const ray = new THREE.Raycaster()
// ray.setFromCamera(point, viewport.camera)
// const intersections = ray.intersectObjects(viewport.dragControl._dragObjects, true)
//
// if (intersections.length === 0) {
// system.msg('没有找到可复制的对象')
// return
// }
// console.log('intersections:', intersections)
// 如果不在线上,查找1米内的有效点 Object3D, 如果有,则以这个点为起点, 延伸同类型的点,并让他们相连
const items = findStateItemsByDistance(viewport, new Vector2(x, z), 1)
const items = viewport.stateManager.findStateItemsByDistance(new Vector2(x, z), 1)
if (items[0]) {
// 找到一个有效点,执行复制操作
viewport.interactionManager.startInteraction(items[0].t, { startPoint: items[0].id })
return
}
// const r = findObject3DByCondition(viewport.scene, object => {
// // 判断 object 是否是有效的 Object3D, 并且是当前 viewport 的对象
// if (object instanceof THREE.Object3D && object.visible &&
// object.userData.type && viewport.toolbox[object.userData.type]) {
//
// const toolbox: Toolbox = viewport.toolbox[object.userData.type]
//
// // 检查是否在 0.2 米内
// const distance = object.position.distanceTo(new THREE.Vector3(x, 0, z))
// if (distance < 0.2) {
// // 找到一个有效点,执行复制操作
// viewport.toolStartObject = object
// viewport.state.cursorMode = object.userData.type
// // toolbox.start(object)
// system.msg('连线成功')
// return true
// }
// }
// return false
// })
if (!items || items.length === 0) {
} else {
system.msg('鼠标所在位置,没有可复制的对象', 'error')
return
}
}
//
// /**
// * 查找射线周围指定半径内的对象
// */
// export function findObjectsInRadius(viewport: Viewport,
// point: THREE.Vector2,
// radius: number,
// lines: { object: THREE.Object3D, distance: number }[],
// points: { object: THREE.Object3D, distance: number }[]
// ): void {
// const ray = new THREE.Raycaster()
// ray.setFromCamera(point, viewport.camera)
//
// viewport.dragControl._dragObjects.forEach(obj => {
// if (obj instanceof THREE.Points) {
// // 处理点云:遍历每个点
// const distance = distanceToRay(ray, point)
// if (distance <= radius) {
// points.push({ object: obj, distance })
// }
//
// } else if (obj instanceof THREE.Line) {
// // 处理线段:计算线段到射线的最近距离
// const distance = getLineDistanceToRay(ray, obj)
// if (distance <= radius) {
// lines.push({ object: obj, distance })
// }
// }
// })
// }
//
// /**
// * 计算点到射线的最短距离
// */
// function distanceToRay(ray: THREE.Raycaster, point: THREE.Vector2) {
// const closestPoint = new THREE.Vector3()
// ray.closestPointToPoint(point, closestPoint)
// return point.distanceTo(closestPoint)
// }
//
// /**
// * 计算线段到射线的最短距离
// */
// function getLineDistanceToRay(ray: THREE.Raycaster, line: THREE.Line) {
// const lineStart = new THREE.Vector3()
// const lineEnd = new THREE.Vector3()
// line.geometry.attributes.position.getXYZ(0, lineStart)
// line.geometry.attributes.position.getXYZ(1, lineEnd)
// line.localToWorld(lineStart)
// line.localToWorld(lineEnd)
//
// const lineSegment = new THREE.Line3(lineStart, lineEnd)
// const closestOnRay = new THREE.Vector3()
// const closestOnLine = new THREE.Vector3()
// THREE.Line3.prototype.closestPointsRayLine ??= function(ray, line, closestOnRay, closestOnLine) {
// // 实现射线与线段最近点计算(需自定义或使用数学库)
// }
//
// lineSegment.closestPointsRayLine(ray, true, closestOnRay, closestOnLine)
// return closestOnRay.distanceTo(closestOnLine)
// }
/**
*
*/
@ -513,62 +380,62 @@ export function findObject3DByCondition(scene: THREE.Object3D, condition: (objec
return foundObjects
}
export function loadSceneFromJson(viewport: Viewport, scene: THREE.Scene, items: ItemJson[]) {
console.time('loadSceneFromJson')
const object3ds: THREE.Object3D[] = []
// beforeLoad 通知所有加载的对象, 模型加载开始
getAllItemTypes().forEach((itemType: ItemTypeDefineOption) => {
const ret = itemType.clazz.beforeLoad()
Array.isArray(ret) && object3ds.push(...ret)
})
const loads = loadObject3DFromJson(items)
Array.isArray(loads) && object3ds.push(...loads)
// afterLoadComplete 通知所有加载的对象, 模型加载完成
getAllItemTypes().forEach((itemType: ItemTypeDefineOption) => {
const ret = itemType.clazz.afterLoadComplete(object3ds)
Array.isArray(ret) && object3ds.push(...ret)
})
scene.add(...object3ds)
// afterAddScene 通知所有加载的对象, 模型加载完成
getAllItemTypes().forEach(itemType => {
itemType.clazz.afterAddScene(viewport, scene, object3ds)
})
console.log('loadSceneFromJson:', items.length, 'items,', object3ds.length, 'objects')
console.timeEnd('loadSceneFromJson')
}
function loadObject3DFromJson(items: ItemJson[]): THREE.Object3D[] {
const result: THREE.Object3D[] = []
for (const item of items) {
if (!item || !item.t) {
console.error('unkown item:', item)
continue
}
const object3D: THREE.Object3D | undefined = getItemTypeByName(item.t)?.clazz.loadFromJson(item)
if (object3D === undefined) {
continue
}
if (_.isArray(item.items)) {
// 如果有子元素,递归处理
const children = loadObject3DFromJson(item.items)
children.forEach(child => object3D.add(child))
}
result.push(object3D)
}
return result
}
// export function loadSceneFromJson(viewport: Viewport, scene: THREE.Scene, items: ItemJson[]) {
// console.time('loadSceneFromJson')
//
// const object3ds: THREE.Object3D[] = []
//
// // beforeLoad 通知所有加载的对象, 模型加载开始
// getAllItemTypes().forEach((itemType: ItemTypeDefineOption) => {
// const ret = itemType.clazz.beforeLoad()
// Array.isArray(ret) && object3ds.push(...ret)
// })
//
// const loads = loadObject3DFromJson(items)
// Array.isArray(loads) && object3ds.push(...loads)
//
// // afterLoadComplete 通知所有加载的对象, 模型加载完成
// getAllItemTypes().forEach((itemType: ItemTypeDefineOption) => {
// const ret = itemType.clazz.afterLoadComplete(object3ds)
// Array.isArray(ret) && object3ds.push(...ret)
// })
//
// scene.add(...object3ds)
//
// // afterAddScene 通知所有加载的对象, 模型加载完成
// getAllItemTypes().forEach(itemType => {
// itemType.clazz.afterAddScene(viewport, scene, object3ds)
// })
//
// console.log('loadSceneFromJson:', items.length, 'items,', object3ds.length, 'objects')
// console.timeEnd('loadSceneFromJson')
// }
//
// function loadObject3DFromJson(items: ItemJson[]): THREE.Object3D[] {
// const result: THREE.Object3D[] = []
//
// for (const item of items) {
// if (!item || !item.t) {
// console.error('unkown item:', item)
// continue
// }
//
// const object3D: THREE.Object3D | undefined = getItemTypeByName(item.t)?.clazz.loadFromJson(item)
// if (object3D === undefined) {
// continue
// }
//
// if (_.isArray(item.items)) {
// // 如果有子元素,递归处理
// const children = loadObject3DFromJson(item.items)
// children.forEach(child => object3D.add(child))
// }
//
// result.push(object3D)
// }
//
// return result
// }
/**
*

110
src/core/base/BaseInteraction.ts

@ -42,10 +42,6 @@ export default abstract class BaseInteraction {
linkStartPointId: string
linkStartPointObject: THREE.Object3D
dragOption: DragOption | undefined
dragOriginPosition: THREE.Vector3 | undefined
dragItem: ItemJson | undefined
templineMaterial = new LineMaterial({
color: 0xE63C17, // 主颜色
linewidth: 2, // 实际可用的线宽
@ -90,56 +86,6 @@ export default abstract class BaseInteraction {
}
/**
*
*/
dragPointStart(viewport: Viewport, dragOption: DragOption) {
this.viewport = viewport
this.dragOption = dragOption
this.dragOriginPosition = dragOption.object.position.clone()
// 找到 itemJson
const itemJson = _.find(this.viewport.stateManager.vdata.items, (item) => item.id === dragOption.entityId)
if (!itemJson) {
system.showErrorDialog('Not found for entityId:' + dragOption.entityId)
return false
}
this.dragItem = itemJson
return true
}
/**
*
*/
dragPointMove(viewport: Viewport, e: MouseEvent) {
if (this.viewport !== viewport) return
}
/**
*
*/
dragPointComplete(viewport: Viewport, e: MouseEvent) {
if (this.viewport !== viewport) return
// 获取当前鼠标所在位置
if (!CurrentMouseInfo || isNaN(CurrentMouseInfo.x) || isNaN(CurrentMouseInfo.z) || !this.dragItem?.tf?.[0]) {
return
}
// 提交状态管理器
const stateManager = this.viewport.stateManager
stateManager.beginStateUpdate({ createFromInteraction: true })
this.dragItem.tf[0][0] = CurrentMouseInfo.x
this.dragItem.tf[0][2] = CurrentMouseInfo.z
stateManager.endStateUpdate()
this.viewport = undefined
this.dragOption = undefined
this.dragItem = undefined
return true
}
/**
*
*/
start(viewport: Viewport, option: InteractionOption = {}) {
@ -318,12 +264,11 @@ export default abstract class BaseInteraction {
}
// 则添加一个新的点
const stateManager = this.viewport.stateManager
stateManager.beginStateUpdate({ createFromInteraction: true })
catchPoint = {} as ItemJson
catchPoint = this.createPointOfItem(catchPoint, point)
stateManager.vdata.items.push(catchPoint)
stateManager.endStateUpdate()
this.viewport.stateManager.update(({ getEntity, putEntity, deleteEntity, addEntity }) => {
catchPoint = {} as ItemJson
catchPoint = this.createPointOfItem(catchPoint, point)
addEntity(catchPoint)
})
return
}
@ -343,19 +288,17 @@ export default abstract class BaseInteraction {
system.msg('Cannot link to itself.')
return
}
// 关联2个点
if (this.linkStartPointId && from) {
// 关联2个点
this.viewport.stateManager.update(({ getEntity, putEntity, deleteEntity, addEntity }) => {
catchPoint = getEntity(catchPoint.id)
catchPoint.dt.center.push(this.linkStartPointId)
from.dt.center.push(catchPoint.id)
}
putEntity(catchPoint)
// 提交状态管理器
const stateManager = this.viewport.stateManager
stateManager.beginStateUpdate({ createFromInteraction: true })
catchPoint.dt.center.push(this.linkStartPointId)
from.dt.center.push(catchPoint.id)
stateManager.endStateUpdate()
from = getEntity(from.id)
from.dt.center.push(catchPoint.id)
putEntity(from)
})
} else {
// 添加正式点
@ -363,18 +306,19 @@ export default abstract class BaseInteraction {
catchPoint = this.createPointOfItem(catchPoint, point)
// 提交状态管理器
const stateManager = this.viewport.stateManager
stateManager.beginStateUpdate({ createFromInteraction: true })
// 关联2个点
stateManager.vdata.items.push(catchPoint)
if (from) {
catchPoint.dt.center.push(this.linkStartPointId)
from.dt.center.push(catchPoint.id)
} else {
stateManager.vdata.items.push(catchPoint)
}
stateManager.endStateUpdate()
this.viewport.stateManager.update(({ getEntity, putEntity, deleteEntity, addEntity }) => {
if (from) {
catchPoint.dt.center.push(from.id)
addEntity(catchPoint)
from = getEntity(from.id)
from.dt.center.push(catchPoint.id)
putEntity(from)
} else {
addEntity(catchPoint)
}
})
}
// 把点加入拖拽控制器
@ -442,4 +386,4 @@ export default abstract class BaseInteraction {
export interface DragOption {
object: THREE.Object3D
entityId: string
}
}

21
src/core/controls/DragControl.ts

@ -255,24 +255,23 @@ export default class DragControl implements IControls {
private dragComplete = (startPos: THREE.Vector2, targetPos: THREE.Vector2): void => {
// console.log(`Drag completed from ${startPos.toArray()} to ${targetPos.toArray()}`)
this.viewport.stateManager.beginStateUpdate()
for (const object of this.dragShadowsGroup.children) {
const entityId = object.userData.entityId
if (entityId) {
const entity = this.viewport.stateManager.findItemById(entityId)
if (entity) {
this.viewport.stateManager.update(({ getEntity, putEntity, deleteEntity, addEntity }) => {
for (const object of this.dragShadowsGroup.children) {
const entityId = object.userData.entityId
if (entityId) {
const entity = getEntity(entityId)
// 更新实体位置
entity.tf[0][0] = object.position.x
entity.tf[0][2] = object.position.z
putEntity(entity)
} else {
system.showErrorDialog('not found entityId:' + entityId)
system.showErrorDialog('not found entity')
}
} else {
system.showErrorDialog('not found entity')
}
}
this.viewport.stateManager.endStateUpdate()
})
EventBus.dispatch('multiselectedObjectChanged', {
multiSelectedObjects: this.viewport.state.multiSelectedObjects
})

450
src/core/manager/StateManager.ts

@ -4,9 +4,10 @@ import localforage from 'localforage'
import type EntityManager from './EntityManager'
import { markRaw, reactive, ref } from 'vue'
import type Viewport from '@/core/engine/Viewport.ts'
import { getQueryParams, setQueryParam } from '@/utils/webutils.ts'
import { getFreezeDeep, getQueryParams, setQueryParam } from '@/utils/webutils.ts'
import { ensureEntityRelationsConsistency } from '@/core/ModelUtils.ts'
import EventBus from '@/runtime/EventBus.ts'
import { Vector2 } from 'three/src/math/Vector2'
// 差异类型定义
interface DataDiff {
@ -28,38 +29,20 @@ interface HistoryStep {
*
* 1. ,
* 2.
* 3.
* - 1. beginStateUpdate
* - 2. vdata
* - 3. endStateUpdate
* 3. update()
* this.viewport.stateManager.update(({ getEntity, putEntity, deleteEntity, addEntity }) => {
* const entity = getEntity(id) // 获取实体
* entity.abc = 123
* putEntity(entity) // 提交修改
*
* deleteEntity(id) // 删除实体
* addEntity(newEntity) // 添加新实体
* })
* 4. syncDataState() vdata EntityManager
* 5. isLoading = true
*
* :
* - 10000 ,
*
* // 用例1, 修改
* stateManager.beginStateUpdate();stateManager.vdata.items[1].tf[0] = [-10, 0, 4];stateManager.endStateUpdate();
* stateManager.undo()
* stateManager.redo()
*
* // 用例2 添加
* stateManager.beginStateUpdate();stateManager.vdata.items[3].dt.center.push('p5');stateManager.vdata.items.push({ id: 'p5', t: 'measure', tf: [[-6.0, 0, 8], [0, 0, 0], [0.25, 0.1, 0.25]], dt: { center: ['p4'] } });stateManager.endStateUpdate();
* stateManager.undo();
* stateManager.redo();
*
* // 用例3 删除
* stateManager.beginStateUpdate(); stateManager.vdata.items.splice(3, 1); stateManager.endStateUpdate();
* stateManager.undo()
* stateManager.redo()
*
* // 用例4 多连线
* stateManager.beginStateUpdate();stateManager.vdata.items[0].dt.center.push('p3');stateManager.vdata.items[2].dt.center.push('p1');stateManager.endStateUpdate();
* stateManager.undo()
*
* // 用例5 删除线
* stateManager.beginStateUpdate();stateManager.vdata.items[0].dt.center=[];stateManager.vdata.items[1].dt.center.splice(0,1);stateManager.endStateUpdate();
* stateManager.undo()
*/
export default class StateManager {
/**
@ -87,7 +70,7 @@ export default class StateManager {
/**
*
*/
vdata: VData
private ___vdata: VData
/**
* 使
@ -110,11 +93,6 @@ export default class StateManager {
}
/**
*
*/
private lastStateDict = new Map<string, ItemJson>()
/**
* @param id , key
* @param viewport ,
* @param maxHistorySteps 20
@ -125,40 +103,12 @@ export default class StateManager {
this.entityManager = viewport.entityManager
this.maxHistorySteps = maxHistorySteps
// this.historySteps = Array.from({ length: this.maxHistorySteps }, () => null)
this.historySteps = []
this.pendingChanges = false
this.isAutoSavingPaused = false
}
/**
*
*/
beginStateUpdate(option: StateUpdateOption = {}): void {
this.lastStateDict = new Map(this.vdata.items.map(item => [item.id, _.cloneDeep(item)]))
this.changeTracker.added.length = 0
this.changeTracker.removed.length = 0
this.changeTracker.updated.length = 0
this.isUpdating = true
}
/**
*
*/
endStateUpdate(option = { autoSave: true }): void {
this.calculateDiff()
this.saveStep()
this.syncDataState(this.changeTracker)
this.isChanged.value = true
this.pendingChanges = true // 标记有需要保存的更改
this.isUpdating = false
if (option.autoSave) {
this.startAutoSave() // 触发自动保存
}
}
// 差异反转方法
private invertDiff(diff: DataDiff): DataDiff {
return {
@ -168,67 +118,6 @@ export default class StateManager {
}
}
// 计算当前状态与快照的差异
private calculateDiff() {
const currentMap = new Map(this.vdata.items.map(item => [item.id, item]))
const added: ItemJson[] = []
const removed: ItemJson[] = []
const updated: { before: ItemJson; after: ItemJson }[] = []
// 检查删除 & 更新
for (const [id, item] of this.lastStateDict.entries()) {
if (!currentMap.has(id)) {
removed.push(item)
} else if (!_.isEqual(item, currentMap.get(id))) {
updated.push({
before: item,
after: currentMap.get(id)
})
}
}
// 检查新增
for (const [id, item] of currentMap.entries()) {
if (!this.lastStateDict.has(id)) {
added.push(item)
}
}
this.changeTracker.added = added
this.changeTracker.removed = removed
this.changeTracker.updated = updated
}
/**
*
*/
private saveStep() {
const { added, removed, updated } = this.changeTracker
if (added.length === 0 && removed.length === 0 && updated.length === 0) return
// 深拷贝差异对象
const clonedDiff = {
added: _.cloneDeep(added),
removed: _.cloneDeep(removed),
updated: _.cloneDeep(updated)
}
const step: HistoryStep = {
diff: clonedDiff,
timestamp: Date.now()
}
if (this.historySteps.length >= this.maxHistorySteps) {
this.historySteps.shift()
}
this.historySteps.push(step)
this.historyIndex = this.historySteps.length - 1
this.pendingChanges = true
}
/**
* entityManager
* - entityManager.beginEntityUpdate()
@ -267,10 +156,10 @@ export default class StateManager {
// 获取被改过的数据, 覆盖之前的数据
const writeBackMap = this.entityManager.cloneWriteBackEntities()
for (let i = 0; i < this.vdata.items.length; i++) {
const item = this.vdata.items[i]
for (let i = 0; i < this.___vdata.items.length; i++) {
const item = this.___vdata.items[i]
if (writeBackMap.has(item.id)) {
this.vdata.items[i] = writeBackMap.get(item.id)
this.___vdata.items[i] = writeBackMap.get(item.id)
}
}
}
@ -346,50 +235,60 @@ export default class StateManager {
this.startAutoSave()
}
startAutoSave() {
this.isAutoSavingPaused = false
if (this.autoSaveTimer !== null) {
window.clearTimeout(this.autoSaveTimer)
this.autoSaveTimer = null
}
// 差异应用方法
private applyDiff(diff: DataDiff) {
const { added, removed, updated } = diff
// 删除
const removedIds = new Set(removed.map(item => item.id))
_.remove(this.___vdata.items, item => removedIds.has(item.id))
// 设置新的定时器,在 5 秒后尝试保存
this.queueAutoSave()
}
// 新增
const addedIds = new Set(added.map(i => i.id))
_.remove(this.___vdata.items, item => addedIds.has(item.id))
this.___vdata.items.push(...added)
stopAutoSave() {
if (this.autoSaveTimer !== null) {
window.clearTimeout(this.autoSaveTimer)
this.autoSaveTimer = null
// 更新
const updateMap = new Map(updated.map(u => [u.after.id, u.after]))
for (const [key, after] of updateMap) {
const idx = _.findIndex(this.___vdata.items, (item => item.id === key))
if (idx >= 0) {
Object.assign(this.___vdata.items[idx], after)
} else {
this.___vdata.items.push(after)
}
}
this.isAutoSavingPaused = true
}
private queueAutoSave() {
if (this.autoSaveTimer !== null || this.isAutoSavingPaused) return
if (!this.pendingChanges) return
/**
*
* @private
*/
private fullSync(isAutoSave = true) {
// 修补关系数据, 确保 center / in / out 关系满足一致性
this.___vdata.items = ensureEntityRelationsConsistency(this.___vdata.items)
this.autoSaveTimer = window.setTimeout(async () => {
this.autoSaveTimer = null
this.entityManager.beginEntityUpdate()
this.___vdata.items.forEach(item => {
this.entityManager.createOrUpdateEntity(item)
})
this.entityManager.endEntityUpdate()
if (this.isUpdating) {
const checkInterval = setInterval(() => {
if (!this.isUpdating) {
clearInterval(checkInterval)
if (!this.isAutoSavingPaused) {
this.queueAutoSave()
}
} else {
console.log('wait for entityManager to finish updating...')
}
}, 200)
return
}
// 初始状态作为第一步
this.pendingChanges = false
await this.saveToLocalstore()
}, 2500)
if (isAutoSave) {
this.startAutoSave()
}
}
undoEnabled() {
return this.historyIndex >= 0
}
redoEnabled() {
const nextIndex = (this.historyIndex + 1) % this.maxHistorySteps
return !!this.historySteps[nextIndex]
}
/**
@ -406,7 +305,7 @@ export default class StateManager {
// 直接替换数组引用(避免响应式开销)
//@ts-ignore
this.vdata = {
this.___vdata = {
id: this.id,
isChanged: false,
...data
@ -426,73 +325,66 @@ export default class StateManager {
}
}
// 差异应用方法
private applyDiff(diff: DataDiff) {
const { added, removed, updated } = diff
// 删除
const removedIds = new Set(removed.map(item => item.id))
_.remove(this.vdata.items, item => removedIds.has(item.id))
// 新增
const addedIds = new Set(added.map(i => i.id))
_.remove(this.vdata.items, item => addedIds.has(item.id))
this.vdata.items.push(...added)
startAutoSave() {
this.isAutoSavingPaused = false
// 更新
const updateMap = new Map(updated.map(u => [u.after.id, u.after]))
for (const [key, after] of updateMap) {
const idx = _.findIndex(this.vdata.items, (item => item.id === key))
if (idx >= 0) {
Object.assign(this.vdata.items[idx], after)
} else {
this.vdata.items.push(after)
}
if (this.autoSaveTimer !== null) {
window.clearTimeout(this.autoSaveTimer)
this.autoSaveTimer = null
}
}
/**
*
* @private
*/
private fullSync() {
// 修补关系数据, 确保 center / in / out 关系满足一致性
this.vdata.items = ensureEntityRelationsConsistency(this.vdata.items)
this.entityManager.beginEntityUpdate()
this.vdata.items.forEach(item => {
this.entityManager.createOrUpdateEntity(item)
})
this.entityManager.endEntityUpdate()
// 初始状态作为第一步
this.beginStateUpdate()
this.endStateUpdate({ autoSave: false })
this.pendingChanges = false
// 设置新的定时器,在 5 秒后尝试保存
this.queueAutoSave()
}
undoEnabled() {
return this.historyIndex >= 0
stopAutoSave() {
if (this.autoSaveTimer !== null) {
window.clearTimeout(this.autoSaveTimer)
this.autoSaveTimer = null
}
this.isAutoSavingPaused = true
}
redoEnabled() {
const nextIndex = (this.historyIndex + 1) % this.maxHistorySteps
return !!this.historySteps[nextIndex]
private queueAutoSave() {
if (this.autoSaveTimer !== null || this.isAutoSavingPaused) return
if (!this.pendingChanges) return
this.autoSaveTimer = window.setTimeout(async () => {
this.autoSaveTimer = null
if (this.isUpdating) {
const checkInterval = setInterval(() => {
if (!this.isUpdating) {
clearInterval(checkInterval)
if (!this.isAutoSavingPaused) {
this.queueAutoSave()
}
} else {
console.log('wait for entityManager to finish updating...')
}
}, 200)
return
}
await this.saveToLocalstore()
}, 2000)
}
/**
*
*/
async save(): Promise<Object> {
return _.cloneDeep(this.vdata)
return _.cloneDeep(this.___vdata)
}
/**
* indexDb()
*/
async saveToLocalstore() {
await localforage.setItem(this.storeKey, this.vdata)
console.log('[StateManager] 数据已保存草稿:' + this.storeKey + ', 共', this.vdata.items.length, '个对象')
await localforage.setItem(this.storeKey, this.___vdata)
console.log('[StateManager] 数据已保存草稿:' + this.storeKey + ', 共', this.___vdata.items.length, '个对象')
}
/**
@ -503,13 +395,13 @@ export default class StateManager {
this.isLoading.value = true
const saved: VData = await localforage.getItem(this.storeKey)
if (saved) {
this.vdata = {
this.___vdata = {
...saved
}
this.isChanged.value = saved.isChanged || false
this.fullSync() // 同步到视口
console.log('[StateManager] 从本地存储恢复', this.vdata.items.length, '个对象')
this.fullSync(false) // 同步到视口
console.log('[StateManager] 从本地存储恢复', this.___vdata.items.length, '个对象')
return true
}
@ -541,7 +433,7 @@ export default class StateManager {
*/
dispose() {
// 清理引用
delete this.vdata
delete this.___vdata
delete this.historySteps
}
@ -582,23 +474,151 @@ export default class StateManager {
* ID
*/
findItemById(linkStartPointId: string): ItemJson | undefined {
return _.find(this.vdata.items, item => item.id === linkStartPointId)
return getFreezeDeep(_.find(this.___vdata.items, item => item.id === linkStartPointId))
}
/**
* ,
*/
findItemByPosition(point: THREE.Vector3, itemTypeName?: string): ItemJson | undefined {
for (const item of this.vdata.items) {
for (const item of this.___vdata.items) {
if (item.tf?.[0]?.[0] === point.x && item.tf?.[0]?.[2] === point.z) {
if (!itemTypeName || item.t === itemTypeName) {
return item
return getFreezeDeep(item)
}
}
}
}
/**
* 使 updater ,
*/
update(updaterFn: (updater: StateUpdater) => void): void {
const changeTracker: DataDiff = {
added: [],
removed: [],
updated: []
}
this.isUpdating = true
const entityMap = new Map(this.___vdata.items.map(item => [item.id, item]))
// 构建 Updater 实例
const updater: StateUpdater = {
getEntity: (id: string) => {
const entity = entityMap.get(id)
if (!entity) throw new Error(`Entity with id ${id} not found`)
return _.cloneDeep(entity)
},
putEntity: (entity: ItemJson) => {
const id = entity.id
const original = entityMap.get(id)
if (!original) throw new Error(`Entity with id ${id} not found for update`)
if (!_.isEqual(original, entity)) {
changeTracker.updated.push({
before: _.cloneDeep(original),
after: _.cloneDeep(entity)
})
entityMap.set(id, _.cloneDeep(entity))
}
},
deleteEntity: (id: string): boolean => {
const original = entityMap.get(id)
if (!original) return
changeTracker.removed.push(_.cloneDeep(original))
return entityMap.delete(id)
},
addEntity: (entity: ItemJson) => {
const id = entity.id
if (entityMap.has(id)) throw new Error(`Entity with id ${id} already exists`)
changeTracker.added.push(_.cloneDeep(entity))
entityMap.set(id, _.cloneDeep(entity))
}
}
// 停止自动保存,避免在更新过程中触发
this.stopAutoSave()
// 执行用户传入的修改函数
updaterFn(updater)
// 更新 vdata.items
this.___vdata.items = Array.from(entityMap.values())
// 保存差分到 history 和 sync 到 entityManager
this.saveStepWithDiff(changeTracker)
this.syncDataState(changeTracker)
this.isChanged.value = true
this.pendingChanges = true
this.isUpdating = false
this.startAutoSave()
}
private saveStepWithDiff(diff: DataDiff): void {
if (diff.added.length === 0 && diff.removed.length === 0 && diff.updated.length === 0) {
return
}
const step: HistoryStep = {
diff: {
added: _.cloneDeep(diff.added),
removed: _.cloneDeep(diff.removed),
updated: _.cloneDeep(diff.updated)
},
timestamp: Date.now()
}
if (this.historySteps.length >= this.maxHistorySteps) {
this.historySteps.shift()
}
this.historySteps.push(step)
this.historyIndex = this.historySteps.length - 1
}
/**
* stateManager item
* @param point x,z
* @param distance
*/
findStateItemsByDistance(point: Vector2, distance: number): ItemJson[] {
const result: ItemJson[] = []
for (const item of this.___vdata.items) {
// 安全校验 tf 结构
if (!item.tf || !Array.isArray(item.tf) || item.tf[0].length < 3) {
continue
}
const [x, , z] = item.tf[0]
const itemPoint = new Vector2(x, z)
const dist = itemPoint.distanceTo(point)
if (dist <= distance) {
result.push(getFreezeDeep(item))
}
}
// 按距离升序排序(近距离优先)
return result.sort((a, b) => {
const aPos = new Vector2(a.tf[0][0], a.tf[0][2])
const bPos = new Vector2(b.tf[0][0], b.tf[0][2])
return aPos.distanceTo(point) - bPos.distanceTo(point)
})
}
}
export interface StateUpdater {
getEntity(id: string): ItemJson
putEntity(entity: ItemJson): void // 修改已有实体
deleteEntity(id: string): boolean // 删除实体
addEntity(entity: ItemJson): void // 添加新实体
}
export interface StateUpdateOption {
autoSave?: boolean
createFromInteraction?: boolean
}

169
src/editor/widgets/property/PropertyPanel.vue

@ -1,138 +1,139 @@
<script setup lang="ts">
import lodash from "lodash";
import { computed, reactive } from "vue";
import { ElCollapse, ElCollapseItem } from "element-plus";
import DataForm from "@/components/data-form/DataForm.vue";
import type { PropertyFlattenSetter, PropertySetterGroup } from "@/core/base/PropertyTypes.ts";
import type { DataFormProps } from "@/components/data-form/DataFormTypes.ts";
import { defDataFormProps } from "@/editor/widgets/property/PropertyPanelConstant.ts";
import Viewport, { type ViewportState } from "@/core/engine/Viewport.ts";
import lodash from 'lodash'
import { computed, reactive } from 'vue'
import { ElCollapse, ElCollapseItem } from 'element-plus'
import DataForm from '@/components/data-form/DataForm.vue'
import type { PropertyFlattenSetter, PropertySetterGroup } from '@/core/base/PropertyTypes.ts'
import type { DataFormProps } from '@/components/data-form/DataFormTypes.ts'
import { defDataFormProps } from '@/editor/widgets/property/PropertyPanelConstant.ts'
import Viewport, { type ViewportState } from '@/core/engine/Viewport.ts'
defineOptions({
name: 'PropertyPanel',
});
name: 'PropertyPanel'
})
// Props
interface PropertyPanelProps {
/** 待编辑数据 */
data?: any;
/** Viewport */
viewport: Viewport;
/** ViewportState */
viewportState: ViewportState;
/** 默认的DataFormProps */
defDataFormProps?: DataFormProps;
/** 最上面平铺的设置器 */
flatten?: PropertyFlattenSetter;
/** 设置器分组集合 */
groups?: Array<PropertySetterGroup>;
/** 待编辑数据 */
data?: any;
/** Viewport */
viewport: Viewport;
/** ViewportState */
viewportState: ViewportState;
/** 默认的DataFormProps */
defDataFormProps?: DataFormProps;
/** 最上面平铺的设置器 */
flatten?: PropertyFlattenSetter;
/** 设置器分组集合 */
groups?: Array<PropertySetterGroup>;
}
// props
const props = withDefaults(defineProps<PropertyPanelProps>(), {});
const props = withDefaults(defineProps<PropertyPanelProps>(), {})
// State
interface PropertyPanelState {
/** 待编辑数据 */
data?: any;
/** 已展开的分组 */
expandGroups: Array<string>;
/** 待编辑数据 */
data?: any;
/** 已展开的分组 */
expandGroups: Array<string>;
}
// state
const state = reactive<PropertyPanelState>({
data: lodash.cloneDeep(props.data),
expandGroups: [],
});
data: lodash.cloneDeep(props.data),
expandGroups: []
})
// Data
interface PropertyPanelData {
}
//
const data: PropertyPanelData = {};
const flattenFormProps = computed(() => getDefFormProps(props.flatten));
const data: PropertyPanelData = {}
const flattenFormProps = computed(() => getDefFormProps(props.flatten))
function getDefFormProps(setter?: PropertyFlattenSetter) {
const formProps: DataFormProps = {
...defDataFormProps,
...props.defDataFormProps,
};
fillFormProps(setter);
return formProps;
const formProps: DataFormProps = {
...defDataFormProps,
...props.defDataFormProps
}
fillFormProps(setter)
return formProps
}
function fillFormProps(formProps: DataFormProps, setter?: PropertyFlattenSetter) {
if (!setter) return;
if (setter.size) formProps.size = setter.size;
if (setter.labelWidth) formProps.labelWidth = setter.labelWidth;
if (!setter) return
if (setter.size) formProps.size = setter.size
if (setter.labelWidth) formProps.labelWidth = setter.labelWidth
}
function getCollapseItemId(group: PropertySetterGroup, idx: number) {
return `_${idx}_${group.title}`;
return `_${idx}_${group.title}`
}
function onDataChange(newData: any) {
const viewport = props.viewport;
if (!viewport) return;
const data = _.find(viewport.stateManager.vdata.items, item => item.id === props.data.id);
viewport.stateManager.beginStateUpdate();
lodash.merge(data, newData);
console.log("onDataChange@1", JSON.stringify(data.tf));
viewport.stateManager.endStateUpdate();
// data = _.find(viewport.stateManager.vdata.items, item => item.id === props.data.id);
// console.log("onDataChange#2", JSON.stringify(data.tf));
const viewport = props.viewport
if (!viewport) return
viewport.stateManager.update(({ getEntity, putEntity }) => {
const data = getEntity(props.data.id)
lodash.merge(data, newData)
putEntity(data)
console.log('onDataChange@1', JSON.stringify(data.tf))
})
// data = _.find(viewport.stateManager.vdata.items, item => item.id === props.data.id);
// console.log("onDataChange#2", JSON.stringify(data.tf));
}
interface PropertyPanelExpose {
state: PropertyPanelState;
data: PropertyPanelData;
state: PropertyPanelState;
data: PropertyPanelData;
}
const expose: PropertyPanelExpose = {
state,
data,
};
state,
data
}
//
defineExpose(expose);
defineExpose(expose)
export type {
PropertyPanelProps,
PropertyPanelState,
PropertyPanelProps,
PropertyPanelState
}
</script>
<template>
<div class="property-panel">
<div class="property-panel">
<DataForm
v-if="props.flatten"
class="property-panel-form"
v-bind="flattenFormProps"
:data="state.data"
:formFields="props.flatten.fields"
@dataChange="onDataChange"
/>
<ElCollapse v-if="props.groups" v-model="state.expandGroups">
<ElCollapseItem
v-for="(group, idx) in props.groups"
:name="getCollapseItemId(group, idx)"
:title="group.title"
>
<DataForm
v-if="props.flatten"
class="property-panel-form"
v-bind="flattenFormProps"
:data="state.data"
:formFields="props.flatten.fields"
@dataChange="onDataChange"
v-if="group"
class="property-panel-form"
v-bind="getDefFormProps(group)"
:data="state.data"
:formFields="props.flatten.fields"
@dataChange="onDataChange"
/>
<ElCollapse v-if="props.groups" v-model="state.expandGroups">
<ElCollapseItem
v-for="(group, idx) in props.groups"
:name="getCollapseItemId(group, idx)"
:title="group.title"
>
<DataForm
v-if="group"
class="property-panel-form"
v-bind="getDefFormProps(group)"
:data="state.data"
:formFields="props.flatten.fields"
@dataChange="onDataChange"
/>
</ElCollapseItem>
</ElCollapse>
</div>
</ElCollapseItem>
</ElCollapse>
</div>
</template>
<style scoped>
.property-panel {
padding: 8px 12px 16px 4px;
padding: 8px 12px 16px 4px;
}
</style>

264
src/editor/widgets/property/PropertyView.vue

@ -1,166 +1,158 @@
<template>
<div class="title">
<template v-if="!!t">
属性
<el-tag type="primary">{{ t }}</el-tag>
<el-input v-model="searchKeyword" size="small" style="width: 240px" placeholder="Search">
<template #prefix>
<component :is="renderIcon('element Search')"></component>
</template>
</el-input>
<span class="close" @click="closeMe()">
<component :is="renderIcon('element Close')"/>
</span>
<div class="title">
<template v-if="!!t">
属性
<el-tag type="primary">{{ t }}</el-tag>
<el-input v-model="searchKeyword" size="small" style="width: 240px" placeholder="Search">
<template #prefix>
<component :is="renderIcon('element Search')"></component>
</template>
</div>
<div class="calc-right-panel">
<el-empty v-if="!t" description="未选中"/>
<PropertyPanel
v-else-if="propertyPanelProps"
v-bind="propertyPanelProps"
/>
<el-empty v-else description="节点未配置设置器"/>
</div>
</el-input>
<span class="close" @click="closeMe()">
<component :is="renderIcon('element Close')" />
</span>
</template>
</div>
<div class="calc-right-panel">
<el-empty v-if="!t" description="未选中" />
<PropertyPanel
v-else-if="propertyPanelProps"
v-bind="propertyPanelProps"
/>
<el-empty v-else description="节点未配置设置器" />
</div>
</template>
<script>
import IWidgets from '../IWidgets.js'
import PropertyPanel from "@/editor/widgets/property/PropertyPanel.vue";
import PropertyPanel from '@/editor/widgets/property/PropertyPanel.vue'
export default {
name: 'PropertyView',
components: {
PropertyPanel,
},
mixins: [IWidgets],
data() {
return {
searchKeyword: '',
propertySetter: undefined,
}
},
computed: {
t() {
return this.selectedItem ? this.selectedItem.t : ''
},
selectedItem() {
return this.state?.selectedItem
},
// selectedObject() {
// return this.state?.selectedObject
// },
// selectedObjectSetter() {
// return this.state?.selectedObjectSetter
// },
propertyPanelProps() {
const state = this.state;
if (!state) return;
const { selectedObjectSetter, selectedItem } = state;
if (!selectedObjectSetter || !selectedItem) return;
const data = _.find(this.viewport.stateManager.vdata.items, item => item.id === selectedItem.id);
if (!data) return;
return {
key: data.id,
data: data,
viewport: this.viewport,
viewportState: state,
flatten: selectedObjectSetter.flatten,
groups: selectedObjectSetter.groups,
};
},
},
watch: {
// selectedItem(newV, oldV) {
// console.log("selectedItem", arguments)
// },
// selectedObject(newV, oldV) {
// console.log("selectedObject", arguments)
// },
},
methods: {
selectedObjectChanged(state) {
const data = state.selectedItem
console.log('selectedObjectChanged data', data)
if (data) {
this.viewport.stateManager.beginStateUpdate()
const item = _.find(this.viewport.stateManager.vdata.items, item => item.id === data.id)
// item.tf[0][0] = item.tf[0][0] / 2;
console.log('selectedObjectChanged item', item)
// _.extend(item, data)
this.viewport.stateManager.endStateUpdate()
}
}
name: 'PropertyView',
components: {
PropertyPanel
},
mixins: [IWidgets],
data() {
return {
searchKeyword: '',
propertySetter: undefined
}
},
computed: {
t() {
return this.selectedItem ? this.selectedItem.t : ''
},
mounted() {
// EventBus.on('selectedObjectChanged', this.selectedObjectChanged)
selectedItem() {
return this.state?.selectedItem
},
unmounted() {
// EventBus.off('selectedObjectChanged', this.selectedObjectChanged)
// selectedObject() {
// return this.state?.selectedObject
// },
// selectedObjectSetter() {
// return this.state?.selectedObjectSetter
// },
propertyPanelProps() {
const state = this.state
if (!state) return
const { selectedObjectSetter, selectedItem } = state
if (!selectedObjectSetter || !selectedItem) return
const data = _.find(this.viewport.stateManager.vdata.items, item => item.id === selectedItem.id)
if (!data) return
return {
key: data.id,
data: data,
viewport: this.viewport,
viewportState: state,
flatten: selectedObjectSetter.flatten,
groups: selectedObjectSetter.groups
}
}
},
watch: {
// selectedItem(newV, oldV) {
// console.log("selectedItem", arguments)
// },
// selectedObject(newV, oldV) {
// console.log("selectedObject", arguments)
// },
},
methods: {
selectedObjectChanged(state) {
const data = state.selectedItem
console.log('selectedObjectChanged data', data)
}
},
mounted() {
// EventBus.on('selectedObjectChanged', this.selectedObjectChanged)
},
unmounted() {
// EventBus.off('selectedObjectChanged', this.selectedObjectChanged)
}
}
</script>
<style lang="less">
.property-panel-form {
margin: 0;
font-size: 14px;
color: #606266;
margin: 0;
font-size: 14px;
color: #606266;
.el-form-item--default {
margin: 5px 3px 0 0;
.el-form-item--default {
margin: 5px 3px 0 0;
.el-form-item__label {
height: 20px;
line-height: 22px;
}
.el-form-item__label {
height: 20px;
line-height: 22px;
}
}
.gui-toolbar {
color: #333;
background: #ffffff;
border-top: 1px solid #dcdcdc;
margin-top: 5px;
.gui-toolbar {
color: #333;
background: #ffffff;
border-top: 1px solid #dcdcdc;
margin-top: 5px;
.el-input-number.is-without-controls .el-input__wrapper {
padding-left: 2px;
padding-right: 2px;
}
.el-input-number.is-without-controls .el-input__wrapper {
padding-left: 2px;
padding-right: 2px;
}
.gui-row {
display: flex;
flex-direction: row;
gap: 3px;
padding: 3px 3px 3px 0;
.gui-row {
display: flex;
flex-direction: row;
gap: 3px;
padding: 3px 3px 3px 0;
.gui-item-name {
width: 26px;
align-self: stretch;
display: flex;
align-items: center;
justify-content: center;
.gui-item-name {
width: 26px;
align-self: stretch;
display: flex;
align-items: center;
justify-content: center;
.el-icon {
font-size: 16px;
}
}
.el-icon {
font-size: 16px;
}
}
.gui-item {
flex: 1;
text-align: center;
font-size: 12px;
.gui-item {
flex: 1;
text-align: center;
font-size: 12px;
.el-input-number {
width: 100%;
.el-input-number {
width: 100%;
.el-input__wrapper {
background-color: #efefef;
box-shadow: none;
}
}
}
.el-input__wrapper {
background-color: #efefef;
box-shadow: none;
}
}
}
}
}
.el-divider {
margin: 5px 0;
}
.el-divider {
margin: 5px 0;
}
}
</style>

Loading…
Cancel
Save