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

347
src/core/ModelUtils.ts

@ -137,50 +137,47 @@ export function deletePointByKeyboard() {
return return
} }
// 删除当前选中的实体
const entityId = viewport.state.selectedEntityId const entityId = viewport.state.selectedEntityId
if (!entityId) { if (!entityId) {
// 删除多选实体
const multiSelectedEntityIds = viewport.state.multiSelectedEntityIds const multiSelectedEntityIds = viewport.state.multiSelectedEntityIds
if (!multiSelectedEntityIds && multiSelectedEntityIds.length === 0) { if (!multiSelectedEntityIds && multiSelectedEntityIds.length === 0) {
system.msg('请选中要删除的实体', 'error') system.msg('请选中要删除的实体', 'error')
return return
} }
const stateManager = viewport.stateManager let deleteCount = 0
stateManager.beginStateUpdate() viewport.stateManager.update(({ getEntity, putEntity, deleteEntity }) => {
const deleteItems = _.remove(stateManager.vdata.items, (item) => multiSelectedEntityIds.includes(item.id)) for (const entityId of multiSelectedEntityIds) {
stateManager.endStateUpdate() deleteEntity(entityId) && deleteCount++
if (deleteItems.length === 0) { }
})
if (deleteCount === 0) {
system.msg('没有找到要删除的实体', 'error') system.msg('没有找到要删除的实体', 'error')
} else { } else {
system.msg('删除了 ' + deleteItems.length + ' 个实体') system.msg('删除了 ' + deleteCount + ' 个实体')
} }
for (const deleteEntityId of multiSelectedEntityIds) { for (const deleteEntityId of multiSelectedEntityIds) {
EventBus.dispatch('entityDeleted', { EventBus.dispatch('entityDeleted', {
deleteEntityId: deleteEntityId deleteEntityId: deleteEntityId
}) })
viewport.selectInspect.clearRedSelectionBoxes()
} }
viewport.selectInspect.cancelMultiSelect()
return return
} }
const stateManager = viewport.stateManager viewport.stateManager.update(({ getEntity, putEntity, deleteEntity }) => {
viewport.stateManager.beginStateUpdate() // 删除实体
_.remove(stateManager.vdata.items, (item) => item.id === entityId) if (deleteEntity(entityId)) {
viewport.stateManager.endStateUpdate() system.msg('删除实体 [' + entityId + '] 成功')
EventBus.dispatch('entityDeleted', {
if (viewport.state.selectedEntityId === entityId) { deleteEntityId: entityId
viewport.state.selectedObject = undefined })
viewport.state.selectedItem = undefined viewport.selectInspect.cancelSelect()
viewport.state.selectedEntityId = undefined }
viewport.state.selectedObjectSetter = undefined
}
EventBus.dispatch('entityDeleted', {
deleteEntityId: entityId
}) })
system.msg('删除 [' + entityId + ']')
viewport.selectInspect.clearSelectionBox()
} }
export function escByKeyboard() { export function escByKeyboard() {
@ -210,38 +207,6 @@ export function escByKeyboard() {
system.msg('操作已取消') 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: '↑' | '↓' | '←' | '→') { export function moveSelectedItem(direct: '↑' | '↓' | '←' | '→') {
// 获取当前是否按住了 Shift // 获取当前是否按住了 Shift
@ -266,14 +231,11 @@ export function moveSelectedItem(direct: '↑' | '↓' | '←' | '→') {
} }
// 群体移动 // 群体移动
const stateManager = viewport.stateManager viewport.stateManager.update(({ getEntity, putEntity, deleteEntity }) => {
stateManager.beginStateUpdate() for (const id of multiSelectedEntityIds) {
for (const item of stateManager.vdata.items) { const item = getEntity(id)
if (multiSelectedEntityIds.includes(item.id)) {
// 根据方向移动
switch (direct) { switch (direct) {
case '↑': case '↑':
console.log('向上移动', item.tf[0][2], '-=', delta)
item.tf[0][2] -= delta // 向上移动 item.tf[0][2] -= delta // 向上移动
break break
case '↓': case '↓':
@ -286,9 +248,9 @@ export function moveSelectedItem(direct: '↑' | '↓' | '←' | '→') {
item.tf[0][0] += delta // 向右移动 item.tf[0][0] += delta // 向右移动
break break
} }
putEntity(item)
} }
} })
stateManager.endStateUpdate()
EventBus.dispatch('multiselectedObjectChanged', { EventBus.dispatch('multiselectedObjectChanged', {
multiSelectedObjects: viewport.state.multiSelectedObjects multiSelectedObjects: viewport.state.multiSelectedObjects
}) })
@ -296,28 +258,30 @@ export function moveSelectedItem(direct: '↑' | '↓' | '←' | '→') {
return return
} }
const stateManager = viewport.stateManager viewport.stateManager.update(({ getEntity, putEntity, deleteEntity }) => {
viewport.stateManager.beginStateUpdate() const item = getEntity(entityId)
for (const item of stateManager.vdata.items) { if (!item) {
if (item.id === entityId) { system.msg('没有找到选中的实体', 'error')
// 根据方向移动 return
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.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', { EventBus.dispatch('multiselectedObjectChanged', {
multiSelectedObjects: viewport.state.multiSelectedObjects multiSelectedObjects: viewport.state.multiSelectedObjects
}) })
@ -336,115 +300,18 @@ export function quickCopyByMouse() {
const x = CurrentMouseInfo.x const x = CurrentMouseInfo.x
const z = CurrentMouseInfo.z const z = CurrentMouseInfo.z
const viewport: Viewport = CurrentMouseInfo.viewport 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, 如果有,则以这个点为起点, 延伸同类型的点,并让他们相连 // 如果不在线上,查找1米内的有效点 Object3D, 如果有,则以这个点为起点, 延伸同类型的点,并让他们相连
const items = findStateItemsByDistance(viewport, new Vector2(x, z), 1) const items = viewport.stateManager.findStateItemsByDistance(new Vector2(x, z), 1)
if (items[0]) { if (items[0]) {
// 找到一个有效点,执行复制操作 // 找到一个有效点,执行复制操作
viewport.interactionManager.startInteraction(items[0].t, { startPoint: items[0].id }) viewport.interactionManager.startInteraction(items[0].t, { startPoint: items[0].id })
return
}
// const r = findObject3DByCondition(viewport.scene, object => { } else {
// // 判断 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) {
system.msg('鼠标所在位置,没有可复制的对象', 'error') 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 return foundObjects
} }
export function loadSceneFromJson(viewport: Viewport, scene: THREE.Scene, items: ItemJson[]) { // export function loadSceneFromJson(viewport: Viewport, scene: THREE.Scene, items: ItemJson[]) {
console.time('loadSceneFromJson') // console.time('loadSceneFromJson')
//
const object3ds: THREE.Object3D[] = [] // const object3ds: THREE.Object3D[] = []
//
// beforeLoad 通知所有加载的对象, 模型加载开始 // // beforeLoad 通知所有加载的对象, 模型加载开始
getAllItemTypes().forEach((itemType: ItemTypeDefineOption) => { // getAllItemTypes().forEach((itemType: ItemTypeDefineOption) => {
const ret = itemType.clazz.beforeLoad() // const ret = itemType.clazz.beforeLoad()
Array.isArray(ret) && object3ds.push(...ret) // Array.isArray(ret) && object3ds.push(...ret)
}) // })
//
const loads = loadObject3DFromJson(items) // const loads = loadObject3DFromJson(items)
Array.isArray(loads) && object3ds.push(...loads) // Array.isArray(loads) && object3ds.push(...loads)
//
// afterLoadComplete 通知所有加载的对象, 模型加载完成 // // afterLoadComplete 通知所有加载的对象, 模型加载完成
getAllItemTypes().forEach((itemType: ItemTypeDefineOption) => { // getAllItemTypes().forEach((itemType: ItemTypeDefineOption) => {
const ret = itemType.clazz.afterLoadComplete(object3ds) // const ret = itemType.clazz.afterLoadComplete(object3ds)
Array.isArray(ret) && object3ds.push(...ret) // Array.isArray(ret) && object3ds.push(...ret)
}) // })
//
scene.add(...object3ds) // scene.add(...object3ds)
//
// afterAddScene 通知所有加载的对象, 模型加载完成 // // afterAddScene 通知所有加载的对象, 模型加载完成
getAllItemTypes().forEach(itemType => { // getAllItemTypes().forEach(itemType => {
itemType.clazz.afterAddScene(viewport, scene, object3ds) // itemType.clazz.afterAddScene(viewport, scene, object3ds)
}) // })
//
console.log('loadSceneFromJson:', items.length, 'items,', object3ds.length, 'objects') // console.log('loadSceneFromJson:', items.length, 'items,', object3ds.length, 'objects')
console.timeEnd('loadSceneFromJson') // console.timeEnd('loadSceneFromJson')
} // }
//
function loadObject3DFromJson(items: ItemJson[]): THREE.Object3D[] { // function loadObject3DFromJson(items: ItemJson[]): THREE.Object3D[] {
const result: THREE.Object3D[] = [] // const result: THREE.Object3D[] = []
//
for (const item of items) { // for (const item of items) {
if (!item || !item.t) { // if (!item || !item.t) {
console.error('unkown item:', item) // console.error('unkown item:', item)
continue // continue
} // }
//
const object3D: THREE.Object3D | undefined = getItemTypeByName(item.t)?.clazz.loadFromJson(item) // const object3D: THREE.Object3D | undefined = getItemTypeByName(item.t)?.clazz.loadFromJson(item)
if (object3D === undefined) { // if (object3D === undefined) {
continue // continue
} // }
//
if (_.isArray(item.items)) { // if (_.isArray(item.items)) {
// 如果有子元素,递归处理 // // 如果有子元素,递归处理
const children = loadObject3DFromJson(item.items) // const children = loadObject3DFromJson(item.items)
children.forEach(child => object3D.add(child)) // children.forEach(child => object3D.add(child))
} // }
//
result.push(object3D) // result.push(object3D)
} // }
//
return result // return result
} // }
/** /**
* *

110
src/core/base/BaseInteraction.ts

@ -42,10 +42,6 @@ export default abstract class BaseInteraction {
linkStartPointId: string linkStartPointId: string
linkStartPointObject: THREE.Object3D linkStartPointObject: THREE.Object3D
dragOption: DragOption | undefined
dragOriginPosition: THREE.Vector3 | undefined
dragItem: ItemJson | undefined
templineMaterial = new LineMaterial({ templineMaterial = new LineMaterial({
color: 0xE63C17, // 主颜色 color: 0xE63C17, // 主颜色
linewidth: 2, // 实际可用的线宽 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 = {}) { start(viewport: Viewport, option: InteractionOption = {}) {
@ -318,12 +264,11 @@ export default abstract class BaseInteraction {
} }
// 则添加一个新的点 // 则添加一个新的点
const stateManager = this.viewport.stateManager this.viewport.stateManager.update(({ getEntity, putEntity, deleteEntity, addEntity }) => {
stateManager.beginStateUpdate({ createFromInteraction: true }) catchPoint = {} as ItemJson
catchPoint = {} as ItemJson catchPoint = this.createPointOfItem(catchPoint, point)
catchPoint = this.createPointOfItem(catchPoint, point) addEntity(catchPoint)
stateManager.vdata.items.push(catchPoint) })
stateManager.endStateUpdate()
return return
} }
@ -343,19 +288,17 @@ export default abstract class BaseInteraction {
system.msg('Cannot link to itself.') system.msg('Cannot link to itself.')
return 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) catchPoint.dt.center.push(this.linkStartPointId)
from.dt.center.push(catchPoint.id) putEntity(catchPoint)
}
// 提交状态管理器 from = getEntity(from.id)
const stateManager = this.viewport.stateManager from.dt.center.push(catchPoint.id)
stateManager.beginStateUpdate({ createFromInteraction: true }) putEntity(from)
catchPoint.dt.center.push(this.linkStartPointId) })
from.dt.center.push(catchPoint.id)
stateManager.endStateUpdate()
} else { } else {
// 添加正式点 // 添加正式点
@ -363,18 +306,19 @@ export default abstract class BaseInteraction {
catchPoint = this.createPointOfItem(catchPoint, point) catchPoint = this.createPointOfItem(catchPoint, point)
// 提交状态管理器 // 提交状态管理器
const stateManager = this.viewport.stateManager this.viewport.stateManager.update(({ getEntity, putEntity, deleteEntity, addEntity }) => {
stateManager.beginStateUpdate({ createFromInteraction: true }) if (from) {
// 关联2个点 catchPoint.dt.center.push(from.id)
stateManager.vdata.items.push(catchPoint) addEntity(catchPoint)
if (from) {
catchPoint.dt.center.push(this.linkStartPointId) from = getEntity(from.id)
from.dt.center.push(catchPoint.id) from.dt.center.push(catchPoint.id)
putEntity(from)
} else {
stateManager.vdata.items.push(catchPoint) } else {
} addEntity(catchPoint)
stateManager.endStateUpdate() }
})
} }
// 把点加入拖拽控制器 // 把点加入拖拽控制器
@ -442,4 +386,4 @@ export default abstract class BaseInteraction {
export interface DragOption { export interface DragOption {
object: THREE.Object3D object: THREE.Object3D
entityId: string 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 => { private dragComplete = (startPos: THREE.Vector2, targetPos: THREE.Vector2): void => {
// console.log(`Drag completed from ${startPos.toArray()} to ${targetPos.toArray()}`) // console.log(`Drag completed from ${startPos.toArray()} to ${targetPos.toArray()}`)
this.viewport.stateManager.beginStateUpdate()
for (const object of this.dragShadowsGroup.children) { this.viewport.stateManager.update(({ getEntity, putEntity, deleteEntity, addEntity }) => {
const entityId = object.userData.entityId for (const object of this.dragShadowsGroup.children) {
if (entityId) { const entityId = object.userData.entityId
const entity = this.viewport.stateManager.findItemById(entityId) if (entityId) {
if (entity) { const entity = getEntity(entityId)
// 更新实体位置 // 更新实体位置
entity.tf[0][0] = object.position.x entity.tf[0][0] = object.position.x
entity.tf[0][2] = object.position.z entity.tf[0][2] = object.position.z
putEntity(entity)
} else { } 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', { EventBus.dispatch('multiselectedObjectChanged', {
multiSelectedObjects: this.viewport.state.multiSelectedObjects 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 type EntityManager from './EntityManager'
import { markRaw, reactive, ref } from 'vue' import { markRaw, reactive, ref } from 'vue'
import type Viewport from '@/core/engine/Viewport.ts' 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 { ensureEntityRelationsConsistency } from '@/core/ModelUtils.ts'
import EventBus from '@/runtime/EventBus.ts' import EventBus from '@/runtime/EventBus.ts'
import { Vector2 } from 'three/src/math/Vector2'
// 差异类型定义 // 差异类型定义
interface DataDiff { interface DataDiff {
@ -28,38 +29,20 @@ interface HistoryStep {
* *
* 1. , * 1. ,
* 2. * 2.
* 3. * 3. update()
* - 1. beginStateUpdate * this.viewport.stateManager.update(({ getEntity, putEntity, deleteEntity, addEntity }) => {
* - 2. vdata * const entity = getEntity(id) // 获取实体
* - 3. endStateUpdate * entity.abc = 123
* putEntity(entity) // 提交修改
*
* deleteEntity(id) // 删除实体
* addEntity(newEntity) // 添加新实体
* })
* 4. syncDataState() vdata EntityManager * 4. syncDataState() vdata EntityManager
* 5. isLoading = true * 5. isLoading = true
* *
* : * :
* - 10000 , * - 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 { 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 id , key
* @param viewport , * @param viewport ,
* @param maxHistorySteps 20 * @param maxHistorySteps 20
@ -125,40 +103,12 @@ export default class StateManager {
this.entityManager = viewport.entityManager this.entityManager = viewport.entityManager
this.maxHistorySteps = maxHistorySteps this.maxHistorySteps = maxHistorySteps
// this.historySteps = Array.from({ length: this.maxHistorySteps }, () => null)
this.historySteps = [] this.historySteps = []
this.pendingChanges = false this.pendingChanges = false
this.isAutoSavingPaused = 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 { private invertDiff(diff: DataDiff): DataDiff {
return { 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
* - entityManager.beginEntityUpdate() * - entityManager.beginEntityUpdate()
@ -267,10 +156,10 @@ export default class StateManager {
// 获取被改过的数据, 覆盖之前的数据 // 获取被改过的数据, 覆盖之前的数据
const writeBackMap = this.entityManager.cloneWriteBackEntities() const writeBackMap = this.entityManager.cloneWriteBackEntities()
for (let i = 0; i < this.vdata.items.length; i++) { for (let i = 0; i < this.___vdata.items.length; i++) {
const item = this.vdata.items[i] const item = this.___vdata.items[i]
if (writeBackMap.has(item.id)) { 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() this.startAutoSave()
} }
startAutoSave() { // 差异应用方法
this.isAutoSavingPaused = false private applyDiff(diff: DataDiff) {
const { added, removed, updated } = diff
if (this.autoSaveTimer !== null) {
window.clearTimeout(this.autoSaveTimer)
this.autoSaveTimer = null
}
// 删除
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) { const updateMap = new Map(updated.map(u => [u.after.id, u.after]))
window.clearTimeout(this.autoSaveTimer) for (const [key, after] of updateMap) {
this.autoSaveTimer = null 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.entityManager.beginEntityUpdate()
this.autoSaveTimer = null this.___vdata.items.forEach(item => {
this.entityManager.createOrUpdateEntity(item)
})
this.entityManager.endEntityUpdate()
if (this.isUpdating) { // 初始状态作为第一步
const checkInterval = setInterval(() => { this.pendingChanges = false
if (!this.isUpdating) {
clearInterval(checkInterval)
if (!this.isAutoSavingPaused) {
this.queueAutoSave()
}
} else {
console.log('wait for entityManager to finish updating...')
}
}, 200)
return
}
await this.saveToLocalstore() if (isAutoSave) {
}, 2500) 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 //@ts-ignore
this.vdata = { this.___vdata = {
id: this.id, id: this.id,
isChanged: false, isChanged: false,
...data ...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))
// 新增 startAutoSave() {
const addedIds = new Set(added.map(i => i.id)) this.isAutoSavingPaused = false
_.remove(this.vdata.items, item => addedIds.has(item.id))
this.vdata.items.push(...added)
// 更新 if (this.autoSaveTimer !== null) {
const updateMap = new Map(updated.map(u => [u.after.id, u.after])) window.clearTimeout(this.autoSaveTimer)
for (const [key, after] of updateMap) { this.autoSaveTimer = null
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)
}
} }
}
/**
*
* @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()
// 初始状态作为第一步 // 设置新的定时器,在 5 秒后尝试保存
this.beginStateUpdate() this.queueAutoSave()
this.endStateUpdate({ autoSave: false })
this.pendingChanges = false
} }
undoEnabled() { stopAutoSave() {
return this.historyIndex >= 0 if (this.autoSaveTimer !== null) {
window.clearTimeout(this.autoSaveTimer)
this.autoSaveTimer = null
}
this.isAutoSavingPaused = true
} }
redoEnabled() { private queueAutoSave() {
const nextIndex = (this.historyIndex + 1) % this.maxHistorySteps if (this.autoSaveTimer !== null || this.isAutoSavingPaused) return
return !!this.historySteps[nextIndex] 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> { async save(): Promise<Object> {
return _.cloneDeep(this.vdata) return _.cloneDeep(this.___vdata)
} }
/** /**
* indexDb() * indexDb()
*/ */
async saveToLocalstore() { async saveToLocalstore() {
await localforage.setItem(this.storeKey, this.vdata) await localforage.setItem(this.storeKey, this.___vdata)
console.log('[StateManager] 数据已保存草稿:' + this.storeKey + ', 共', this.vdata.items.length, '个对象') console.log('[StateManager] 数据已保存草稿:' + this.storeKey + ', 共', this.___vdata.items.length, '个对象')
} }
/** /**
@ -503,13 +395,13 @@ export default class StateManager {
this.isLoading.value = true this.isLoading.value = true
const saved: VData = await localforage.getItem(this.storeKey) const saved: VData = await localforage.getItem(this.storeKey)
if (saved) { if (saved) {
this.vdata = { this.___vdata = {
...saved ...saved
} }
this.isChanged.value = saved.isChanged || false this.isChanged.value = saved.isChanged || false
this.fullSync() // 同步到视口 this.fullSync(false) // 同步到视口
console.log('[StateManager] 从本地存储恢复', this.vdata.items.length, '个对象') console.log('[StateManager] 从本地存储恢复', this.___vdata.items.length, '个对象')
return true return true
} }
@ -541,7 +433,7 @@ export default class StateManager {
*/ */
dispose() { dispose() {
// 清理引用 // 清理引用
delete this.vdata delete this.___vdata
delete this.historySteps delete this.historySteps
} }
@ -582,23 +474,151 @@ export default class StateManager {
* ID * ID
*/ */
findItemById(linkStartPointId: string): ItemJson | undefined { 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 { 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 (item.tf?.[0]?.[0] === point.x && item.tf?.[0]?.[2] === point.z) {
if (!itemTypeName || item.t === itemTypeName) { 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 { export interface StateUpdateOption {
autoSave?: boolean
createFromInteraction?: boolean createFromInteraction?: boolean
} }

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

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

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

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

Loading…
Cancel
Save