Browse Source

Merge remote-tracking branch 'origin/master'

master
yuliang 7 months ago
parent
commit
14ce0d6ecf
  1. 214
      src/components/data-form/DataForm.vue
  2. 26
      src/components/data-form/DataFormConstant.ts
  3. 2
      src/components/data-form/DataFormTypes.ts
  4. 23
      src/core/ModelUtils.ts
  5. 164
      src/core/controls/SelectInspect.ts
  6. 34
      src/core/engine/Viewport.ts
  7. 21
      src/core/manager/EntityManager.ts
  8. 2
      src/editor/widgets/property/PropertyView.vue
  9. 2
      src/modules/way/WayRenderer.ts
  10. 416
      src/pages/DataForm01.vue
  11. 3
      src/runtime/EventBus.ts

214
src/components/data-form/DataForm.vue

@ -1,13 +1,16 @@
<script setup lang="ts">
import lodash from "lodash";
import { computed, getCurrentInstance, onMounted, reactive, watch } from "vue";
import { computed, getCurrentInstance, nextTick, onMounted, reactive, watch } from "vue";
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
import { Expression, Typeof } from "@ease-forge/shared";
import { ElForm, ElFormItem, ElLoadingDirective } from "element-plus";
import { Expression, Request, Typeof, Utils } from "@ease-forge/shared";
import { calcBreakpointValue, toVNode } from "@/utils/Utils.ts";
import { type DataFormData, type DataFormItem, type DataFormProps, type DataFormState, type FormData, type FormField } from "./DataFormTypes.ts";
import { type DataFormData, type DataFormItem, type DataFormProps, type DataFormState, type FormData, type FormField, type WatchFormFieldValue } from "./DataFormTypes.ts";
import { dataFormInputComponents } from "./DataFormConstant.ts";
import { dataPathToNamePath } from "./DataFormUtils.tsx";
const vLoading = ElLoadingDirective;
defineOptions({
name: 'DataForm',
});
@ -68,9 +71,9 @@ if (Typeof.isObj(props.columnCount)) {
watch(breakpoints.current(), current => state.columnCount = calcColumnCount(props.columnCount, current), { immediate: true });
}
//
// if (props.autoLoadData && props.dataApi) reload().finally();
if (props.autoLoadData && props.dataApi) reload().finally();
// data
// watch(() => state.data, newData => dataChange(state.dataFormItems, newData), { immediate: true, deep: true });
watch(() => state.data, newData => dataChange(state.dataFormItems, newData), { immediate: true, deep: true });
// loading
watch(() => state.loading, loading => emit("loadingChange", loading));
// ctxData()
@ -240,6 +243,46 @@ function getFormRows(dataFormItems: Array<DataFormItem>, columnCount: number) {
return formRows;
}
/** 表单数据变化,执行watchValues */
function dataChange(dataFormItems: Array<DataFormItem>, newFormData?: FormData) {
// Record<dataPath, Array<{watch, watchOwnerDataFormItem}>>
const watchRecord: Record<string, Array<{ watch: WatchFormFieldValue; item: DataFormItem; }>> = {};
for (let dataFormItem of dataFormItems) {
const { watchValues } = dataFormItem;
if (!watchValues) continue;
for (let watchValue of watchValues) {
const { dataPath, onChange } = watchValue;
if (!dataPath || !onChange) continue;
if (!watchRecord[dataPath]) watchRecord[dataPath] = [];
watchRecord[dataPath].push({ watch: watchValue, item: dataFormItem });
}
}
// watchValues onChange
const watchFns: Array<Function> = [];
for (let dataPath in watchRecord) {
const watchItems = watchRecord[dataPath];
const oldValue = data.oldData[dataPath];
const newValue = Expression.getKeyPathValue(dataPath, newFormData);
const hasChange = !Utils.equalsValues(oldValue, newValue);
for (let watchItem of watchItems) {
const { watch: { onChange, immediate }, item: dataFormItem } = watchItem;
if (!onChange) continue;
const input = data.inputRefs[dataFormItem.inputRef];
const form = data.formRef;
if (immediate && data.firstDataChange) {
// immediate, onChange hasChange
watchFns.push(() => onChange(oldValue, newValue, newFormData, dataFormItem, input, form, data.ctxData));
} else if (hasChange) {
watchFns.push(() => onChange(oldValue, newValue, newFormData, dataFormItem, input, form, data.ctxData));
}
}
data.oldData[dataPath] = newValue;
}
data.firstDataChange = false;
// watchValues | nextTick, antd bug 使 nextTick
nextTick(() => watchFns.forEach(onChange => onChange()));
}
/**
* 根据响应式断点配置计算一行显示的字段数量
* @param columnCount 响应式断点配置
@ -249,6 +292,53 @@ function calcColumnCount(columnCount?: DataFormProps["columnCount"], current?: A
return Math.min(calcBreakpointValue(columnCount, current, props.defColumnCount ?? 2), 24);
}
/**
* 重新加载数据
* @param dataApi 请求配置
*/
async function reload(dataApi?: HttpRequestConfig<FormData>) {
const requestConfig: HttpRequestConfig<FormData> = lodash.defaultsDeep({}, dataApi, props.dataApi);
if (Typeof.noValue(requestConfig.url)) {
console.warn("未配置服务端数据API url");
return;
}
state.loading = true;
try {
state.data = await Request.request.request(requestConfig);
} finally {
state.loading = false;
}
}
/** 计算输入组件的值 */
function calcInputValue(dataFormItem: DataFormItem) {
const { dataPath, format } = dataFormItem;
if (!dataPath) return;
const dataValue = Expression.getKeyPathValue(dataPath, state.data);
if (!format) return dataValue;
if (Typeof.isArray(format)) {
const dictItem = window.globalConfig.matchDictItem(format, dataValue);
return dictItem?.text ?? dataValue;
} else if (Typeof.isFunction(format)) {
return format(dataValue, dataPath, state.data);
}
return dataValue;
}
/** 设置输入值 */
function setInputValue(newVal: any, dataFormItem: DataFormItem) {
const { dataPath, transform } = dataFormItem;
if (!dataPath) return;
let value = newVal;
if (Typeof.isArray(transform)) {
const dictItem = window.globalConfig.matchDictItem(transform, newVal);
value = dictItem?.text ?? newVal;
} else if (Typeof.isFunction(transform)) {
value = transform(newVal, dataPath, state.data);
}
Expression.setKeyPathValue(dataPath, state.data, value);
}
interface DataFormExpose {
state: DataFormState;
data: DataFormData;
@ -268,15 +358,121 @@ export type {
</script>
<template>
<div class="data-form">
<Loading>
</Loading>
<ElForm
:ref="($ref: any) => data.formRef = $ref"
:class="[
'data-form',
{
'data-form-label-fixed': !isBothFixed(),
'data-form-label-input-fixed': isBothFixed(),
},
]"
v-loading="state.loading"
:labelPosition="props.labelPosition"
:labelSuffix="props.labelSuffix"
:labelWidth="props.labelWidth"
:size="props.size"
:showMessage="props.showMessage"
:inlineMessage="props.inlineMessage"
:requireAsteriskPosition="props.requireAsteriskPosition"
:hideRequiredAsterisk="props.hideRequiredAsterisk"
:rules="props.rules"
:validateOnRuleChange="props.validateOnRuleChange"
:disabled="props.disabled"
:scrollToError="props.scrollToError"
:scrollIntoViewOptions="props.scrollIntoViewOptions"
v-bind="props.rawProps"
:model="state.data"
>
<div v-for="(formRow, rowIdx) in formRows" class="data-form-row">
<template v-for="(dataFormItem, celIdx) in formRow">
<div
v-if="dataFormItem.hidden"
:key="`div_${rowIdx}_${celIdx}`"
:class="[
'data-form-item',
'data-form-item-hidden',
`data-form-item-flex-${Math.min(dataFormItem.widthCount, state.columnCount)}`,
{
'multiple-width-count': Math.min(dataFormItem.widthCount, state.columnCount) > 1,
},
]"
/>
<ElFormItem
v-else
:key="`form_item_${rowIdx}_${celIdx}`"
v-bind="dataFormItem.formItemProps"
:class="[
'data-form-item',
`data-form-item-flex-${Math.min(dataFormItem.widthCount, state.columnCount)}`,
{
'multiple-width-count': Math.min(dataFormItem.widthCount, state.columnCount) > 1,
'data-form-item-has-ext-input': dataFormItem.extInputs && dataFormItem.extInputs.length > 0,
},
]"
>
<component
v-if="dataFormItem.input"
:key="dataFormItem.inputRef"
class="data-form-item-main-input"
:is="dataFormItem.input"
:ref="($ref: any) => data.inputRefs[dataFormItem.inputRef] = $ref"
v-bind="dataFormItem.inputProps"
:modelValue="calcInputValue(dataFormItem)"
@update:modelValue="(newVal: any) => setInputValue(newVal, dataFormItem)"
/>
<template v-for="(extInput, innerIdx) in dataFormItem.extInputs">
<ElFormItem
:key="`${rowIdx}_${celIdx}_${innerIdx}`"
v-if="extInput.input"
v-bind="extInput.formItemProps"
class="data-form-item-ext-input"
>
<component
v-if="extInput.input"
:key="extInput.inputRef"
:is="extInput.input"
:ref="($ref: any) => data.inputRefs[extInput.inputRef] = $ref"
v-bind="extInput.inputProps"
:modelValue="calcInputValue(extInput)"
@update:modelValue="(newVal: any) => setInputValue(newVal, extInput)"
/>
</ElFormItem>
</template>
</ElFormItem>
</template>
<div
v-if="state.columnCount - lodash.sum(formRow.map(item => item.widthCount)) > 0"
:key="`div_${rowIdx}`"
:class="[
'data-form-item',
'data-form-item-empty',
`data-form-item-flex-${state.columnCount - lodash.sum(formRow.map(item => item.widthCount))}`,
]"
/>
</div>
</ElForm>
</template>
<style scoped>
.data-form {
width: 100%;
height: 100%;
}
.data-form :deep(.el-date-editor) {
--el-date-editor-width: unset;
}
.data-form-row {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
}
.data-form-row > .data-form-item[class*=data-form-item-flex-]:not(.multiple-width-count) {
flex-shrink: 0;
}
.data-form-item.data-form-item-flex-1 {
flex: 1 1 0;

26
src/components/data-form/DataFormConstant.ts

@ -3,6 +3,7 @@ import {
ElAutocomplete,
ElCascader,
ElCheckbox,
ElCheckboxGroup,
ElColorPicker,
ElDatePicker,
ElInput,
@ -10,6 +11,7 @@ import {
ElInputTag,
ElMention,
ElRadio,
ElRadioGroup,
ElRate,
ElSelect,
ElSelectV2,
@ -25,25 +27,27 @@ import type { DisplayMode } from "./DataFormTypes.ts";
/** 内建的表单输入组件 */
const builtInInputComponents = {
Autocomplete: markRaw<any>(ElAutocomplete),
Cascader: markRaw<any>(ElCascader),
Checkbox: markRaw<any>(ElCheckbox),
ColorPicker: markRaw<any>(ElColorPicker),
DatePicker: markRaw<any>(ElDatePicker),
Input: markRaw<any>(ElInput),
InputNumber: markRaw<any>(ElInputNumber),
InputTag: markRaw<any>(ElInputTag),
Mention: markRaw<any>(ElMention),
Checkbox: markRaw<any>(ElCheckbox),
CheckboxGroup: markRaw<any>(ElCheckboxGroup),
Radio: markRaw<any>(ElRadio),
Rate: markRaw<any>(ElRate),
RadioGroup: markRaw<any>(ElRadioGroup),
Switch: markRaw<any>(ElSwitch),
Select: markRaw<any>(ElSelect),
SelectV2: markRaw<any>(ElSelectV2),
Slider: markRaw<any>(ElSlider),
Switch: markRaw<any>(ElSwitch),
DatePicker: markRaw<any>(ElDatePicker),
TimePicker: markRaw<any>(ElTimePicker),
TimeSelect: markRaw<any>(ElTimeSelect),
Transfer: markRaw<any>(ElTransfer),
Cascader: markRaw<any>(ElCascader),
TreeSelect: markRaw<any>(ElTreeSelect),
Autocomplete: markRaw<any>(ElAutocomplete),
InputTag: markRaw<any>(ElInputTag),
ColorPicker: markRaw<any>(ElColorPicker),
Slider: markRaw<any>(ElSlider),
Rate: markRaw<any>(ElRate),
Mention: markRaw<any>(ElMention),
Transfer: markRaw<any>(ElTransfer),
Upload: markRaw<any>(ElUpload),
};

2
src/components/data-form/DataFormTypes.ts

@ -49,7 +49,7 @@ interface FormInputBaseProps {
/** 占位符 */
placeholder?: string;
/** 允许清空 */
allowClear?: boolean;
clearable?: boolean;
/** 只读 */
readonly?: boolean;
/** 禁用 */

23
src/core/ModelUtils.ts

@ -141,7 +141,27 @@ export function deletePointByKeyboard() {
const entityId = viewport.state.selectedEntityId
if (!entityId) {
system.msg('没有选中任何点')
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) {
system.msg('没有找到要删除的实体', 'error')
} else {
system.msg('删除了 ' + deleteItems.length + ' 个实体')
}
for (const deleteEntityId of multiSelectedEntityIds) {
EventBus.dispatch('entityDeleted', {
deleteEntityId: deleteEntityId
})
viewport.selectInspect.clearRedSelectionBoxes()
}
return
}
@ -162,6 +182,7 @@ export function deletePointByKeyboard() {
})
system.msg('删除 [' + entityId + ']')
viewport.selectInspect.clearSelectionBox()
}
export function escByKeyboard() {

164
src/core/controls/SelectInspect.ts

@ -19,7 +19,12 @@ export default class SelectInspect implements IControls {
/**
* 线
*/
material: LineMaterial = new LineMaterial({ color: 0xffff00, linewidth: 2 })
yellowMaterial: LineMaterial = new LineMaterial({ color: 0xffff00, linewidth: 2 })
/**
* 线
*/
redMaterial: LineMaterial = new LineMaterial({ color: 0xff0000, linewidth: 2 })
/**
*
@ -76,6 +81,11 @@ export default class SelectInspect implements IControls {
this.updateSelectionBox(this.viewport.state.selectedObject)
})
EventBus.on('multiSelectedObjectsChanged', (data) => {
// 如果多选对象发生变化,清除当前选中对象的包围盒线框
this.updateMultiSelectionBoxes(data.multiSelectedObjects)
})
EventBus.on('selectedObjectPropertyChanged', (data) => {
this.updateSelectionBox(this.viewport.state.selectedObject)
})
@ -90,23 +100,87 @@ export default class SelectInspect implements IControls {
})
}
redSelectionGroup = new THREE.Group()
private updateMultiSelectionBoxes(multiSelectedObjects: THREE.Object3D[]) {
// 为所有多选对象创建包围盒线框
this.clearRedSelectionBoxes()
if (!multiSelectedObjects || multiSelectedObjects.length === 0) {
return
}
for (const object of multiSelectedObjects) {
if (object.userData.entityId) {
this.createRedSelectionBox(object)
}
}
}
clearRedSelectionBoxes() {
// 清除之前的红色包围盒线框
if (this.redSelectionGroup.children.length > 0) {
for (const child of this.redSelectionGroup.children) {
this.redSelectionGroup.remove(child)
}
}
this.viewport.scene.remove(this.redSelectionGroup)
this.redSelectionGroup = new THREE.Group()
this.viewport.scene.add(this.redSelectionGroup)
}
createRedSelectionBox(object: THREE.Object3D) {
// 如果对象没有 entityId,则不创建包围盒线框
if (!object.userData.entityId) {
return
}
// 如果选中的对象小于 0.5,要扩展包围盒
const RED_EXPAND_AMOUNT = 0.01 // 扩展包围盒的大小
// 避免某些蒙皮网格的帧延迟效应(e.g. Michelle.glb)
object.updateWorldMatrix(false, true)
const box = new THREE.Box3().setFromObject(object)
box.expandByScalar(RED_EXPAND_AMOUNT)
const size = new THREE.Vector3()
box.getSize(size)
const center = new THREE.Vector3()
box.getCenter(center)
// 创建包围盒几何体
const helperGeometry = new THREE.BoxGeometry(size.x, size.y, size.z)
const edgesGeometry = new THREE.EdgesGeometry(helperGeometry)
const lineGeom = new LineGeometry()
// @ts-ignore
lineGeom.setPositions(edgesGeometry.attributes.position.array)
const selectionBox = new Line2(lineGeom, this.redMaterial)
selectionBox.computeLineDistances()
selectionBox.position.copy(center)
this.redSelectionGroup.add(selectionBox)
}
/**
* 线
*/
updateSelectionBox(selectedObject: THREE.Object3D) {
this.disposeSelectionBox()
this.clearSelectionBox()
if (!selectedObject) {
return
}
this.selectionId = selectedObject.userData?.entityId
const expandAmount = 0.2 // 扩展包围盒的大小
// 如果选中的对象小于 0.5,要扩展包围盒
const YELLOW_EXPAND_AMOUNT = 0.03 // 扩展包围盒的大小
// 避免某些蒙皮网格的帧延迟效应(e.g. Michelle.glb)
selectedObject.updateWorldMatrix(false, true)
const box = new THREE.Box3().setFromObject(selectedObject)
box.expandByScalar(expandAmount)
box.expandByScalar(YELLOW_EXPAND_AMOUNT)
const size = new THREE.Vector3()
box.getSize(size)
@ -117,16 +191,13 @@ export default class SelectInspect implements IControls {
// 创建包围盒几何体
const helperGeometry = new THREE.BoxGeometry(size.x, size.y, size.z)
const edgesGeometry = new THREE.EdgesGeometry(helperGeometry)
// 使用 LineGeometry 包装 edgesGeometry
const lineGeom = new LineGeometry()
// @ts-ignore
lineGeom.setPositions(edgesGeometry.attributes.position.array)
const selectionBox = new Line2(lineGeom, this.material)
const selectionBox = new Line2(lineGeom, this.yellowMaterial)
selectionBox.computeLineDistances()
selectionBox.position.copy(center)
selectionBox.name = 'selectionBox'
this.selectionBox = selectionBox
console.log('selectedItem', this.viewport.state.selectedItem)
@ -143,11 +214,12 @@ export default class SelectInspect implements IControls {
puFn = undefined
// 销毁选择工具
this.disposeSelectionBox()
this.clearSelectionBox()
this.disposeRect()
this.clearRedSelectionBoxes()
}
disposeSelectionBox() {
clearSelectionBox() {
if (this.selectionBox) {
this.viewport.scene.remove(this.selectionBox)
this.selectionBox.geometry.dispose()
@ -162,7 +234,7 @@ export default class SelectInspect implements IControls {
if (this.recStartPos) {
// 创建矩形
this.rectangle = new THREE.Mesh(
new THREE.PlaneGeometry(1, 1),
new THREE.PlaneGeometry(0.001, 0.001),
this.rectMaterial
)
this.rectangle.name = 'selectRectangle'
@ -176,6 +248,7 @@ export default class SelectInspect implements IControls {
}
}
updateRectangle(position: THREE.Vector3) {
if (!this.rectangle || !this.recStartPos) return
// console.log('updateRectangle', this.recStartPos, position)
@ -201,6 +274,8 @@ export default class SelectInspect implements IControls {
// 记录鼠标按下位置
this.recStartPos = this.viewport.getClosestIntersection(event)
this.createRectangle()
this.viewport.controls.enabled = false // 禁用控制器
} else {
// 为 click 事件添加处理逻辑
this.clickTime = Date.now()
@ -220,11 +295,14 @@ export default class SelectInspect implements IControls {
disposeRect() {
if (this.rectangle !== null) {
// 查找在这个矩形内的所有有效业务对象,并将他们添加进 viewport.state.multiSelectedObjects
this.multipleSelectedObjects()
this.viewport.scene.remove(this.rectangle)
this.rectangle.geometry.dispose()
this.rectangle = null
}
this.recStartPos = null
this.viewport.controls.enabled = true // 启用控制器
}
onMouseUp(event: MouseEvent) {
@ -253,7 +331,71 @@ export default class SelectInspect implements IControls {
selectedObjectMeta: this.viewport.state.selectedObjectMeta
})
}
} else {
// 如果没有选中任何对象,清除选中状态
this.viewport.state.selectedObject = null
this.viewport.state.selectedItem = null
this.viewport.state.selectedEntityId = null
this.viewport.state.selectedObjectMeta = null
EventBus.dispatch('selectedObjectChanged', {
viewport: markRaw(this.viewport),
selectedObject: null,
selectedItem: null,
selectedEntityId: null,
selectedObjectMeta: null
})
}
}
}
private multipleSelectedObjects() {
if (!this.rectangle || !this.recStartPos) return
// 获取矩形的包围盒
const box = new THREE.Box3().setFromObject(this.rectangle)
// 获取盒子的 startX, startZ, endX, endZ
const startX = box.min.x
const startZ = box.min.z
const endX = box.max.x
const endZ = box.max.z
// 查找所有在矩形内的对象
const objects = this.viewport.entityManager.getObjectsInBox(startX, startZ, endX, endZ)
// 清空之前的多选对象
this.viewport.state.multiSelectedObjects = []
// 遍历找到的对象,添加到多选对象中
const multiSelectedObjects = []
const multiSelectedItems = []
const multiSelectedEntityIds = []
const multiSelectedObjectMetas = []
for (const object of objects) {
if (object.userData.entityId && object.userData.t) {
const item = this.viewport.entityManager.findItemById(object.userData.entityId)
if (item && item.dt.protected !== true) {
multiSelectedObjects.push(object)
multiSelectedItems.push(item)
multiSelectedEntityIds.push(object.userData.entityId)
multiSelectedObjectMetas.push(getMeta(object.userData.t))
}
}
}
// 触发多选对象更新事件
this.viewport.state.multiSelectedObjects = markRaw(objects)
this.viewport.state.multiSelectedItems = markRaw(multiSelectedItems)
this.viewport.state.multiSelectedEntityIds = multiSelectedEntityIds
this.viewport.state.multiSelectedObjectMetas = multiSelectedObjectMetas
EventBus.dispatch('multiSelectedObjectsChanged', {
viewport: markRaw(this.viewport),
multiSelectedObjects: this.viewport.state.multiSelectedObjects,
multiSelectedItems: this.viewport.state.multiSelectedItems,
multiSelectedEntityIds: this.viewport.state.multiSelectedEntityIds,
multiSelectedObjectMetas: this.viewport.state.multiSelectedObjectMetas
})
}
}

34
src/core/engine/Viewport.ts

@ -37,10 +37,12 @@ export default class Viewport {
dragControl: any // EsDragControls
animationFrameId: any = null
scene: SceneHelp
selectInspect = new SelectInspect()
mouseMoveInspect = new MouseMoveInspect()
tools: IControls[] = [
new MouseMoveInspect(),
new SelectInspect()
markRaw(this.selectInspect),
markRaw(this.mouseMoveInspect)
]
// 状态管理器
@ -66,7 +68,17 @@ export default class Viewport {
isReady: false,
isUpdating: false,
cursorMode: 'normal',
selectedObject: null,
selectedObject: undefined,
selectedItem: undefined,
selectedEntityId: undefined,
selectedObjectMeta: undefined,
multiSelectedObjects: [],
multiSelectedItems: [],
multiSelectedEntityIds: [],
multiSelectedObjectMetas: [],
view3DMode: Constract.Mode2D,
camera: {
position: { x: 0, y: 0, z: 0 },
@ -550,20 +562,22 @@ export interface ViewportState {
cursorMode: string // CursorMode,
/**
*
*
*/
selectedObject: THREE.Object3D | undefined
selectedItem: ItemJson | undefined
selectedEntityId: string | undefined
view3DMode: string // Constract.Mode2D | Constract.Mode3D
selectedObjectMeta: IMeta | undefined
/**
*
*
*/
selectedObjectMeta: IMeta | undefined
multiSelectedObjects: THREE.Object3D[]
multiSelectedItems: ItemJson[]
multiSelectedEntityIds: string[]
multiSelectedObjectMetas: IMeta[]
view3DMode: string // Constract.Mode2D | Constract.Mode3D
/**
*

21
src/core/manager/EntityManager.ts

@ -548,6 +548,27 @@ export default class EntityManager {
r => getClosestObject(r.object)
).filter(obj => obj?.userData && obj.userData.selectable !== false)
}
/**
*
*/
getObjectsInBox(startX: number, startZ: number, endX: number, endZ: number) {
const box = new THREE.Box2(
new THREE.Vector2(startX, startZ),
new THREE.Vector2(endX, endZ)
)
const objectsInBox: THREE.Object3D[] = []
for (const [id, objects] of this.objects.entries()) {
for (const obj of objects) {
if (box.containsPoint(new Vector2(obj.position.x, obj.position.z))) {
objectsInBox.push(obj)
}
}
}
return objectsInBox
}
}
interface LineDiffItem {

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

@ -93,12 +93,14 @@ export default {
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() {

2
src/modules/way/WayRenderer.ts

@ -87,7 +87,7 @@ export default class WayRenderer extends BaseRenderer {
const width = 1
const curve = new THREE.LineCurve3(startPosition, endPosition)
const tubeGeometry = new THREE.TubeGeometry(curve, 1, width / 2, 8, false)
const tubeGeometry = new THREE.TubeGeometry(curve, 1, width / 2, 2, false)
const lineMesh = new THREE.Mesh(tubeGeometry, this.lineMaterial)
group.add(lineMesh)

416
src/pages/DataForm01.vue

@ -1,11 +1,423 @@
<script setup lang="ts">
import { reactive } from "vue";
import { Format } from "@ease-forge/shared";
import DataForm, { type DataFormProps } from "@/components/data-form/DataForm.vue";
const dataForm1 = reactive<DataFormProps>({
data: {
str: "abcABC",
num_1: 123,
checkbox_1: true,
checkbox_group_1: ["v2"],
radio_1: false,
radio_group_1: "v2",
switch_1: false,
select_1: "006",
select_2: "006",
date_1: "2025-02-03",
date_2: Format.toDayjs("2025-02-03").toDate(),
time_1: "08:30:00",
time_select_1: "08:30:00",
cascader_1: ["wh"],
tree_select_1: "wh",
autocomplete: "汤磊",
tags_1: ["DEV"],
color_1: "#CA4343",
slider_1: 80,
rate_1: 3.5,
mentions_1: "abc @bj ABC",
transfer_1: ["v1", "v2"],
upload_1: [
{
name: 'image.png',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
{
uid: '-2',
name: 'image.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
{
uid: '-3',
percent: 50,
name: 'image.png',
status: 'uploading',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
{
uid: '-5',
name: 'image.png',
status: 'error',
},
],
password: "2025fff",
textarea: [
"蛇衔春色千山秀",
"龙舞祥光万户新",
"福满人间",
"属性适用于 textarea 节点,并且只有高度会自动变化。另外 autoSize 可以设定为一个对象,指定最小行数和最大行数"
].join("\n"),
},
formFields: [
{
dataPath: 'str', label: '字符串', input: 'Input',
inputProps: {
placeholder: '请输入',
clearable: true,
},
},
{
dataPath: 'num_1', label: '数字', input: 'InputNumber',
inputProps: {
placeholder: '请输入',
controlsPosition: 'right',
},
},
{
dataPath: 'checkbox_1', label: '复选', input: 'Checkbox',
inputProps: {
trueValue: true,
falseValue: false,
},
},
{
dataPath: 'checkbox_group_1', label: '复选组', input: 'CheckboxGroup',
inputProps: {
max: 2,
// TODO options
},
},
{
dataPath: 'radio_1', label: '单选', input: 'Radio',
inputProps: {
value: true,
},
},
{
dataPath: 'radio_group_1', label: '单选组', input: 'RadioGroup',
inputProps: {
// TODO options
},
},
{
dataPath: 'switch_1', label: '开关', input: 'Switch',
inputProps: {
inactiveValue: false,
activeValue: true,
inlinePrompt: true,
inactiveText: "关闭",
activeText: "开启",
},
},
{
dataPath: 'select_1', label: '选择器', input: 'Select',
inputProps: {
placeholder: "请选择",
clearable: true,
// TODO options
},
},
{
dataPath: 'select_2', label: '选择器', input: 'SelectV2',
inputProps: {
placeholder: "请选择",
clearable: true,
options: [
{ value: "001", label: "选项1" },
{ value: "002", label: "选项2" },
{ value: "003", label: "选项3" },
{ value: "004", label: "选项4" },
{ value: "005", label: "选项5" },
{ value: "006", label: "选项6" },
],
},
},
{
dataPath: 'date_1', label: '日期选择', input: 'DatePicker',
inputProps: {
placeholder: "选择时间",
type: "date",
format: "YYYY-MM-DD",
valueFormat: "YYYY-MM-DD",
},
},
{
dataPath: 'date_2', label: '日期时间选择', input: 'DatePicker',
inputProps: {
placeholder: "选择时间",
type: "datetime",
format: "YYYY-MM-DD HH:mm:ss",
// valueFormat: "YYYY-MM-DD",
},
},
{
dataPath: 'time_1', label: '时间选择', input: 'TimePicker',
inputProps: {
placeholder: "选择时间",
isRange: false,
format: "HH:mm:ss",
valueFormat: "HH:mm:ss",
},
},
{
dataPath: 'time_select_1', label: '时间选择', input: 'TimeSelect',
inputProps: {
placeholder: "选择时间",
clearable: true,
step: "00:15",
format: "HH:mm:ss",
},
},
{
dataPath: 'cascader_1', label: '级联选择', input: 'Cascader',
inputProps: {
placeholder: '请输入',
options: [
{
value: 'zj', label: '浙江',
children: [
{
value: 'hz', label: '杭州',
children: [
{ value: 'xh', label: '西湖' },
],
},
],
},
{
value: 'js', label: '江苏',
children: [
{
value: 'nj', label: '南京',
children: [
{ value: 'zhm', label: '中华门' },
],
},
],
},
{ value: 'sh', label: '上海', },
{ value: 'bj', label: '北京', },
{ value: 'sz', label: '深圳', },
{ value: 'cq', label: '重庆', },
{ value: 'gz', label: '广州', },
{ value: 'cd', label: '成都', },
{ value: 'wh', label: '武汉', },
],
},
},
{
dataPath: 'tree_select_1', label: '树形选择', input: 'TreeSelect',
inputProps: {
placeholder: '请输入',
clearable: true,
defaultExpandAll: true,
// renderAfterExpand: true,
data: [
{
value: 'zj', label: '浙江',
children: [
{
value: 'hz', label: '杭州',
children: [
{ value: 'xh', label: '西湖' },
],
},
],
},
{ value: 'wh', label: '武汉', },
{ value: 'sh', label: '上海', },
{ value: 'bj', label: '北京', },
],
},
},
{
dataPath: 'autocomplete', label: '自动补全', input: 'Autocomplete',
inputProps: {
placeholder: "自动补全",
clearable: true,
fetchSuggestions: (queryString: string, callback: Function) => {
const data = [
{ label: "1熊超", value: "熊超" },
{ label: "2姚洋", value: "姚洋" },
{ label: "3崔艳", value: "崔艳" },
{ label: "4侯芳", value: "侯芳" },
{ label: "5林敏", value: "林敏" },
{ label: "6金丽", value: "金丽" },
{ label: "7侯秀英", value: "侯秀英" },
{ label: "8刘秀英", value: "刘秀英" },
{ label: "9林刚", value: "林刚" },
{ label: "10汤磊", value: "汤磊" },
{ label: "11刘军", value: "刘军" },
{ label: "12潘娜", value: "潘娜" },
{ label: "13袁军", value: "袁军" },
{ label: "14段勇", value: "段勇" },
{ label: "15李霞", value: "李霞" },
{ label: "16赵杰", value: "赵杰" },
];
const res = data.filter(item => item.label.includes(queryString));
callback(res);
},
},
},
{
dataPath: 'tags_1', label: '标签输入', input: 'InputTag',
inputProps: {
placeholder: "请输入",
clearable: true,
},
},
{
dataPath: 'color_1', label: '颜色选择', input: 'ColorPicker',
inputProps: {},
},
{
dataPath: 'slider_1', label: '滑块', input: 'Slider',
widthCount: 2,
inputProps: {
showInput: true,
},
},
{
dataPath: 'rate_1', label: '评分', input: 'Rate',
inputProps: {
allowHalf: true,
},
},
{
dataPath: 'mentions_1', label: '提及', input: 'Mention',
inputProps: {
placeholder: '请输入',
options: [
{ value: 'sh', label: '上海', },
{ value: 'bj', label: '北京', },
{ value: 'sz', label: '深圳', },
{ value: 'cq', label: '重庆', },
],
},
},
{
dataPath: 'password', label: '密码', input: 'Input',
inputProps: {
placeholder: '请输入',
clearable: true,
type: "password",
showPassword: true,
},
},
{
dataPath: 'textarea', label: '多行文本', input: 'Input',
widthCount: 3,
inputProps: {
placeholder: '请输入',
clearable: true,
type: "textarea",
autosize: { minRows: 3, maxRows: 8 },
},
},
{
dataPath: 'transfer_1', label: '穿梭框', input: 'Transfer',
widthCount: 3,
inputProps: {
titles: ["数据项", "已选择"],
data: [
{ title: "选项1", key: "v1" },
{ title: "选项2", key: "v2" },
{ title: "选项3", key: "v3" },
{ title: "选项4", key: "v4" },
{ title: "选项5", key: "v5" },
{ title: "选项6", key: "v6" },
{ title: "选项7", key: "v7" },
{ title: "选项8", key: "v8" },
],
},
},
{
dataPath: 'upload_1', label: '文件上传', input: 'Upload',
widthCount: 3,
inputProps: {
listType: "picture-card",
fileList: [
{
name: 'food.jpeg',
url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100',
},
{
name: 'food.jpeg',
url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100',
},
{
name: 'food.jpeg',
url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100',
},
],
},
},
],
columnCount: 3,
layout: "onlyLabelFixed",
});
</script>
<template>
DataForm01.vue
<div class="flex-row-container" style="column-gap: 12px;height: calc(100% - 30px);">
<DataForm
class="flex-item-fixed"
style="width: 900px;height: 100%; overflow-y: auto;border: 1px solid #ccc;padding: 6px"
:data="dataForm1.data"
:formFields="dataForm1.formFields"
:columnCount="dataForm1.columnCount"
:layout="dataForm1.layout"
labelWidth="100px"
inputWidth=""
:colon="true"
/>
<pre class="flex-item-fill" style="overflow-y: auto;" lang="json">{{ JSON.stringify(dataForm1.data, null, 4) }}</pre>
</div>
<div style="height: 24px;"/>
</template>
<style scoped lang="less">
<style>
/** flex多行容器 */
.flex-column-container {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
}
/** flex多列容器 */
.flex-row-container {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
}
/** flex自动填充 */
.flex-item-fill {
flex-grow: 1;
overflow: hidden;
}
/** flex固定大小 */
.flex-item-fixed {
flex-shrink: 0;
}
/** flex主轴上对齐方式 */
.flex-justify-content-center {
justify-content: center;
}
/** flex交叉轴上对齐方式 */
.flex-align-items-center {
align-items: center;
}
/** 内容居中 */
.content-center {
display: flex;
align-items: center;
justify-content: center;
}
</style>

3
src/runtime/EventBus.ts

@ -6,7 +6,8 @@ export type DispatchNames = 'selectedObjectChanged' |
'catalogChanged' |
'dataLoadComplete' |
'entityDeleted' |
'selectedObjectPropertyChanged'
'selectedObjectPropertyChanged' |
'multiSelectedObjectsChanged'
export default {
dispatch(name: DispatchNames, data?: any) {

Loading…
Cancel
Save