diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..1184c67
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+max_line_length = 240
+
+[*.md]
+trim_trailing_whitespace = false
+
+[Makefile]
+indent_style = tab
diff --git a/src/components/Model3DView.vue b/src/components/Model3DView.vue
index e5b7ecf..110e9db 100644
--- a/src/components/Model3DView.vue
+++ b/src/components/Model3DView.vue
@@ -17,6 +17,7 @@
添加输送线
添加货架
+ 添加地堆
材质颜色
@@ -378,6 +379,15 @@ function createShelf(){//创建货架
})
}
+function createGroundStore() {
+ const planeGeometry = new THREE.PlaneGeometry(1, 1);
+ const material = new THREE.MeshBasicMaterial({
+ color: 0x00ff00,
+ side: THREE.DoubleSide // 双面渲染:ml-citation{ref="5,8" data="citationList"}
+ });
+ const planeMesh = new THREE.Mesh(planeGeometry, material);
+ scene.add(planeMesh);
+}
function initThree() {
const viewerDom = canvasContainer.value
diff --git a/src/components/data-form/DataForm.vue b/src/components/data-form/DataForm.vue
new file mode 100644
index 0000000..6e3bf3a
--- /dev/null
+++ b/src/components/data-form/DataForm.vue
@@ -0,0 +1,283 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/data-form/DataFormConstant.ts b/src/components/data-form/DataFormConstant.ts
new file mode 100644
index 0000000..0387773
--- /dev/null
+++ b/src/components/data-form/DataFormConstant.ts
@@ -0,0 +1,69 @@
+import { markRaw } from "vue";
+import {
+ ElAutocomplete,
+ ElCascader,
+ ElCheckbox,
+ ElColorPicker,
+ ElDatePicker,
+ ElInput,
+ ElInputNumber,
+ ElInputTag,
+ ElMention,
+ ElRadio,
+ ElRate,
+ ElSelect,
+ ElSelectV2,
+ ElSlider,
+ ElSwitch,
+ ElTimePicker,
+ ElTimeSelect,
+ ElTransfer,
+ ElTreeSelect,
+ ElUpload,
+} from "element-plus";
+import type { DisplayMode } from "./DataFormTypes.ts";
+
+/** 内建的表单输入组件 */
+const builtInInputComponents = {
+ Autocomplete: markRaw
(ElAutocomplete),
+ Cascader: markRaw(ElCascader),
+ Checkbox: markRaw(ElCheckbox),
+ ColorPicker: markRaw(ElColorPicker),
+ DatePicker: markRaw(ElDatePicker),
+ Input: markRaw(ElInput),
+ InputNumber: markRaw(ElInputNumber),
+ InputTag: markRaw(ElInputTag),
+ Mention: markRaw(ElMention),
+ Radio: markRaw(ElRadio),
+ Rate: markRaw(ElRate),
+ Select: markRaw(ElSelect),
+ SelectV2: markRaw(ElSelectV2),
+ Slider: markRaw(ElSlider),
+ Switch: markRaw(ElSwitch),
+ TimePicker: markRaw(ElTimePicker),
+ TimeSelect: markRaw(ElTimeSelect),
+ Transfer: markRaw(ElTransfer),
+ TreeSelect: markRaw(ElTreeSelect),
+ Upload: markRaw(ElUpload),
+};
+
+type BuiltInInput = keyof typeof builtInInputComponents;
+
+const dataFormInputComponents: Record = markRaw(builtInInputComponents);
+
+/** DisplayMode 的默认值 */
+const defDisplayMode: DisplayMode = {};
+
+export type {
+ BuiltInInput,
+};
+
+export default {
+ dataFormInputComponents,
+ defDisplayMode,
+}
+
+export {
+ dataFormInputComponents,
+ defDisplayMode,
+}
diff --git a/src/components/data-form/DataFormTypes.ts b/src/components/data-form/DataFormTypes.ts
new file mode 100644
index 0000000..6c96a5e
--- /dev/null
+++ b/src/components/data-form/DataFormTypes.ts
@@ -0,0 +1,276 @@
+import { type ComponentInternalInstance, type CSSProperties } from "vue";
+import { type Breakpoints, breakpointsTailwind } from "@vueuse/core";
+import { type FormItemProps, type FormProps } from "element-plus";
+
+/** 表单数据类型 */
+type FormData = Record | Array;
+
+/** 字段标题 */
+type Label = VueNode | ((value: any, dataPath: string, formData?: FormData) => VueNode);
+
+/** 表单数据绑定到表单组件上时的格式化操作 */
+type ValueFormat = DictGroup | ((value: any, dataPath: string, formData?: FormData) => any);
+
+/** 表单组件值更新到表单数据时的数据转换 */
+type ValueTransform = DictGroup | ((value: any, dataPath: string, formData?: FormData) => any);
+
+/** 前缀、后缀的自定义组件配置 */
+interface PrefixSuffixComponent {
+ /** 禁用 */
+ disabled?: boolean;
+ /** 组件名或组件对象 */
+ component?: any;
+ /** 组件props */
+ props?: Record;
+ /** 自定义渲染逻辑 */
+ render?: (value: any) => VueNode;
+}
+
+/** 显示模式 */
+interface DisplayMode {
+ /** 禁用 */
+ disabled?: boolean;
+ /** 定义样式 */
+ style?: CSSProperties;
+ /** 自定义class样式 */
+ class?: string;
+ /** 数据格式显示 */
+ format?: (value: any) => string;
+ /** 自定义渲染逻辑 */
+ render?: (value: any) => VueNode;
+}
+
+/** 表单字段输入组件通用的props */
+interface FormInputBaseProps {
+ /** 定义样式 */
+ style?: CSSProperties;
+ /** 自定义class样式 */
+ class?: string;
+ /** 占位符 */
+ placeholder?: string;
+ /** 允许清空 */
+ allowClear?: boolean;
+ /** 只读 */
+ readonly?: boolean;
+ /** 禁用 */
+ disabled?: boolean;
+ /** 隐藏 */
+ hidden?: boolean;
+ /** 前缀区域的自定义组件 */
+ prefixConfig?: PrefixSuffixComponent;
+ /** 后缀区域的自定义组件 */
+ suffixConfig?: PrefixSuffixComponent;
+ /** 前缀区域已经后缀区域的包装容器的样式 */
+ preSufWrapStyle?: CSSProperties;
+ /** 显示模式 */
+ displayMode?: DisplayMode | true;
+
+ // onFocus
+ // onBlur
+ // onChange
+ // onPressEnter
+
+ [key: string]: any;
+}
+
+/** 监听表单字段值 */
+interface WatchFormFieldValue {
+ /** 数据路径,如: "userName"、"user.age"、"user.hobby.[1].name"、"[2].id"*/
+ dataPath: string
+ /**
+ * 表单字段值变化时的回调
+ * @param oldValue 监听的表单字段旧值
+ * @param newValue 监听的表单字段新值
+ * @param data 整个表单数据
+ * @param formItem 表单字段配置(修改当前对象的属性实现ui更新)
+ * @param input 输入组件实例
+ * @param from Form组件实例
+ * @param ctxData 表单上下文数据
+ */
+ onChange: (
+ oldValue: V,
+ newValue: V,
+ formData: any,
+ formItem: Pick,
+ input: any,
+ from: any,
+ ctxData: DataFormData["ctxData"],
+ ) => void
+ /** 在开始监听时立即触发回调 */
+ immediate?: boolean
+}
+
+/** 表单字段,一般包含一个 label 和一个 input */
+interface FormField {
+ // 核心配置
+ /** 数据路径,如: "userName"、"user.age"、"user.hobby.[1].name"、"[2].id" */
+ dataPath?: string;
+ /** 宽度占用列数 */
+ widthCount?: number;
+ /** 字段标题 */
+ label?: Label;
+ /** 字段输入组件,组件名或者组件对象 */
+ input?: any;
+ /** 字段输入组件props */
+ inputProps?: FormInputBaseProps;
+ /** 表单数据绑定到表单组件上时的格式化操作 */
+ format?: ValueFormat;
+ /** 表单组件值更新到表单数据时的数据转换 */
+ transform?: ValueTransform;
+ /** 当字段输入组件有多个时,扩展的输入组件 */
+ extInputs?: Array>;
+ /** 是否隐藏当前字段 */
+ hidden?: boolean;
+ /** 监听表单字段值变化,修改当前表单字段状态 */
+ watchValues?: WatchFormFieldValue | Array;
+ // 扩展配置
+ /** 定义样式 */
+ style?: CSSProperties;
+ /** 自定义class样式 */
+ class?: string;
+ /** label宽度 */
+ labelWidth?: string | number;
+ /** label标签的位置 */
+ labelPosition?: "left" | "right"
+ /** 是否显示校验错误信息 */
+ showMessage?: boolean;
+ /** 是否在行内显示校验信息 */
+ inlineMessage?: string | boolean;
+ /** 表单验证规则 */
+ rules?: FormItemProps["rules"];
+ /** 是否必填,如不设置,则会根据校验规则自动生成 */
+ required?: boolean;
+ /** 原始的 Form.Item 属性 */
+ rawProps?: FormItemProps;
+}
+
+/** 运行时的表单项 */
+interface DataFormItem {
+ /** 表单项占用列数 */
+ widthCount: number;
+ /** 输入组件ref */
+ inputRef: string;
+ /** 输入组件 */
+ input?: VueComponent;
+ /** 输入组件props */
+ inputProps?: FormInputBaseProps;
+ /** 扩展的输入组件 */
+ extInputs?: Array>;
+ /** 需要使用的 FormItem 属性 */
+ formItemProps: Record;
+ /** 数据路径,如: "userName"、"user.age"、"user.hobby.[1].name"、"[2].id" */
+ dataPath?: string;
+ /** 表单数据绑定到表单组件上时的格式化操作 */
+ format?: ValueFormat;
+ /** 表单组件值更新到表单数据时的数据转换 */
+ transform?: ValueTransform;
+ /** 是否隐藏当前表单项 */
+ hidden?: boolean;
+ /** 监听表单字段值变化 */
+ watchValues?: Array;
+}
+
+// 定义 Props 类型
+interface DataFormProps {
+ // 核心配置
+ /** 表单名称,会作为表单字段 id 前缀使用 */
+ name?: string;
+ /** 表单数据 */
+ data?: FormData;
+ /** 服务端数据API */
+ dataApi?: HttpRequestConfig;
+ /** 自动使用"dataApi"配置加载服务端数据 */
+ autoLoadData?: boolean;
+ /** 表单字段 */
+ formFields?: Array;
+ /** 响应式断点配置 */
+ breakpoints?: Breakpoints;
+ /** 默认一行显示的字段数量 */
+ defColumnCount?: number;
+ /** 一行显示的字段数量,支持响应式断点配置,最大值:24 */
+ columnCount?: number | Record<(keyof typeof breakpointsTailwind) | string, number>;
+ /** 表单布局:bothFixed:label和input都固定宽度;onlyLabelFixed:仅label固定宽度 */
+ layout?: "bothFixed" | "onlyLabelFixed";
+ /** label标签的位置 */
+ labelPosition?: "left" | "right";
+ /** 表单域标签的后缀 */
+ labelSuffix?: string;
+ /** label宽度 */
+ labelWidth?: string | number;
+ /** value宽度,仅layout=bothFixed时有效 */
+ inputWidth?: string | number;
+ /** submit 区域 FormItem 的 Props 配置 */
+ submitFormItemProps?: Record;
+ // 扩展配置
+ /** 表单字段大小 */
+ size?: "" | "small" | "default" | "large";
+ /** 是否显示校验错误信息 */
+ showMessage?: boolean;
+ /** 是否在行内显示校验信息 */
+ inlineMessage?: boolean;
+ /** 是否显示校验错误信息 */
+ requireAsteriskPosition?: "left" | "right";
+ /** 隐藏所有表单项的必选标记 */
+ hideRequiredAsterisk?: boolean;
+ /** 表单验证规则 */
+ rules?: FormProps["rules"];
+ /** 是否在 rules 属性改变后立即触发一次验证 */
+ validateOnRuleChange?: boolean;
+ /** 设置表单组件禁用 */
+ disabled?: boolean;
+ /** 当校验失败时,滚动到第一个错误表单项 */
+ scrollToError?: boolean;
+ /** 当校验有失败结果时,滚动到第一个失败的表单项目 可通过 scrollIntoView 配置 */
+ scrollIntoViewOptions?: FormProps["scrollIntoViewOptions"];
+ /** 注册内置使用的组件 */
+ components?: Record;
+ /** 原始的 Form 属性 */
+ rawProps?: FormProps;
+}
+
+// 定义 State 类型
+interface DataFormState {
+ /** 是否是加载中 */
+ loading: boolean;
+ /** 数据值 */
+ data?: FormData;
+ /** 表格列数 */
+ columnCount: number;
+ /** 表单项数组 */
+ dataFormItems: Array;
+}
+
+// 定义 Data 类型
+interface DataFormData {
+ /** 表单数据是否是第一次变化 */
+ firstDataChange: boolean;
+ /** 表单旧数据,Record */
+ oldData: Record;
+ /** 表单组件的引用 */
+ formRef: any;
+ /** 输入组件的引用 */
+ inputRefs: Record;
+ /** 表单上下文数据 */
+ ctxData: {
+ /** 当前 DataForm 组件实例 */
+ instance?: ComponentInternalInstance;
+ /** 其它属性 */
+ [key: string]: any;
+ };
+}
+
+export type {
+ FormData,
+ Label,
+ ValueFormat,
+ ValueTransform,
+ PrefixSuffixComponent,
+ DisplayMode,
+ FormInputBaseProps,
+ WatchFormFieldValue,
+ FormField,
+ DataFormItem,
+ DataFormProps,
+ DataFormState,
+ DataFormData,
+}
diff --git a/src/components/data-form/DataFormUtils.tsx b/src/components/data-form/DataFormUtils.tsx
new file mode 100644
index 0000000..184c658
--- /dev/null
+++ b/src/components/data-form/DataFormUtils.tsx
@@ -0,0 +1,37 @@
+import lodash from "lodash";
+import { Typeof } from "@ease-forge/shared";
+
+/**
+ * 将字符串转换成 antd 支持的 NamePath
+ * @param dataPath 数据路径,如: "userName"、"user.age"、"user.hobby.[1].name"、"[2].id"
+ */
+function dataPathToNamePath(dataPath: string): string | number | Array {
+ dataPath = lodash.trim(dataPath);
+ const paths = dataPath.split(".");
+ const namePath: Array = [];
+ for (let path of paths) {
+ path = lodash.trim(path);
+ let name: string | number = path;
+ if (path.startsWith("[") && path.endsWith("]")) {
+ name = lodash.toInteger(path.substring(1, path.length - 1));
+ if (!Typeof.isValidNumber(name)) {
+ name = path;
+ }
+ }
+ namePath.push(name);
+ }
+ if (namePath.length === 0) {
+ return dataPath;
+ } else if (namePath.length === 1) {
+ return namePath[0];
+ }
+ return namePath;
+}
+
+export default {
+ dataPathToNamePath,
+}
+
+export {
+ dataPathToNamePath,
+}
diff --git a/src/core/base/IMeta.ts b/src/core/base/IMeta.ts
index 680d003..356693f 100644
--- a/src/core/base/IMeta.ts
+++ b/src/core/base/IMeta.ts
@@ -44,6 +44,7 @@ export type IMeta = MetaItem[]
export interface MetaItem {
field?: string;
editor: string;
+ editorProps?: any;
label?: string;
readonly?: boolean;
category?: string;
diff --git a/src/editor/widgets/property/PropertyView.vue b/src/editor/widgets/property/PropertyView.vue
index dd9ad6b..4ea9053 100644
--- a/src/editor/widgets/property/PropertyView.vue
+++ b/src/editor/widgets/property/PropertyView.vue
@@ -11,36 +11,48 @@