diff --git a/examples/main.js b/examples/main.js
index 88079c9..a1250f5 100644
--- a/examples/main.js
+++ b/examples/main.js
@@ -1,6 +1,5 @@
 import { createApp } from 'vue'
 import ElementPlus from 'element-plus'
-import * as ElementPlusIconsVue from '@element-plus/icons-vue'
 import Vant, { Locale } from 'vant'
 import enUS from 'vant/es/locale/lang/en-US'
 // import '@vant/touch-emulator'
@@ -14,6 +13,3 @@ app.use(router)
 app.use(ElementPlus)
 app.use(Vant)
 app.mount('#app')
-for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
-  app.component(key, component)
-}
diff --git a/examples/views/formEditor.vue b/examples/views/formEditor.vue
index 0e47131..99b9ee9 100644
--- a/examples/views/formEditor.vue
+++ b/examples/views/formEditor.vue
@@ -1,9 +1,27 @@
 <script setup>
-import { ref, onMounted, getCurrentInstance } from 'vue'
+import { ref, onMounted, getCurrentInstance, inject } from 'vue'
 import { erFormEditor } from '@ER/formEditor'
+const {
+  lang
+} = inject('globalConfig')
 const EReditorRef = ref(null)
+const handleListener = async ({ type, data }) => {
+  // console.log(data)
+  switch (type) {
+    case 'lang':
+      lang.value = data
+      localStorage.setItem('er-lang', data)
+      break
+  }
+}
+const checkFieldsForNewBadge = (field) => {
+  return field.type === 'subform'
+}
 </script>
 <template>
   <er-form-editor
+    :checkFieldsForNewBadge="checkFieldsForNewBadge"
+    :lang="lang"
+    @listener="handleListener"
     ref="EReditorRef"/>
 </template>
diff --git a/examples/views/formEditor/objEdit.vue b/examples/views/formEditor/objEdit.vue
index 6fe3183..eb30dfa 100644
--- a/examples/views/formEditor/objEdit.vue
+++ b/examples/views/formEditor/objEdit.vue
@@ -41,7 +41,6 @@ const getObjData = async () => {
   }
 }
 const handleListener = async ({ type, data }) => {
-  console.log(type)
   switch (type) {
     case 'lang':
       lang.value = data
@@ -53,6 +52,8 @@ const handleListener = async ({ type, data }) => {
       }
       loading.value = true
       try {
+        // console.log(data)
+        // data.fields[0].options.defaultValue = []
         const postData = {
           name: state.name,
           content: Object.assign({
@@ -89,6 +90,9 @@ const quickImages = ref([
   '/public/Everright-logo.svg',
   '/public/Everright-logo.svg'
 ])
+const checkFieldsForNewBadge = (field) => {
+  return field.type === 'subform'
+}
 </script>
 <template>
   <div
@@ -96,6 +100,7 @@ const quickImages = ref([
   >
     <er-form-editor
       :checkPropsBySelected="checkPropsBySelected"
+      :checkFieldsForNewBadge="checkFieldsForNewBadge"
       v-if="isRender"
       :quickImages="quickImages"
       :layoutType="layoutType"
diff --git a/examples/views/formEditorConfig.vue b/examples/views/formEditorConfig.vue
index 29507e0..82d93a1 100644
--- a/examples/views/formEditorConfig.vue
+++ b/examples/views/formEditorConfig.vue
@@ -41,6 +41,11 @@ watch(lang, (newLang) => {
         node.columns[0].rows[0].columns[0].label = `${node.columns[0].label} > ${node.columns[0].rows[0].columns[0].type}`
         store.layouts.push(node.columns[0].rows[0].columns[0])
         break
+      case 'subform':
+        node.columns[0].list[0].push(erGeneratorData(erComponentsConfig.fieldsConfig[1].list[0], true, 'en'))
+        // node.columns[0].rows[0].columns[0].label = `${node.columns[0].label} > ${node.columns[0].rows[0].columns[0].type}`
+        // store.layouts.push(node.columns[0].rows[0].columns[0])
+        break
     }
   })
   all.value = [...store.fields, ...store.layouts]
diff --git a/package.json b/package.json
index f17cff0..5fa79a4 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,7 @@
     "canvas": "^2.11.2",
     "dayjs": "^1.11.7",
     "element-plus": "^2.2.28",
-    "everright-filter": "^0.0.22",
+    "everright-filter": "^1.1.1",
     "jss": "^10.9.2",
     "jss-preset-default": "^10.9.2",
     "lodash-es": "^4.17.21",
@@ -79,7 +79,6 @@
     "@ckeditor/ckeditor5-vue": "^4.0.1",
     "@commitlint/cli": "^17.4.4",
     "@commitlint/config-conventional": "^17.4.4",
-    "@element-plus/icons-vue": "^2.0.10",
     "@vitejs/plugin-vue": "^3.2.0",
     "@vitejs/plugin-vue-jsx": "^2.1.1",
     "@vue/compiler-sfc": "^3.2.47",
diff --git a/packages/formEditor/components/FormTypes/Uploadfile/pc.vue b/packages/formEditor/components/FormTypes/Uploadfile/pc.vue
index fc4ae7d..5cc0800 100644
--- a/packages/formEditor/components/FormTypes/Uploadfile/pc.vue
+++ b/packages/formEditor/components/FormTypes/Uploadfile/pc.vue
@@ -80,7 +80,9 @@ const handleError = (error) => {
     :before-upload="beforeAvatarUpload"
     :on-error="handleError"
   >
-    <el-icon><Plus /></el-icon>
+    <el-icon>
+      <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728=""><path fill="currentColor" d="M480 480V128a32 32 0 0 1 64 0v352h352a32 32 0 1 1 0 64H544v352a32 32 0 1 1-64 0V544H128a32 32 0 0 1 0-64h352z"></path></svg>
+    </el-icon>
   </el-upload>
 
   <el-image-viewer
diff --git a/packages/formEditor/components/Layout/ControlInsertionPlugin.js b/packages/formEditor/components/Layout/ControlInsertionPlugin.js
index 85ddd72..27536bc 100644
--- a/packages/formEditor/components/Layout/ControlInsertionPlugin.js
+++ b/packages/formEditor/components/Layout/ControlInsertionPlugin.js
@@ -163,7 +163,7 @@ const setBorder = (el, className) => {
   el.classList.add(className)
 }
 const getDragElement = (node) => {
-  return node.__draggable_context.element
+  return node.__draggable_context?.element
 }
 
 const setStates = (newTarget, ev, ER) => {
@@ -313,14 +313,6 @@ function ControlInsertionPlugin (ER) {
   }
   ControlInsertion.prototype = {
     dragStart (e) {
-      // const {
-      //   rootEl,
-      //   target
-      // } = e
-      // const isBlock = _.get(e, 'sortable.options.dataSource', false) === 'block'
-      // if (isBlock) return false
-      // const targetContainer = rootEl.parentNode
-      // targetContainer.style.zIndex = 2
     },
     drop (e) {
       if (!prevEl || !e.activeSortable) {
@@ -344,7 +336,12 @@ function ControlInsertionPlugin (ER) {
         }
       }
       if (inserRowIndex !== '') {
-        const store = Array.isArray(prevSortable.options.parent) ? prevSortable.options.parent : prevSortable.options.parent.list
+        let store = []
+        if (prevSortable.options.parent.type === 'subform') {
+          store = prevSortable.options.parent.list[0]
+        } else {
+          store = Array.isArray(prevSortable.options.parent) ? prevSortable.options.parent : prevSortable.options.parent.list
+        }
         store.splice(inserRowIndex, 0, newElement)
         utils.addContext(store[inserRowIndex], prevSortable.options.parent)
       }
@@ -382,7 +379,7 @@ function ControlInsertionPlugin (ER) {
       const {
         activeSortable: {
           constructor: {
-            utils
+            utils: SortableUtils
           },
           options: {
             dataSource
@@ -413,15 +410,26 @@ function ControlInsertionPlugin (ER) {
       if (target.dataset.layoutType === 'grid') {
         return false
       }
+      const dragNode = getDragElement(dragEl)
+      const targetNode = getDragElement(target)
+      if ((!utils.checkIsField(dragNode) || dragNode.type === 'subform') && utils.checkIsInSubform(targetNode)) {
+        return false
+      }
+      if (target.dataset.layoutType === 'subform') {
+        // console.log(utils)
+        if (!utils.checkIsField(dragNode) || dragNode.type === 'subform') {
+          return false
+        }
+      }
       originalEvent.stopPropagation && originalEvent.stopPropagation()
       const direction = ''
       const targetContainer = el.parentNode
       const targetOnlyOne = targetList.length === 1
-      let newTarget = utils.closest(target, this.options.draggable, sortable.el)
+      let newTarget = SortableUtils.closest(target, this.options.draggable, sortable.el)
       if (dragEl.contains(newTarget)) {
         return false
       }
-      if (/^(grid-col|tabs-col|td|collapse-col|root|inline)$/.test(target.dataset.layoutType)) {
+      if (/^(grid-col|tabs-col|td|collapse-col|root|inline|subform)$/.test(target.dataset.layoutType)) {
         newTarget = target
         const state = (newTarget.__draggable_component__ || newTarget.children[0].__draggable_component__)
         if (!state.list.length) {
@@ -433,9 +441,6 @@ function ControlInsertionPlugin (ER) {
           if (/^(root|grid-col)$/.test(target.dataset.layoutType)) {
             const rows = el.children
             prevEl = lastChild(el)
-            // if (prevEl.contains(dragEl) && list.length === 1) {
-            // console.log(prevEl)
-            // console.log(dragEl)
             if (prevEl === dragEl.parentNode.parentNode && list.length === 1) {
               prevEl = ''
               return false
diff --git a/packages/formEditor/components/Layout/DragGable.jsx b/packages/formEditor/components/Layout/DragGable.jsx
index d21dd44..09c820d 100644
--- a/packages/formEditor/components/Layout/DragGable.jsx
+++ b/packages/formEditor/components/Layout/DragGable.jsx
@@ -21,6 +21,7 @@ import LayoutTabsLayout from './TabsLayout'
 import LayoutCollapseLayout from './CollapseLayout'
 import LayoutTableLayout from './TableLayout'
 import LayoutInlineLayout from './InlineLayout'
+import LayoutSubformLayout from './SubformLayout'
 import Selection from '@ER/formEditor/components/Selection/selectElement.jsx'
 import ControlInsertionPlugin from './ControlInsertionPlugin'
 const dragGableWrap = defineComponent({
@@ -143,6 +144,11 @@ export default defineComponent({
           case 'inline':
             node = (<LayoutInlineLayout key={element.id} data={element} parent={props.data}></LayoutInlineLayout>)
             break
+          case 'subform':
+            if (unref(isEditModel) || _.get(state.fieldsLogicState.get(element), 'visible', undefined) !== 0) {
+              node = (<LayoutSubformLayout key={element.id} data={element} parent={props.data}></LayoutSubformLayout>)
+            }
+            break
           default:
             let TypeComponent = ''
             if (unref(isEditModel) || _.get(state.fieldsLogicState.get(element), 'visible', undefined) !== 0) {
diff --git a/packages/formEditor/components/Layout/SubformLayout.jsx b/packages/formEditor/components/Layout/SubformLayout.jsx
new file mode 100644
index 0000000..d7741f2
--- /dev/null
+++ b/packages/formEditor/components/Layout/SubformLayout.jsx
@@ -0,0 +1,164 @@
+import {
+  defineComponent,
+  useAttrs,
+  unref,
+  inject,
+  onBeforeUnmount
+} from 'vue'
+import Selection from '@ER/formEditor/components/Selection/selectElement.jsx'
+import LayoutDragGable from './DragGable.jsx'
+import hooks from '@ER/hooks'
+import _ from 'lodash-es'
+import utils from '@ER/utils'
+import Icon from '@ER/icon'
+export default defineComponent({
+  name: 'SubformLayout',
+  inheritAttrs: false,
+  customOptions: {},
+  props: {
+    data: Object,
+    parent: Array
+  },
+  setup (props) {
+    const ER = inject('Everright')
+    const ExtraParams = inject('EverrightExtraParams', {})
+    const ns = hooks.useNamespace('SubformLayout')
+    const {
+      state,
+      isEditModel,
+      isPc,
+      setSelection
+    } = hooks.useTarget()
+    const typeProps = hooks.useProps(state, props.data, unref(isPc))
+    const addData = unref(isEditModel) ? [] : _.cloneDeep(props.data.list[0])
+    const handleAdd = () => {
+      props.data.list.splice(props.data.list.length, 0, _.cloneDeep(addData))
+      // console.log(props.data.list)
+      // console.log(props.data.list[props.data.list.length - 1])
+      props.data.list[props.data.list.length - 1].forEach(e => {
+        utils.addContext(e, props.data)
+      })
+    }
+    const clearList = () => {
+      props.data.list.splice(0, props.data.list.length)
+    }
+    const setList = (length, values) => {
+      clearList()
+      for (let i = 0; i < length; i++) {
+        handleAdd()
+      }
+      props.data.list.forEach((e, index) => {
+        e.forEach(e => {
+          e.columns.forEach(e => {
+            if (props.data.options.defaultValue) {
+              try {
+                ER.setValue(e, values[index][e.key])
+              } catch (e) {
+              }
+            }
+          })
+        })
+      })
+    }
+    if (!unref(isEditModel)) {
+      onBeforeUnmount(() => {
+        props.data.list[0] = addData
+      })
+      clearList()
+      if (ER.state.remoteValues.has(props.data.key)) {
+        const values = ER.state.remoteValues.get(props.data.key)
+        setList(values.length, values)
+      } else {
+        if (props.data.options.defaultValue.length) {
+          setList(props.data.options.defaultValue.length, props.data.options.defaultValue)
+        }
+      }
+    }
+    if (ExtraParams.inSubformDefaultValueComponent) {
+      ExtraParams.handle.handleAdd = handleAdd
+    }
+    const params = {
+      hasCopy: true,
+      hasDel: true,
+      hasDrag: true,
+      hasWidthScale: true,
+      data: props.data,
+      parent: props.parent
+    }
+    if (process.env.NODE_ENV === 'test') {
+      params['data-field-id'] = `${props.data.id}`
+    }
+    return () => {
+      return (
+        <Selection
+          {...useAttrs()}
+          {
+            ...params
+          }
+        >
+          <div class={ns.b()}>
+            <el-form-item
+              {...typeProps.value}
+            >
+              <div
+                class={[ns.e('content')]}
+              >
+                {
+                  props.data.list.map((node, index) =>
+                    (
+                      <div
+                        class={[
+                          ns.e('item'),
+                          !unref(isEditModel) && !typeProps.value.disabled && ns.e('edit')
+                        ]}
+                        {...utils.addTestId('SubformLayout:item')}
+                      >
+                        <div
+                          class={[ns.e('button')]}>
+                          <el-button
+                            size="large"
+                            circle
+                          >
+                            {index + 1}
+                          </el-button>
+                          <el-button
+                            size="large"
+                            circle
+                            type="danger"
+                            onClick={() => props.data.list.splice(index, 1)}
+                            icon={<Icon class={[ns.e('icon')]} icon="delete"></Icon>}
+                          >
+                          </el-button>
+                        </div>
+                        <LayoutDragGable
+                          data-layout-type={'subform'}
+                          data={node}
+                          ControlInsertion={true}
+                          parent={props.data}/>
+                      </div>
+                    ))
+                }
+                {
+                  (!typeProps.value.disabled && !ExtraParams.inSubformDefaultValueComponent && addData.length)
+                    ? (<div
+                  class={[ns.e('addButton')]}
+                  {...utils.addTestId('SubformLayout:addButton')}
+                >
+                  <el-button
+                    link
+                    type="primary"
+                    onClick={!unref(isEditModel) && handleAdd}
+                  >
+                    Add new
+                  </el-button>
+                </div>)
+                    : ''
+                }
+              </div>
+            </el-form-item>
+          </div>
+        </Selection>
+      )
+    }
+  }
+})
diff --git a/packages/formEditor/components/Panels/Config/components/BackgroundComponent.vue b/packages/formEditor/components/Panels/Config/components/BackgroundComponent.vue
index 0c0ba3a..daa2c82 100644
--- a/packages/formEditor/components/Panels/Config/components/BackgroundComponent.vue
+++ b/packages/formEditor/components/Panels/Config/components/BackgroundComponent.vue
@@ -227,7 +227,9 @@ const handleSuccess = (response, uploadFile) => {
             :on-success="handleSuccess"
             :on-error="handleError"
           >
-            <el-icon><Plus /></el-icon>
+            <el-icon>
+              <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728=""><path fill="currentColor" d="M480 480V128a32 32 0 0 1 64 0v352h352a32 32 0 1 1 0 64H544v352a32 32 0 1 1-64 0V544H128a32 32 0 0 1 0-64h352z"></path></svg>
+            </el-icon>
           </el-upload>
         </li>
         <li
diff --git a/packages/formEditor/components/Panels/Config/components/DataComponent2.jsx b/packages/formEditor/components/Panels/Config/components/DataComponent2.jsx
index 9d65308..f7499ed 100644
--- a/packages/formEditor/components/Panels/Config/components/DataComponent2.jsx
+++ b/packages/formEditor/components/Panels/Config/components/DataComponent2.jsx
@@ -1,4 +1,4 @@
-import { computed, defineComponent, resolveComponent, unref, ref, watch, reactive, defineExpose, nextTick } from 'vue'
+import { computed, defineComponent, resolveComponent, unref, ref, watch, reactive, defineExpose, nextTick, h } from 'vue'
 import utils from '@ER/utils'
 import hooks from '@ER/hooks'
 import Icon from '@ER/icon'
@@ -141,11 +141,11 @@ export default defineComponent({
                           onClick={() => handleAction(2, index, e)}
                           vModel={e.label}></el-input>
                         <el-icon onClick={() => items.splice(i, 1)} color="#fff" size={10} class={ns.e('del')}>
-                          <Minus/>
+                          <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728=""><path fill="currentColor" d="M128 544h768a32 32 0 1 0 0-64H128a32 32 0 0 0 0 64z"></path></svg>
                         </el-icon>
                         <el-icon onClick={() => handleAction(3, index, e)} color="#409eff" size={20} class={[ns.e('hide'), e.disabled && ns.e('show')]}>
                           {
-                            e.disabled ? <Hide/> : <View/>
+                            e.disabled ? <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728=""><path d="M876.8 156.8c0-9.6-3.2-16-9.6-22.4-6.4-6.4-12.8-9.6-22.4-9.6-9.6 0-16 3.2-22.4 9.6L736 220.8c-64-32-137.6-51.2-224-60.8-160 16-288 73.6-377.6 176C44.8 438.4 0 496 0 512s48 73.6 134.4 176c22.4 25.6 44.8 48 73.6 67.2l-86.4 89.6c-6.4 6.4-9.6 12.8-9.6 22.4 0 9.6 3.2 16 9.6 22.4 6.4 6.4 12.8 9.6 22.4 9.6 9.6 0 16-3.2 22.4-9.6l704-710.4c3.2-6.4 6.4-12.8 6.4-22.4Zm-646.4 528c-76.8-70.4-128-128-153.6-172.8 28.8-48 80-105.6 153.6-172.8C304 272 400 230.4 512 224c64 3.2 124.8 19.2 176 44.8l-54.4 54.4C598.4 300.8 560 288 512 288c-64 0-115.2 22.4-160 64s-64 96-64 160c0 48 12.8 89.6 35.2 124.8L256 707.2c-9.6-6.4-19.2-16-25.6-22.4Zm140.8-96c-12.8-22.4-19.2-48-19.2-76.8 0-44.8 16-83.2 48-112 32-28.8 67.2-48 112-48 28.8 0 54.4 6.4 73.6 19.2L371.2 588.8ZM889.599 336c-12.8-16-28.8-28.8-41.6-41.6l-48 48c73.6 67.2 124.8 124.8 150.4 169.6-28.8 48-80 105.6-153.6 172.8-73.6 67.2-172.8 108.8-284.8 115.2-51.2-3.2-99.2-12.8-140.8-28.8l-48 48c57.6 22.4 118.4 38.4 188.8 44.8 160-16 288-73.6 377.6-176C979.199 585.6 1024 528 1024 512s-48.001-73.6-134.401-176Z" fill="currentColor"></path><path d="M511.998 672c-12.8 0-25.6-3.2-38.4-6.4l-51.2 51.2c28.8 12.8 57.6 19.2 89.6 19.2 64 0 115.2-22.4 160-64 41.6-41.6 64-96 64-160 0-32-6.4-64-19.2-89.6l-51.2 51.2c3.2 12.8 6.4 25.6 6.4 38.4 0 44.8-16 83.2-48 112-32 28.8-67.2 48-112 48Z" fill="currentColor"></path></svg> : <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728=""><path fill="currentColor" d="M512 160c320 0 512 352 512 352S832 864 512 864 0 512 0 512s192-352 512-352zm0 64c-225.28 0-384.128 208.064-436.8 288 52.608 79.872 211.456 288 436.8 288 225.28 0 384.128-208.064 436.8-288-52.608-79.872-211.456-288-436.8-288zm0 64a224 224 0 1 1 0 448 224 224 0 0 1 0-448zm0 64a160.192 160.192 0 0 0-160 160c0 88.192 71.744 160 160 160s160-71.808 160-160-71.744-160-160-160z"></path></svg>
                           }
                         </el-icon>
                       </div>
@@ -158,7 +158,23 @@ export default defineComponent({
           {this.shows[index] && (
             <div class={[ns.e('control')]}>
               <el-button
-                icon={'CirclePlus'}
+                icon={h('svg', {
+                  viewBox: '0 0 1024 1024',
+                  xmlns: 'http://www.w3.org/2000/svg'
+                },
+                h('path', {
+                  fill: 'currentColor',
+                  d: 'M352 480h320a32 32 0 1 1 0 64H352a32 32 0 0 1 0-64z'
+                }),
+                h('path', {
+                  fill: 'currentColor',
+                  d: 'M480 672V352a32 32 0 1 1 64 0v320a32 32 0 0 1-64 0z'
+                }),
+                h('path', {
+                  fill: 'currentColor',
+                  d: 'M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768zm0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896z'
+                })
+                )}
                 onClick={() => handleAction(1, index, items)}
                 text>
                 {t('er.config.dataComponent2.add')}
diff --git a/packages/formEditor/components/Panels/Config/components/LogicComponent.vue b/packages/formEditor/components/Panels/Config/components/LogicComponent.vue
index 1c3f89b..645a93e 100644
--- a/packages/formEditor/components/Panels/Config/components/LogicComponent.vue
+++ b/packages/formEditor/components/Panels/Config/components/LogicComponent.vue
@@ -5,7 +5,8 @@ import {
   generateIfFilterOptionsData,
   generateIfFilterConditionsData,
   generateThenFilterOptionsData,
-  generateThenFilterConditionsData
+  generateThenFilterConditionsData,
+  findValidFields
 } from './generateFilterdata.js'
 import _ from 'lodash-es'
 import { EverrightFilter } from 'everright-filter'
@@ -58,7 +59,7 @@ const {
 const getIfOptions = (type) => async () => {
   return new Promise((resolve, reject) => {
     resolve({
-      data: generateIfFilterOptionsData(type, state.fields)
+      data: generateIfFilterOptionsData(type, findValidFields(state.fields))
     })
   })
 }
@@ -72,14 +73,14 @@ const getIfConditions = (type) => async ({ property }) => {
 const getThenOptions = (type) => async () => {
   return new Promise((resolve, reject) => {
     resolve({
-      data: generateThenFilterOptionsData(type, state.fields)
+      data: generateThenFilterOptionsData(type, findValidFields(state.fields))
     })
   })
 }
 const getThenConditions = (type) => async ({ property }) => {
   return new Promise((resolve, reject) => {
     resolve({
-      data: generateThenFilterConditionsData(type, state.fields)
+      data: generateThenFilterConditionsData(type, findValidFields(state.fields))
     })
   })
 }
diff --git a/packages/formEditor/components/Panels/Config/components/PropsPanel.vue b/packages/formEditor/components/Panels/Config/components/PropsPanel.vue
index 2ecca37..c36c7b2 100644
--- a/packages/formEditor/components/Panels/Config/components/PropsPanel.vue
+++ b/packages/formEditor/components/Panels/Config/components/PropsPanel.vue
@@ -13,6 +13,7 @@ import PanelsConfigComponentsBackgroundComponent from './BackgroundComponent.vue
 import PanelsConfigComponentsDataComponent1 from './DataComponent1.jsx'
 import PanelsConfigComponentsDataComponent2 from './DataComponent2.jsx'
 import PanelsConfigComponentsDataComponent3 from './DataComponent3.vue'
+import PanelsConfigComponentsSubformDefaultValue from './SubformDefaultValue.vue'
 import Icon from '@ER/icon'
 import _ from 'lodash-es'
 export default {
@@ -37,7 +38,8 @@ const {
   isSelectTabs,
   isSelectCollapse,
   isSelectTable,
-  isPc
+  isPc,
+  isSelectSubform
 } = hooks.useTarget()
 defineEmits(['changePanel'])
 const bgStatus = ref(false)
@@ -415,7 +417,7 @@ onMounted(() => {
       <template v-slot:content>
         <div :class="[ns.e('collapseWrap'), ns.e('collapseWrap-left')]">
           <el-row justify="space-between" align="middle">
-            <el-col :span="isPc ? 11 : 24">
+            <el-col :span="isSelectSubform ? 24 : (isPc ? 11 : 24)">
               <el-form-item v-bind="utils.addTestId('configPanel:title')">
                 <template v-slot:label>
                   <Icon icon="title"/>
@@ -427,7 +429,7 @@ onMounted(() => {
                 />
               </el-form-item>
             </el-col>
-            <el-col :span="12" v-if="isPc">
+            <el-col :span="12" v-if="isPc && !isSelectSubform">
               <el-form-item v-bind="utils.addTestId('configPanel:titleWidth')">
                 <template v-slot:label>
                   <Icon icon="dragWidth"/>
@@ -445,7 +447,7 @@ onMounted(() => {
     <PanelsConfigComponentsTypeComponent
       :label="t('er.config.propsPanel.defaultContent')"
       :layoutType="0"
-      v-if="checkTypeBySelected([
+      v-if="checkTypeBySelected(['subform'], 'defaultValue') ? target.list[0].length : checkTypeBySelected([
       'input',
       'textarea',
       'time',
@@ -528,6 +530,9 @@ onMounted(() => {
           style="padding: 0 14px;"
         />
       </template>
+      <template v-else-if="checkTypeBySelected(['subform'], 'defaultValue')">
+        <PanelsConfigComponentsSubformDefaultValue/>
+      </template>
     </PanelsConfigComponentsTypeComponent>
     <PanelsConfigComponentsTypeComponent
       :label="t('er.public.Data')"
diff --git a/packages/formEditor/components/Panels/Config/components/SubformDefaultValue.vue b/packages/formEditor/components/Panels/Config/components/SubformDefaultValue.vue
new file mode 100644
index 0000000..19753e5
--- /dev/null
+++ b/packages/formEditor/components/Panels/Config/components/SubformDefaultValue.vue
@@ -0,0 +1,122 @@
+<script>
+import _ from 'lodash-es'
+import hooks from '@ER/hooks'
+import { ref, inject, nextTick, reactive, computed, watch, onMounted, provide, onBeforeUnmount } from 'vue'
+import utils from '@ER/utils'
+import erFormPreview from '@ER/formEditor/preview.vue'
+export default {
+  name: 'ConfigSubformDefaultValueComponent',
+  inheritAttrs: false,
+  customOptions: {}
+}
+</script>
+<script setup>
+const handle = {}
+const ExtraParams = provide('EverrightExtraParams', {
+  inSubformDefaultValueComponent: true,
+  handle
+})
+const ER = inject('Everright')
+const EReditorPreviewRef = ref('')
+const scrollbarRef = ref()
+const {
+  t,
+  lang
+} = hooks.useI18n()
+const {
+  target,
+  state
+} = hooks.useTarget()
+const ns = hooks.useNamespace('ConfigSubformDefaultValueComponent')
+const dialogVisible = ref(false)
+const handleClosed = () => {
+  // tabs.value.forEach(tab => {
+  //   tab.rules = []
+  // })
+}
+const openDialog = async () => {
+  dialogVisible.value = true
+  let rawData = {}
+  if (state.mode === 'config') {
+    rawData = utils.generateData()
+    rawData.list = [target.value]
+    rawData = utils.disassemblyData1(_.cloneDeep(rawData))
+  } else {
+    rawData = ER.getData()
+    rawData.logic = {}
+    rawData.list = [{
+      type: 'inline',
+      columns: [
+        target.value.id
+      ]
+    }]
+  }
+  await nextTick()
+  const value = {}
+  value[target.value.key] = target.value.options.defaultValue
+  EReditorPreviewRef.value.setData(rawData, value)
+}
+const closeDialog = () => {
+  dialogVisible.value = false
+}
+const handleAction = (type) => {
+  switch (type) {
+    case 0:
+      closeDialog()
+      break
+    case 1:
+      handle.handleAdd()
+      break
+    case 2:
+      target.value.options.defaultValue = EReditorPreviewRef.value.getData()[target.value.key]
+      closeDialog()
+      break
+  }
+}
+</script>
+<template>
+  <el-drawer
+    destroy-on-close
+    size="60%"
+    :modal="false"
+    append-to-body
+    :close-on-press-escape="false"
+    :with-header="false"
+    @closed="handleClosed"
+    :class="[ns.b()]"
+    v-model="dialogVisible">
+    <div>{{ t('er.config.propsPanel.defaultContent') }}</div>
+    <div>
+      <el-scrollbar ref="scrollbarRef" max-height="calc(100vh - 180px)">
+        <er-form-preview
+          ref="EReditorPreviewRef"
+          :isShowCompleteButton="false"
+        />
+      </el-scrollbar>
+      <el-button
+        :class="[ns.e('button')]"
+        @click="handleAction(1)"
+        v-bind="utils.addTestId('configPanel:defaultValue:addButton')"
+      >
+        {{ t('er.public.add')}}
+      </el-button>
+    </div>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="handleAction(0)">
+          {{ t('er.public.cancel')}}
+        </el-button>
+        <el-button type="primary" @click="handleAction(2)">
+          {{ t('er.public.confirm')}}
+        </el-button>
+      </span>
+    </template>
+  </el-drawer>
+  <el-button
+    v-bind="utils.addTestId('configPanel:defaultValue:button')"
+    style="width: 100%;"
+    type="primary"
+    @click="openDialog">
+    {{ t('er.config.propsPanel.setDefaultContent') }}
+  </el-button>
+</template>
diff --git a/packages/formEditor/components/Panels/Config/components/generateFilterdata.js b/packages/formEditor/components/Panels/Config/components/generateFilterdata.js
index 7bcbdd5..00037f6 100644
--- a/packages/formEditor/components/Panels/Config/components/generateFilterdata.js
+++ b/packages/formEditor/components/Panels/Config/components/generateFilterdata.js
@@ -138,6 +138,7 @@ const generateIfFilterOptionsData = (activeTab, fields) => {
         break
       case 'signature':
       case 'uploadfile':
+      case 'subform':
         filterNode.renderType = 'NONE'
         filterNode.operatorKey = 'Text'
         filterNode.includeOperator = {
@@ -411,9 +412,15 @@ const generateThenFilterConditionsData = (activeTab, fields) => {
     }
   })
 }
+const findValidFields = (fields) => {
+  return fields.filter(field => {
+    return field.type === 'subform' || !utils.checkIsInSubform(field)
+  })
+}
 export {
   generateIfFilterOptionsData,
   generateIfFilterConditionsData,
   generateThenFilterOptionsData,
-  generateThenFilterConditionsData
+  generateThenFilterConditionsData,
+  findValidFields
 }
diff --git a/packages/formEditor/components/Panels/Config/index.vue b/packages/formEditor/components/Panels/Config/index.vue
index 9ee3114..3209315 100644
--- a/packages/formEditor/components/Panels/Config/index.vue
+++ b/packages/formEditor/components/Panels/Config/index.vue
@@ -1,7 +1,7 @@
 <script>
 import utils from '@ER/utils'
 import hooks from '@ER/hooks'
-import { ref, computed, reactive, watch, onMounted, inject } from 'vue'
+import { ref, computed, reactive, watch, onMounted, inject, h } from 'vue'
 import _ from 'lodash-es'
 import Icon from '@ER/icon'
 import PanelsConfigComponentsPropsPanel from '@ER/formEditor/components/Panels/Config/components/PropsPanel.vue'
@@ -155,10 +155,21 @@ watch(target, () => {
   <el-aside :class="[ns.b()]" :width="ER.props.configPanelWidth">
     <el-breadcrumb
       :class="[ns.e('breadcrumb')]"
-      separator-icon="ArrowRight"
+      :separator-icon="() => (
+        h('svg', {
+          viewBox: '0 0 1024 1024',
+          xmlns: 'http://www.w3.org/2000/svg'
+        },
+         h('path', {
+           fill: 'currentColor',
+           d: 'M340.864 149.312a30.592 30.592 0 0 0 0 42.752L652.736 512 340.864 831.872a30.592 30.592 0 0 0 0 42.752 29.12 29.12 0 0 0 41.728 0L714.24 534.336a32 32 0 0 0 0-44.672L382.592 149.376a29.12 29.12 0 0 0-41.728 0z'
+         })
+        )
+        )"
       v-bind="utils.addTestId('configPanel:breadcrumb')"
     >
-      <el-breadcrumb-item @click="(index !== bars.length - 1 && item.node.value !== 'placeholder') && handleBreadcrumbClick(item.node)" v-for="(item, index) in bars" :key="index">
+      <el-breadcrumb-item
+        @click="(index !== bars.length - 1 && item.node.value !== 'placeholder') && handleBreadcrumbClick(item.node)" v-for="(item, index) in bars" :key="index">
         {{item.node.value === 'placeholder' ? '...' : item.label}}
       </el-breadcrumb-item>
     </el-breadcrumb>
diff --git a/packages/formEditor/components/Panels/Fields/index.jsx b/packages/formEditor/components/Panels/Fields/index.jsx
index eb01668..3a0312d 100644
--- a/packages/formEditor/components/Panels/Fields/index.jsx
+++ b/packages/formEditor/components/Panels/Fields/index.jsx
@@ -23,7 +23,6 @@ export default {
     const {
       t
     } = hooks.useI18n()
-    // console.log(t('lhf'))
     const {
       state,
       setSelection
@@ -42,7 +41,7 @@ export default {
     const slots = {
       item: ({ element }) => {
         return (
-          <li onClick={() => addStore(element)}>
+          <li class={[ER.props.checkFieldsForNewBadge(element) ? ns.is('new') : '']} onClick={() => addStore(element)}>
             <Icon class={[ns.e('icon')]} icon={element.icon}></Icon>
             <span>{utils.fieldLabel(t, element)}</span>
           </li>
diff --git a/packages/formEditor/componentsConfig.js b/packages/formEditor/componentsConfig.js
index 8422e46..7fea22d 100644
--- a/packages/formEditor/componentsConfig.js
+++ b/packages/formEditor/componentsConfig.js
@@ -563,6 +563,21 @@ export const fieldsConfig = [
           labelHidden: true,
           required: false
         }
+      },
+      {
+        type: 'subform',
+        label: '子表单',
+        icon: 'subform',
+        id: '',
+        list: [
+          []
+        ],
+        options: {
+          defaultValue: [],
+          isShowLabel: true,
+          required: false,
+          disabled: false
+        }
       }
     ]
   }
diff --git a/packages/formEditor/index.vue b/packages/formEditor/index.vue
index 6a25991..43554d7 100644
--- a/packages/formEditor/index.vue
+++ b/packages/formEditor/index.vue
@@ -51,6 +51,10 @@ const props = defineProps(_.merge({
     type: String,
     default: 'icon',
     validator: (value) => ['full', 'icon'].includes(value)
+  },
+  checkFieldsForNewBadge: {
+    type: Function,
+    default: () => {}
   }
 }, defaultProps))
 const layout = {
@@ -229,7 +233,7 @@ const syncLayout = (platform, fn) => {
   layout[isPc ? 'mobile' : 'pc'] = original
   if (_.isEmpty(isPc ? layout.pc : layout.mobile)) {
     // const newData = _.cloneDeep(state.fields.map(e => wrapElement(e, true, false)))
-    const newData = state.fields.map(e => wrapElement(e, true, false, false, false))
+    const newData = state.fields.filter(field => !utils.checkIsInSubform(field)).map(e => wrapElement(e, true, false, false, false))
     fn && fn(newData)
   } else {
     // debugger
@@ -239,7 +243,7 @@ const syncLayout = (platform, fn) => {
       }
     })
     const copyData = _.cloneDeep(isPc ? layout.pc : layout.mobile)
-    const addFields = _.differenceBy(state.fields, layoutFields, 'id')
+    const addFields = _.differenceBy(state.fields.filter(field => !utils.checkIsInSubform(field)), layoutFields, 'id')
     const delFields = _.differenceBy(layoutFields, state.fields, 'id')
     utils.repairLayout(copyData, delFields)
     // console.log(JSON.stringify(copyData, '', 2))
@@ -258,7 +262,7 @@ const getLayoutDataByplatform = (platform) => {
       utils.disassemblyData2(original)
       return original
     } else {
-      const newData = _.cloneDeep(state.fields.map(e => wrapElement(e, true, false, false, false)))
+      const newData = _.cloneDeep(state.fields.filter(field => !utils.checkIsInSubform(field)).map(e => wrapElement(e, true, false, false, false)))
       utils.disassemblyData2(newData)
       return newData
     }
@@ -274,7 +278,7 @@ const getLayoutDataByplatform = (platform) => {
       }
     })
     const copyData = _.cloneDeep(isPc ? layout.pc : layout.mobile)
-    const addFields = _.cloneDeep(_.differenceBy(state.fields, layoutFields, 'id').map(e => wrapElement(e, true, false, false, false)))
+    const addFields = _.cloneDeep(_.differenceBy(state.fields.filter(field => !utils.checkIsInSubform(field)), layoutFields, 'id').map(e => wrapElement(e, true, false, false, false)))
     const delFields = _.differenceBy(layoutFields, state.fields, 'id')
     utils.repairLayout(copyData, delFields)
     utils.disassemblyData2(addFields)
@@ -304,36 +308,24 @@ const fireEvent = (type, data) => {
     data
   })
 }
-provide('Everright', {
-  state,
-  setSelection,
-  props,
-  wrapElement,
-  delField,
-  addField,
-  switchPlatform,
-  addFieldData,
-  canvesScrollRef,
-  fireEvent
-})
 const ns = hooks.useNamespace('Main', state.Namespace)
 const getData1 = () => {
-  return Object.assign(utils.disassemblyData1(_.cloneDeep({
+  return utils.disassemblyData1(_.cloneDeep({
     list: state.store,
     config: state.config,
-    data: state.data
-  })), {
+    data: state.data,
     logic: state.logic
-  })
+  }))
 }
 const getData2 = () => {
+  const fields = utils.processField(_.cloneDeep(state.store))
   layout.pc = getLayoutDataByplatform('pc')
   layout.mobile = getLayoutDataByplatform('mobile')
   return _.cloneDeep({
     layout,
     data: state.data,
     config: state.config,
-    fields: state.fields,
+    fields: fields,
     logic: state.logic
   })
 }
@@ -341,6 +333,7 @@ const setData1 = (data) => {
   if (_.isEmpty(data)) return false
   // stop()
   const newData = utils.combinationData1(_.cloneDeep(data))
+  // console.log(newData.list[0].columns[0].list[0][0].columns[0])
   isShow.value = false
   // console.log(data.list.slice(data.list.length - 1))
   state.store = newData.list
@@ -355,6 +348,7 @@ const setData1 = (data) => {
   })
   nextTick(() => {
     isShow.value = true
+    // setSelection(state.store[0])
     // restart()
   })
 }
@@ -372,6 +366,7 @@ const setData2 = (data) => {
   state.store = curLayout
   state.config = newData.config
   state.data = newData.data
+  state.logic = newData.logic
   setSelection(state.config)
   state.store.forEach((e) => {
     utils.addContext(e, state.store)
@@ -455,6 +450,19 @@ watch(() => state.selected, (newVal) => {
 })
 const onClickOutside = () => {
 }
+provide('Everright', {
+  state,
+  setSelection,
+  props,
+  wrapElement,
+  delField,
+  addField,
+  switchPlatform,
+  addFieldData,
+  canvesScrollRef,
+  fireEvent,
+  getData
+})
 </script>
 <template>
   <el-dialog
diff --git a/packages/formEditor/locale/en.js b/packages/formEditor/locale/en.js
index cdd52e0..04f9905 100644
--- a/packages/formEditor/locale/en.js
+++ b/packages/formEditor/locale/en.js
@@ -33,7 +33,8 @@ export default {
       divider: 'Divider',
       container: 'Container',
       field: 'Basic field',
-      defaultField: 'Default field'
+      defaultField: 'Default field',
+      subform: 'Sub-form'
     },
     layout: {
       tabsCol: 'Tab panel',
@@ -172,7 +173,8 @@ export default {
         alpha: 'Alpha',
         anyNode: 'Select any node',
         clearable: 'clearable',
-        star: 'Star'
+        star: 'Star',
+        setDefaultContent: 'Set default content'
       }
     },
     public: {
diff --git a/packages/formEditor/locale/zh-cn.js b/packages/formEditor/locale/zh-cn.js
index 02d9df0..1a09423 100644
--- a/packages/formEditor/locale/zh-cn.js
+++ b/packages/formEditor/locale/zh-cn.js
@@ -33,7 +33,8 @@ export default {
       divider: '分割线',
       container: '容器',
       field: '基础字段',
-      defaultField: '预设字段'
+      defaultField: '预设字段',
+      subform: '子表单'
     },
     layout: {
       tabsCol: '标签面板',
@@ -172,7 +173,8 @@ export default {
         alpha: '透明度',
         anyNode: '选择任意节点',
         clearable: '一键清除按钮',
-        star: '星星数'
+        star: '星星数',
+        setDefaultContent: '设置默认内容'
       }
     },
     public: {
diff --git a/packages/formEditor/preview.vue b/packages/formEditor/preview.vue
index a3e0c68..fc38f95 100644
--- a/packages/formEditor/preview.vue
+++ b/packages/formEditor/preview.vue
@@ -29,31 +29,24 @@ const state = reactive({
   data: {},
   fields: [],
   logic: {},
-  fieldsLogicState: new Map()
+  fieldsLogicState: new Map(),
+  remoteValues: new Map()
 })
 const ns = hooks.useNamespace('Main', state.Namespace)
 hooks.useLogic(state)
-// const checkFieldsValidation = async () => {
-//   for (const [key, value] of state.fieldsValidation) {
-//     if (value) {
-//       if (utils.isPc()) {
-//         ElMessage({
-//           message: key.value,
-//           type: 'warning'
-//         })
-//       } else {
-//         showNotify({ type: 'warning', message: key.value })
-//       }
-//       return Promise.reject(key)
-//     }
-//   }
-//   return Promise.resolve()
-// }
-// window.checkFieldsValidation = checkFieldsValidation
 const getData = () => {
   const result = {}
   state.fields.forEach(e => {
-    result[e.key] = e.options.defaultValue
+    if (e.type === 'subform') {
+      result[e.key] = utils.getSubFormValues(e)
+    } else {
+      try {
+        if (!utils.checkIsInSubform(e)) {
+          result[e.key] = e.options.defaultValue
+        }
+      } catch (e) {
+      }
+    }
   })
   return _.cloneDeep(result)
 }
@@ -63,12 +56,18 @@ const fireEvent = (type, data) => {
     data
   })
 }
+const setValue = (field, value) => {
+  if (field.type === 'time' && !field.options.valueFormat) {
+    field.options.valueFormat = 'HH:mm:ss'
+  }
+  field.options.defaultValue = value
+}
 provide('Everright', {
   state,
   getData,
   props,
-  fireEvent
-  // checkFieldsValidation
+  fireEvent,
+  setValue
 })
 const setData2 = (data, value) => {
   const newData = _.cloneDeep(data)
@@ -96,7 +95,7 @@ const setData2 = (data, value) => {
     })
   }
 }
-const setData1 = (data, value) => {
+const setData1 = async (data, value) => {
   if (_.isEmpty(data)) return false
   const newData = utils.combinationData1(_.cloneDeep(data))
   state.store = newData.list
@@ -107,13 +106,21 @@ const setData1 = (data, value) => {
   state.store.forEach((e) => {
     utils.addContext(e, state.store)
   })
+  const subforms = _.cloneDeep(state.fields.filter(e => e.type === 'subform'))
+  // For SubformLayout.jsx to get the first data
+  await nextTick()
   if (!_.isEmpty(value)) {
-    state.fields.forEach((e) => {
-      if (e.type === 'time' && !e.options.valueFormat) {
-        e.options.valueFormat = 'HH:mm:ss'
-      }
-      if (value[e.key]) {
-        e.options.defaultValue = value[e.key]
+    for (const key in value) {
+      state.remoteValues.set(key, value[key])
+    }
+    state.fields.forEach((field) => {
+      if (field.type !== 'subform') {
+        try {
+          if (!utils.checkIsInSubform(field)) {
+            setValue(field, value[field.key])
+          }
+        } catch (e) {
+        }
       }
     })
   }
@@ -126,6 +133,7 @@ defineExpose({
   setData,
   getData
 })
+window.state = state
 </script>
 <template>
   <CanvesPanel v-if="state.store.length"></CanvesPanel>
diff --git a/packages/hooks/use-logic/index.js b/packages/hooks/use-logic/index.js
index 490342d..2efd72b 100644
--- a/packages/hooks/use-logic/index.js
+++ b/packages/hooks/use-logic/index.js
@@ -312,7 +312,16 @@ const listenEvent = (state) => {
     rules[type].forEach(rule => {
       const targetFields = findFieldsByid(rule.if.conditions.map(e => e.property), state.fields)
       const operator = (v) => rule.if.logicalOperator === 'and' ? v.every(v => v) : v.some(v => v)
-      // console.log(rule)
+      const subforms = targetFields.filter(field => field.type === 'subform')
+      subforms.forEach(subform => {
+        watch(() => utils.findSubFormAllFields(subform).map(e => e.options.defaultValue), () => {
+          subform.options.defaultValue = utils.getSubFormValues(subform)
+        }, {
+          immediate: true,
+          deep: true,
+          flush: 'sync'
+        })
+      })
       watch(() => targetFields.map(e => e.options.defaultValue), (values) => {
         // console.log(operator(values.map((value, index) => validator(rule.if.conditions[index], value, targetFields[index]))))
         switch (type) {
diff --git a/packages/hooks/use-props/index.js b/packages/hooks/use-props/index.js
index 316796d..1765230 100644
--- a/packages/hooks/use-props/index.js
+++ b/packages/hooks/use-props/index.js
@@ -1,10 +1,22 @@
-import { computed, isRef } from 'vue'
+import { computed, inject, isRef } from 'vue'
 import { showToast } from 'vant'
 import dayjs from 'dayjs'
 import _ from 'lodash-es'
 import Region from '@ER/region/Region'
 import { areaList } from '@vant/area-data'
 import { useI18n } from '../use-i18n'
+import utils from '@ER/utils'
+const findPosition = (node, parent) => {
+  for (let y = 0; y < parent.list.length; y++) {
+    const row = parent.list[y]
+    const x = row.indexOf(node)
+    if (x !== -1) {
+      return { x, y }
+    }
+  }
+
+  return { x: -1, y: -1 }
+}
 const addValidate = (result, node, isPc, t) => {
   const {
     options
@@ -17,17 +29,25 @@ const addValidate = (result, node, isPc, t) => {
       } else {
         const parent = e.context.parent
         let nodes = []
-        if (parent.columns) {
-          nodes = parent.columns
-          result += 'columns.'
-        } else if (parent.list) {
-          nodes = parent.list
-          result += 'list.'
-        } else if (parent.rows) {
-          nodes = parent.rows
-          result += 'rows.'
+        if (parent.type === 'subform') {
+          const {
+            x,
+            y
+          } = findPosition(e, parent)
+          result += `list.${y}.${x}`
+        } else {
+          if (parent.columns) {
+            nodes = parent.columns
+            result += 'columns.'
+          } else if (parent.list) {
+            nodes = parent.list
+            result += 'list.'
+          } else if (parent.rows) {
+            nodes = parent.rows
+            result += 'rows.'
+          }
+          result += nodes.indexOf(e)
         }
-        result += nodes.indexOf(e)
       }
       return result
     }).join('.') + '.options.defaultValue'
@@ -35,31 +55,48 @@ const addValidate = (result, node, isPc, t) => {
 
   const obj = {
   }
-  // if (node.type === 'select') {
-  //   // obj.type = 'array'
-  // }
   const validator = (...arg0) => new Promise((...arg1) => {
-    const resolve = arg1[0]
+    const resolve = () => {
+      arg1[0]()
+    }
     const reject = isPc
       ? arg1[1]
       : (message) => {
           obj.message = message
           arg1[0](false)
         }
-    // const value = options.isShowTrim ? (isPc ? arg0[1] : arg0[0]).trim() : (isPc ? arg0[1] : arg0[0])
-    // let message
-    // let result = true
-    // let msg = ''
     let value = isPc ? arg0[1] : arg0[0]
     // only for mobile
     if (/^(signature|radio|checkbox|select|html)$/.test(node.type)) {
       value = options.defaultValue
     }
     const newValue = options.isShowTrim ? value.trim() : value
-    // if (options.required && (!newValue || newValue === null || newValue === undefined || (Array.isArray(newValue) && !newValue.length))) {
-    if (result.required && (newValue === '' || newValue === null || newValue === undefined || (Array.isArray(newValue) && !newValue.length))) {
-      reject(t('er.validateMsg.required'))
-      return
+    if (node.type === 'subform') {
+      const allFields = utils.findSubFormAllFields(node)
+      if (result.required) {
+        if (allFields.length) {
+          if (allFields.some(e => utils.isEmpty(e.options.isShowTrim ? e.options.defaultValue.trim() : e.options.defaultValue))) {
+            reject(t('er.validateMsg.required'))
+          }
+        } else {
+          reject(t('er.validateMsg.required'))
+        }
+      } else {
+        resolve()
+      }
+    } else {
+      let isRequired = result.required
+      if (utils.checkIsInSubform(node)) {
+        const parent = node?.context?.parent?.context?.parent
+        if (parent) {
+          const parentProps = useProps(state, parent, isPc).value
+          isRequired = parentProps.required
+        }
+      }
+      if (isRequired && node.type !== 'subform' && utils.isEmpty(newValue)) {
+        reject(t('er.validateMsg.required'))
+        return
+      }
     }
     switch (node.type) {
       case 'input':
@@ -126,6 +163,7 @@ export const useProps = (state, data, isPc = true, isRoot = false, specialHandli
     t
   } = useI18n()
   return computed(() => {
+    const ExtraParams = inject('EverrightExtraParams', {})
     let node = isRoot ? data.config : data
     let result = {}
     const platform = isPc ? 'pc' : 'mobile'
@@ -167,6 +205,20 @@ export const useProps = (state, data, isPc = true, isRoot = false, specialHandli
         result.required = result.disabled ? false : required === 1
       }
     }
+    if (utils.checkIsInSubform(node)) {
+      const parent = node?.context?.parent?.context?.parent
+      if (parent) {
+        const parentProps = useProps(state, parent, isPc).value
+        result.disabled = parentProps.disabled
+        result.required = parentProps.required
+      }
+    }
+    try {
+      if (ExtraParams.inSubformDefaultValueComponent) {
+        result.disabled = result.required = false
+      }
+    } catch (e) {
+    }
     addValidate(result, node, isPc, t)
     if (isPc) {
       result.labelWidth = options.isShowLabel ? options.labelWidth + 'px' : 'auto'
diff --git a/packages/hooks/use-target/index.js b/packages/hooks/use-target/index.js
index 21f3eb3..c3e58cb 100644
--- a/packages/hooks/use-target/index.js
+++ b/packages/hooks/use-target/index.js
@@ -97,6 +97,11 @@ export const useTarget = () => {
       return /^(edit|config)$/.test(state.mode)
     }
   })
+  const isSelectSubform = computed({
+    get () {
+      return checkTypeBySelected(['subform'])
+    }
+  })
   return {
     state,
     setSelection,
@@ -113,6 +118,7 @@ export const useTarget = () => {
     isSelectTable,
     isSelectRoot,
     isPc,
-    isEditModel
+    isEditModel,
+    isSelectSubform
   }
 }
diff --git a/packages/icon/svg/subform.svg b/packages/icon/svg/subform.svg
new file mode 100644
index 0000000..7d8dffe
--- /dev/null
+++ b/packages/icon/svg/subform.svg
@@ -0,0 +1 @@
+<svg fill="currentColor" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><path d="M10 28V35H18" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M18 28H42V42H18V35V28Z" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="6" y1="13.5" x2="6" y2="12.5" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="6" y1="20" x2="6" y2="19" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="6" y1="7" x2="6" y2="6" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="32" y1="13.5" x2="32" y2="12.5" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="32" y1="20" x2="32" y2="19" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="32" y1="7" x2="32" y2="6" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="32" y1="20" x2="31" y2="20" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="7" y1="20" x2="6" y2="20" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="7" y1="6" x2="6" y2="6" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="13" y1="6" x2="12" y2="6" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="19.5" y1="6" x2="18.5" y2="6" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="19.5" y1="20" x2="18.5" y2="20" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="26" y1="6" x2="25" y2="6" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="13" y1="20" x2="12" y2="20" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="26" y1="20" x2="25" y2="20" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="32" y1="6" x2="31" y2="6" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>
diff --git a/packages/region/index.vue b/packages/region/index.vue
index 0af98ec..c1f4baf 100644
--- a/packages/region/index.vue
+++ b/packages/region/index.vue
@@ -301,7 +301,7 @@ onMounted(() => {
               :class="[ns.e('icon'), 'icon-circle-close']"
               @click.stop="handleClear"
             >
-              <circle-close />
+              <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728=""><path fill="currentColor" d="m466.752 512-90.496-90.496a32 32 0 0 1 45.248-45.248L512 466.752l90.496-90.496a32 32 0 1 1 45.248 45.248L557.248 512l90.496 90.496a32 32 0 1 1-45.248 45.248L512 557.248l-90.496 90.496a32 32 0 0 1-45.248-45.248L466.752 512z"></path><path fill="currentColor" d="M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768zm0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896z"></path></svg>
             </el-icon>
             <el-icon
               v-else
@@ -313,7 +313,7 @@ onMounted(() => {
               ]"
               @click.stop="togglePopperVisible()"
             >
-              <arrow-down />
+              <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728=""><path fill="currentColor" d="M831.872 340.864 512 652.672 192.128 340.864a30.592 30.592 0 0 0-42.752 0 29.12 29.12 0 0 0 0 41.6L489.664 714.24a32 32 0 0 0 44.672 0l340.288-331.712a29.12 29.12 0 0 0 0-41.728 30.592 30.592 0 0 0-42.752 0z"></path></svg>
             </el-icon>
           </template>
         </el-input>
@@ -397,7 +397,7 @@ onMounted(() => {
                   <span :class="[ns.e('label')]">{{node.label}}</span>
                   <template v-if="!node.isLeaf">
                     <el-icon :class="['arrow-right', ns.e('postfix')]">
-                      <arrow-right />
+                      <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728=""><path fill="currentColor" d="M340.864 149.312a30.592 30.592 0 0 0 0 42.752L652.736 512 340.864 831.872a30.592 30.592 0 0 0 0 42.752 29.12 29.12 0 0 0 41.728 0L714.24 534.336a32 32 0 0 0 0-44.672L382.592 149.376a29.12 29.12 0 0 0-41.728 0z"></path></svg>
                     </el-icon>
                   </template>
                 </li>
diff --git a/packages/theme/formEditor/ConfigSubformDefaultValueComponent.scss b/packages/theme/formEditor/ConfigSubformDefaultValueComponent.scss
new file mode 100644
index 0000000..06b7132
--- /dev/null
+++ b/packages/theme/formEditor/ConfigSubformDefaultValueComponent.scss
@@ -0,0 +1,14 @@
+@include b(ConfigSubformDefaultValueComponent) {
+  @include e(button) {
+    width: calc(100% - 20px);
+    border: none;
+    margin: 10px;
+    box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.1) !important;
+    &:focus {
+      background-color: transparent;
+    }
+    &:hover {
+      background: var(--el-button-hover-bg-color);
+    }
+  }
+}
diff --git a/packages/theme/formEditor/Fields.scss b/packages/theme/formEditor/Fields.scss
index c50c058..831fa22 100644
--- a/packages/theme/formEditor/Fields.scss
+++ b/packages/theme/formEditor/Fields.scss
@@ -21,7 +21,20 @@
     justify-content: space-between;
     flex-wrap: wrap;
     li {
-      overflow: hidden;
+      @include when(new) {
+        &::after {
+          content: 'new';
+          position: absolute;
+          top: -6px;
+          right: 0;
+          color: #fff;
+          background-color: #f00;
+          font-size: 12px;
+          padding: 0 6px;
+          border-radius: 10px;
+        }
+      }
+      position: relative;
       border-radius: 4px;
       margin: 4px;
       background: #F9F9F9;
diff --git a/packages/theme/formEditor/SubformLayout.scss b/packages/theme/formEditor/SubformLayout.scss
new file mode 100644
index 0000000..cb12dc9
--- /dev/null
+++ b/packages/theme/formEditor/SubformLayout.scss
@@ -0,0 +1,58 @@
+@include b(SubformLayout) {
+  @include e(content) {
+    width: 100%;
+  }
+  @include e(item) {
+    position: relative;
+    [data-layout-type="subform"] {
+      padding-left: 50px;
+    }
+  }
+  .el-form-item {
+    display: block;
+    .el-form-item__label {
+      display: block;
+      height: auto;
+      text-align: left;
+      margin-bottom: 8px;
+      line-height: 22px;
+    }
+  }
+  @include e(button) {
+    width: 50px;
+    top: 10px;
+    left: 10px;
+    position: absolute;
+    button {
+      margin: 0 !important;
+    }
+    button:last-child {
+      display: none;
+      svg {
+        color: #ffffff;
+      }
+    }
+  }
+  @include e(edit) {
+    &:hover {
+      background: #F9F9F9;
+      .#{$namespace}-SubformLayout__button {
+        button:first-child {
+          display: none;
+        }
+        button:last-child {
+          display: inline-flex;
+        }
+      }
+    }
+  }
+  @include e(addButton) {
+    padding-top: 20px;
+    //width: 100%;
+    //button {
+    //  //border: 1px dashed #409EFF;
+    //  width: 100%;
+    //  box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.1);
+    //}
+  }
+}
diff --git a/packages/theme/formEditor/index.scss b/packages/theme/formEditor/index.scss
index 2222462..c400a9d 100644
--- a/packages/theme/formEditor/index.scss
+++ b/packages/theme/formEditor/index.scss
@@ -15,6 +15,7 @@ $namespace: 'Everright-formEditor';
 @import './TabsLayout';
 @import './CollapseLayout';
 @import './InlineLayout';
+@import './SubformLayout';
 @import './DragGableLayout';
 @import './SelectElement';
 @import './FormTypes/Signature';
@@ -37,4 +38,5 @@ $namespace: 'Everright-formEditor';
 @import './ConfigCollapseComponent';
 @import './ConfigTypeComponent';
 @import './ConfigLogicComponent';
+@import './ConfigSubformDefaultValueComponent';
 
diff --git a/packages/utils/addContext.js b/packages/utils/addContext.js
index c4232ce..baebc9d 100644
--- a/packages/utils/addContext.js
+++ b/packages/utils/addContext.js
@@ -3,6 +3,7 @@ import dayjs from 'dayjs'
 import _ from 'lodash-es'
 import { nanoid } from './nanoid'
 import { wrapElement } from './field'
+import utils from '@ER/utils/index.js'
 
 const getNodes = (node, key) => {
   const {
@@ -595,11 +596,11 @@ export const addContext = (node, parent, fn) => {
       arr.splice(index + 1, 0, newNode)
     },
     delete () {
-      // console.log(123123)
-      arr.splice(arr.indexOf(node), 1)
-      // if (node.context.parent.type === 'inline' && !arr.length) {
-      //   node.context.parent.context.delete()
-      // }
+      if (utils.checkIsInSubform(node) && node.type === 'inline') {
+        arr[0].splice(arr[0].indexOf(node), 1)
+      } else {
+        arr.splice(arr.indexOf(node), 1)
+      }
     },
     appendCol () {
       const newNode = wrapElement({
@@ -874,7 +875,12 @@ export const addContext = (node, parent, fn) => {
     enumerable: false,
     configurable: true
   })
-  const nodes = node.columns || node.list || node.rows || []
+  let nodes = []
+  if (node.type === 'subform') {
+    nodes = node.list[0]
+  } else {
+    nodes = node.columns || node.list || node.rows || []
+  }
   nodes.forEach(e => {
     addContext(e, node, fn)
   })
diff --git a/packages/utils/field.js b/packages/utils/field.js
index 2e057ef..277d54e 100644
--- a/packages/utils/field.js
+++ b/packages/utils/field.js
@@ -1,9 +1,9 @@
 import _ from 'lodash-es'
 import { nanoid } from './nanoid'
-const fieldsRe = /^(input|textarea|number|radio|checkbox|select|time|date|rate|switch|slider|html|cascader|uploadfile|signature|region)$/
+const fieldsRe = /^(input|textarea|number|radio|checkbox|select|time|date|rate|switch|slider|html|cascader|uploadfile|signature|region|subform)$/
 const deepTraversal = (node, fn) => {
   fn(node)
-  const nodes = node.list || node.rows || node.columns || node.children || []
+  const nodes = node.type === 'subform' ? node.list[0] : (node.list || node.rows || node.columns || node.children || [])
   nodes.forEach(e => {
     deepTraversal(e, fn)
   })
@@ -11,6 +11,7 @@ const deepTraversal = (node, fn) => {
 const wrapElement = (element, fn) => {
   const result = element
   deepTraversal(result, (node) => {
+    if (Array.isArray(node)) return false
     if (!node.style) {
       node.style = {}
     }
@@ -72,7 +73,7 @@ const flatNodes = (nodes, excludes, fn, excludesFn) => {
     } else {
       excludesFn && excludesFn(nodes, node, currentIndex)
     }
-    const children = node.list || node.rows || node.columns || node.children || []
+    const children = node.type === 'subform' ? node.list[0] : (node.list || node.rows || node.columns || node.children || [])
     res = res.concat(flatNodes(children, excludes, fn, excludesFn))
     return res
   }, [])
@@ -81,14 +82,16 @@ const getAllFields = (store) => flatNodes(store, excludes)
 const pickfields = (list) => {
   return flatNodes(list, excludes)
 }
+const processField = (list) => flatNodes(list, excludes, (nodes, node, currentIndex) => {
+  nodes[currentIndex] = node.id
+})
 const disassemblyData1 = (data) => {
   const result = {
     list: data.list,
     config: data.config,
-    fields: flatNodes(data.list, excludes, (nodes, node, currentIndex) => {
-      nodes[currentIndex] = node.id
-    }),
-    data: data.data
+    fields: processField(data.list),
+    data: data.data,
+    logic: data.logic
   }
   return result
 }
@@ -100,21 +103,29 @@ const combinationData1 = (data) => {
     fields: data.fields,
     logic: data.logic
   }
-  flatNodes(data.list, excludes, (nodes, node, currentIndex) => {
+  const fn = (nodes, node, currentIndex) => {
     const cur = _.find(data.fields, { id: node })
     if (!_.isEmpty(cur)) {
+      if (cur.type === 'subform') {
+        flatNodes(cur.list[0], excludes, fn)
+      }
       nodes[currentIndex] = cur
     }
-  })
+  }
+  flatNodes(data.list, excludes, fn)
   return result
 }
 const combinationData2 = (list, fields) => {
-  flatNodes(list, excludes, (nodes, node, currentIndex) => {
+  const fn = (nodes, node, currentIndex) => {
     const cur = _.find(fields, { id: node })
     if (!_.isEmpty(cur)) {
+      if (cur.type === 'subform') {
+        flatNodes(cur.list[0], excludes, fn)
+      }
       nodes[currentIndex] = cur
     }
-  })
+  }
+  flatNodes(list, excludes, fn)
 }
 const repairLayout = (layout, fields) => {
   flatNodes(layout, excludes, (nodes, node, currentIndex) => {
@@ -196,6 +207,40 @@ const transferData = (lang, path, locale, options = {}) => {
   return result
 }
 const isNull = (e) => e === '' || e === null || e === undefined
+const checkIsInSubform = (node) => {
+  if (!node) return false
+  let result = false
+  let parent = node.context.parent
+  while (parent && !result) {
+    if (parent.type === 'subform') {
+      result = true
+    }
+    parent = parent.context?.parent
+  }
+  return result
+}
+const getSubFormValues = (subform) => subform.list.map(e => {
+  const cur = {}
+  const children = []
+  e.forEach(e => {
+    e.columns.forEach(e => {
+      children.push(e)
+    })
+  })
+  children.forEach(e => {
+    cur[e.key] = e.options.defaultValue
+  })
+  return cur
+})
+const findSubFormAllFields = (subform) => {
+  const result = []
+  subform.list.forEach(e => {
+    e.forEach(e => {
+      result.push(...e.columns)
+    })
+  })
+  return result
+}
 export {
   syncWidthByPlatform,
   wrapElement,
@@ -213,5 +258,9 @@ export {
   transferData,
   transferLabelPath,
   isNull,
-  repairLayout
+  repairLayout,
+  checkIsInSubform,
+  getSubFormValues,
+  findSubFormAllFields,
+  processField
 }
diff --git a/packages/utils/generateOptions.js b/packages/utils/generateOptions.js
index 79c39a3..bd82d6b 100644
--- a/packages/utils/generateOptions.js
+++ b/packages/utils/generateOptions.js
@@ -1,4 +1,6 @@
 import { nanoid } from './nanoid'
+import { globalConfig } from '@ER/formEditor/componentsConfig.js'
+import _ from 'lodash-es'
 export const generateOptions = (len) => {
   const result = []
   while (len--) {
@@ -9,3 +11,16 @@ export const generateOptions = (len) => {
   }
   return result
 }
+export const generateData = (layoutType = 1) => {
+  const result = {
+    config: _.cloneDeep(globalConfig)
+  }
+  result.logic = result.data = {}
+  if (layoutType === 1) {
+    result.list = []
+  }
+  if (layoutType === 2) {
+    result.layout = []
+  }
+  return result
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index feba837..ecf65f7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -10,7 +10,6 @@ specifiers:
   '@ckeditor/ckeditor5-vue': ^4.0.1
   '@commitlint/cli': ^17.4.4
   '@commitlint/config-conventional': ^17.4.4
-  '@element-plus/icons-vue': ^2.0.10
   '@vant/area-data': ^1.4.0
   '@vant/touch-emulator': ^1.4.0
   '@vitejs/plugin-vue': ^3.2.0
@@ -34,7 +33,7 @@ specifiers:
   eslint-plugin-n: ^15.6.1
   eslint-plugin-promise: ^6.1.1
   eslint-plugin-vue: ^9.9.0
-  everright-filter: ^0.0.22
+  everright-filter: ^1.1.1
   express: ^4.18.2
   husky: ^8.0.3
   jsdom: ^22.0.0
@@ -68,7 +67,7 @@ dependencies:
   canvas: 2.11.2
   dayjs: registry.npmmirror.com/dayjs/1.11.7
   element-plus: registry.npmmirror.com/element-plus/2.3.1_vue@3.2.47
-  everright-filter: 0.0.22_ap7lbsygcc6j7vt74w6nzwfhdi
+  everright-filter: 1.1.1_ap7lbsygcc6j7vt74w6nzwfhdi
   jss: registry.npmmirror.com/jss/10.10.0
   jss-preset-default: registry.npmmirror.com/jss-preset-default/10.10.0
   lodash-es: registry.npmmirror.com/lodash-es/4.17.21
@@ -90,7 +89,6 @@ devDependencies:
   '@ckeditor/ckeditor5-vue': registry.npmmirror.com/@ckeditor/ckeditor5-vue/4.0.1
   '@commitlint/cli': registry.npmmirror.com/@commitlint/cli/17.4.4
   '@commitlint/config-conventional': registry.npmmirror.com/@commitlint/config-conventional/17.4.4
-  '@element-plus/icons-vue': registry.npmmirror.com/@element-plus/icons-vue/2.1.0_vue@3.2.47
   '@vitejs/plugin-vue': registry.npmmirror.com/@vitejs/plugin-vue/3.2.0_vite@3.2.5+vue@3.2.47
   '@vitejs/plugin-vue-jsx': registry.npmmirror.com/@vitejs/plugin-vue-jsx/2.1.1_vite@3.2.5+vue@3.2.47
   '@vue/compiler-sfc': registry.npmmirror.com/@vue/compiler-sfc/3.2.47
@@ -280,10 +278,6 @@ packages:
     dev: true
     optional: true
 
-  /@vue/devtools-api/6.5.0:
-    resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==}
-    dev: false
-
   /@vue/server-renderer/3.2.47_vue@3.2.47:
     resolution: {integrity: sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==}
     requiresBuild: true
@@ -823,8 +817,8 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
-  /everright-filter/0.0.22_ap7lbsygcc6j7vt74w6nzwfhdi:
-    resolution: {integrity: sha512-wVKk4FCgB1G6owZwByz/wFqeQ3Gx2UzIVqd2OwElizxaT7fX2n/KpghE5gs6YiyenupQZRfsxCv2Ou77UFNJCw==}
+  /everright-filter/1.1.1_ap7lbsygcc6j7vt74w6nzwfhdi:
+    resolution: {integrity: sha512-xQGZ15FcLVw8ja54JiL6nLlvMdviGXketkNwUtfzV+Hvxt0cgr10bKZcaEES1aGzfw+qo5W6c5s1bVebdCL66g==}
     peerDependencies:
       element-plus: ^2.2.28
       vue: ^3.2.45
@@ -839,8 +833,6 @@ packages:
       lodash-es: 4.17.21
       nzh: 1.0.8
       vue: registry.npmmirror.com/vue/3.2.47
-      vue-router: 4.1.6_vue@3.2.47
-      vuedraggable: 4.1.0_vue@3.2.47
       written-number: 0.11.1
     transitivePeerDependencies:
       - '@vue/composition-api'
@@ -1090,7 +1082,6 @@ packages:
 
   /lodash-es/4.17.21:
     resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
-    dev: false
 
   /lodash/4.17.21:
     resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
@@ -1441,10 +1432,6 @@ packages:
       once: 1.4.0
       simple-concat: 1.0.1
 
-  /sortablejs/1.14.0:
-    resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==}
-    dev: false
-
   /source-map-js/1.0.2:
     resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
     engines: {node: '>=0.10.0'}
@@ -1735,24 +1722,6 @@ packages:
       vue: registry.npmmirror.com/vue/3.2.47
     dev: false
 
-  /vue-router/4.1.6_vue@3.2.47:
-    resolution: {integrity: sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==}
-    peerDependencies:
-      vue: ^3.2.0
-    dependencies:
-      '@vue/devtools-api': 6.5.0
-      vue: registry.npmmirror.com/vue/3.2.47
-    dev: false
-
-  /vuedraggable/4.1.0_vue@3.2.47:
-    resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
-    peerDependencies:
-      vue: ^3.0.1
-    dependencies:
-      sortablejs: 1.14.0
-      vue: registry.npmmirror.com/vue/3.2.47
-    dev: false
-
   /w3c-xmlserializer/4.0.0:
     resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==}
     engines: {node: '>=14'}
@@ -2408,7 +2377,7 @@ packages:
       '@ckeditor/ckeditor5-engine': registry.npmmirror.com/@ckeditor/ckeditor5-engine/35.4.0
       '@ckeditor/ckeditor5-utils': registry.npmmirror.com/@ckeditor/ckeditor5-utils/35.4.0
       '@ckeditor/ckeditor5-widget': registry.npmmirror.com/@ckeditor/ckeditor5-widget/35.4.0
-      lodash-es: registry.npmmirror.com/lodash-es/4.17.21
+      lodash-es: 4.17.21
     dev: true
 
   registry.npmmirror.com/@ckeditor/ckeditor5-cloud-services/35.4.0:
@@ -2429,7 +2398,7 @@ packages:
       '@ckeditor/ckeditor5-engine': registry.npmmirror.com/@ckeditor/ckeditor5-engine/35.4.0
       '@ckeditor/ckeditor5-ui': registry.npmmirror.com/@ckeditor/ckeditor5-ui/35.4.0
       '@ckeditor/ckeditor5-utils': registry.npmmirror.com/@ckeditor/ckeditor5-utils/35.4.0
-      lodash-es: registry.npmmirror.com/lodash-es/4.17.21
+      lodash-es: 4.17.21
     dev: true
 
   registry.npmmirror.com/@ckeditor/ckeditor5-easy-image/35.4.0:
@@ -2478,7 +2447,7 @@ packages:
     engines: {node: '>=14.0.0', npm: '>=5.7.1'}
     dependencies:
       '@ckeditor/ckeditor5-utils': registry.npmmirror.com/@ckeditor/ckeditor5-utils/35.4.0
-      lodash-es: registry.npmmirror.com/lodash-es/4.17.21
+      lodash-es: 4.17.21
     dev: true
 
   registry.npmmirror.com/@ckeditor/ckeditor5-enter/35.4.0:
@@ -2630,7 +2599,7 @@ packages:
     dependencies:
       '@ckeditor/ckeditor5-core': registry.npmmirror.com/@ckeditor/ckeditor5-core/35.4.0
       '@ckeditor/ckeditor5-utils': registry.npmmirror.com/@ckeditor/ckeditor5-utils/35.4.0
-      lodash-es: registry.npmmirror.com/lodash-es/4.17.21
+      lodash-es: 4.17.21
     dev: true
 
   registry.npmmirror.com/@ckeditor/ckeditor5-undo/35.4.0:
@@ -2661,7 +2630,7 @@ packages:
     version: 35.4.0
     engines: {node: '>=14.0.0', npm: '>=5.7.1'}
     dependencies:
-      lodash-es: registry.npmmirror.com/lodash-es/4.17.21
+      lodash-es: 4.17.21
     dev: true
 
   registry.npmmirror.com/@ckeditor/ckeditor5-vue/4.0.1:
@@ -2683,7 +2652,7 @@ packages:
       '@ckeditor/ckeditor5-typing': registry.npmmirror.com/@ckeditor/ckeditor5-typing/35.4.0
       '@ckeditor/ckeditor5-ui': registry.npmmirror.com/@ckeditor/ckeditor5-ui/35.4.0
       '@ckeditor/ckeditor5-utils': registry.npmmirror.com/@ckeditor/ckeditor5-utils/35.4.0
-      lodash-es: registry.npmmirror.com/lodash-es/4.17.21
+      lodash-es: 4.17.21
     dev: true
 
   registry.npmmirror.com/@commitlint/cli/17.4.4:
@@ -2913,6 +2882,7 @@ packages:
       vue: ^3.2.0
     dependencies:
       vue: registry.npmmirror.com/vue/3.2.47
+    dev: false
 
   registry.npmmirror.com/@eslint-community/eslint-utils/4.3.0_eslint@8.36.0:
     resolution: {integrity: sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.3.0.tgz}
@@ -3447,7 +3417,7 @@ packages:
     name: '@vueuse/shared'
     version: 9.13.0
     dependencies:
-      vue-demi: registry.npmmirror.com/vue-demi/0.13.11_vue@3.2.47
+      vue-demi: 0.13.11_vue@3.2.47
     transitivePeerDependencies:
       - '@vue/composition-api'
       - vue
diff --git a/test/__snapshots__/erGeneratorData.test.js.snap b/test/__snapshots__/erGeneratorData.test.js.snap
index 7b8d706..8e467b0 100644
--- a/test/__snapshots__/erGeneratorData.test.js.snap
+++ b/test/__snapshots__/erGeneratorData.test.js.snap
@@ -767,6 +767,39 @@ exports[`Generate field data: > Slider 1`] = `
 }
 `;
 
+exports[`Generate field data: > Subform 1`] = `
+{
+  "columns": [
+    {
+      "icon": "subform",
+      "id": "{{test-id-nanoid}}",
+      "key": "subform_{{test-id-nanoid}}",
+      "label": "Sub-form",
+      "list": [
+        [],
+      ],
+      "options": {
+        "defaultValue": [],
+        "disabled": false,
+        "isShowLabel": true,
+        "required": false,
+      },
+      "style": {
+        "width": {
+          "mobile": "100%",
+          "pc": "100%",
+        },
+      },
+      "type": "subform",
+    },
+  ],
+  "id": "{{test-id-nanoid}}",
+  "key": "inline_{{test-id-nanoid}}",
+  "style": {},
+  "type": "inline",
+}
+`;
+
 exports[`Generate field data: > Switch 1`] = `
 {
   "columns": [
diff --git a/test/disassemblyData/layoutType1.test.js b/test/disassemblyData/layoutType1.test.js
new file mode 100644
index 0000000..1828b8b
--- /dev/null
+++ b/test/disassemblyData/layoutType1.test.js
@@ -0,0 +1,77 @@
+import { describe, assert, expect, test, beforeEach, beforeAll, vi } from 'vitest'
+import erGeneratorData from '@ER/formEditor/generatorData.js'
+import * as erComponentsConfig from '@ER/formEditor/componentsConfig.js'
+import _ from 'lodash-es'
+import { nextTick } from 'vue'
+import { _mount, wrapLayoutDataByLayoutType } from '@ER-test/utils.js'
+describe('Fields and layout not separated', () => {
+  let wrapper = {}
+  const handleListener = vi.fn()
+  let field = {}
+  beforeAll(() => {
+    vi.stubEnv('TESTIDTYPE', 'nanoid')
+    field = erGeneratorData(erComponentsConfig.fieldsConfig[0].list[0], true, 'en')
+    wrapper = _mount(`
+      <er-form-editor
+        @listener="handleListener"
+        :layoutType="1"
+        ref="EReditorRef"/>
+      `, () => ({
+      handleListener
+    })
+    )
+    return () => {
+      vi.stubEnv('TESTIDTYPE', '')
+    }
+  })
+  test('Only fields without layout', async () => {
+    const newField = _.cloneDeep(field)
+    newField.columns[0] = newField.columns[0].id
+    const data = wrapLayoutDataByLayoutType([newField], [field.columns[0]])
+    wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    await nextTick()
+    expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data)
+  })
+  test('Grid', async () => {
+    const data = wrapLayoutDataByLayoutType([erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[0]), true, 'en')], [field.columns[0]])
+    data.list[0].columns[0].columns[0].list.push(field.columns[0].id)
+    wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    await nextTick()
+    expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data)
+  })
+  test('Table', async () => {
+    const data = wrapLayoutDataByLayoutType([erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[1]), true, 'en')], [field.columns[0]])
+    data.list[0].columns[0].rows[0].columns[0].list.push(field.columns[0].id)
+    wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    await nextTick()
+    expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data)
+  })
+  test('Tabs', async () => {
+    const data = wrapLayoutDataByLayoutType([erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[2]), true, 'en')], [field.columns[0]])
+    data.list[0].columns[0].columns[0].list.push(field.columns[0].id)
+    wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    await nextTick()
+    expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data)
+  })
+  test('nested layout:Grid > Grid > field', async () => {
+    const grid0 = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[0]), true, 'en')
+    const grid1 = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[0]), true, 'en')
+    grid0.columns[0].columns[0].list.push(grid1)
+    grid1.columns[0].columns[0].list.push(field.columns[0].id)
+    const data = wrapLayoutDataByLayoutType([grid0], [field.columns[0]])
+    wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    await nextTick()
+    expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data)
+  })
+  test('Sub-form', async () => {
+    const newField = _.cloneDeep(field)
+    newField.columns[0] = newField.columns[0].id
+    const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en')
+    const list = _.cloneDeep(subForm)
+    list.columns[0] = subForm.columns[0].id
+    subForm.columns[0].list[0].push(newField)
+    const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0], field.columns[0]])
+    wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data)
+  })
+})
diff --git a/test/disassemblyData/layoutType2.test.js b/test/disassemblyData/layoutType2.test.js
new file mode 100644
index 0000000..96a3293
--- /dev/null
+++ b/test/disassemblyData/layoutType2.test.js
@@ -0,0 +1,112 @@
+import { describe, assert, expect, test, beforeEach, beforeAll, vi } from 'vitest'
+import erGeneratorData from '@ER/formEditor/generatorData.js'
+import * as erComponentsConfig from '@ER/formEditor/componentsConfig.js'
+import _ from 'lodash-es'
+import { nextTick } from 'vue'
+import { _mount, wrapLayoutDataByLayoutType } from '@ER-test/utils.js'
+describe('Fields and layout separated', () => {
+  let wrapper = {}
+  const handleListener = vi.fn()
+  let field = {}
+  beforeAll(() => {
+    vi.stubEnv('TESTIDTYPE', 'nanoid')
+    field = erGeneratorData(erComponentsConfig.fieldsConfig[0].list[0], true, 'en')
+    wrapper = _mount(`
+      <er-form-editor
+        @listener="handleListener"
+        :layoutType="2"
+        ref="EReditorRef"/>
+      `, () => ({
+      handleListener
+    })
+    )
+    return () => {
+      vi.stubEnv('TESTIDTYPE', '')
+    }
+  })
+  test('Only fields without layout', async () => {
+    const newField = _.cloneDeep(field)
+    newField.columns[0] = newField.columns[0].id
+    const data = wrapLayoutDataByLayoutType({
+      pc: [
+        newField
+      ],
+      mobile: [
+        newField
+      ]
+    }, [field.columns[0]], 2)
+    wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    await nextTick()
+    expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data)
+  })
+  test('Grid', async () => {
+    const newField = _.cloneDeep(field)
+    newField.columns[0] = newField.columns[0].id
+    const layout = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[0]), true, 'en')
+    layout.columns[0].columns[0].list.push(newField)
+    const data = wrapLayoutDataByLayoutType({
+      pc: [
+        layout
+      ],
+      mobile: [
+        layout
+      ]
+    }, [field.columns[0]], 2)
+    wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    await nextTick()
+    expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data)
+  })
+  test('Table', async () => {
+    const newField = _.cloneDeep(field)
+    newField.columns[0] = newField.columns[0].id
+    const layout = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[1]), true, 'en')
+    layout.columns[0].rows[0].columns[0].list.push(newField)
+    const data = wrapLayoutDataByLayoutType({
+      pc: [
+        layout
+      ],
+      mobile: [
+        layout
+      ]
+    }, [field.columns[0]], 2)
+    wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    await nextTick()
+    expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data)
+  })
+  test('Tabs', async () => {
+    const newField = _.cloneDeep(field)
+    newField.columns[0] = newField.columns[0].id
+    const layout = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[2]), true, 'en')
+    layout.columns[0].columns[0].list.push(newField)
+    const data = wrapLayoutDataByLayoutType({
+      pc: [
+        layout
+      ],
+      mobile: [
+        layout
+      ]
+    }, [field.columns[0]], 2)
+    wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    await nextTick()
+    expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data)
+  })
+  test('nested layout:Grid > Grid > field', async () => {
+    const newField = _.cloneDeep(field)
+    newField.columns[0] = newField.columns[0].id
+    const grid0 = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[0]), true, 'en')
+    const grid1 = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[0]), true, 'en')
+    grid0.columns[0].columns[0].list.push(grid1)
+    grid1.columns[0].columns[0].list.push(newField)
+    const data = wrapLayoutDataByLayoutType({
+      pc: [
+        grid0
+      ],
+      mobile: [
+        grid0
+      ]
+    }, [field.columns[0]], 2)
+    wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    await nextTick()
+    expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data)
+  })
+})
diff --git a/test/erFormConfig.test.js b/test/erFormConfig.test.js
index b5f3ce3..5b006ba 100644
--- a/test/erFormConfig.test.js
+++ b/test/erFormConfig.test.js
@@ -1,7 +1,6 @@
 import { describe, assert, expect, test, beforeEach, vi, beforeAll, afterEach } from 'vitest'
 import { erComponentsConfig, erFormConfig, erGeneratorData, utils } from '@ER/formEditor/index.js'
 import { mount } from '@vue/test-utils'
-import { Plus } from '@element-plus/icons-vue'
 import ElementPlus from 'element-plus'
 import Vant from 'vant'
 import { computed, nextTick, reactive, ref } from 'vue'
@@ -18,9 +17,6 @@ const _mount = (template, data, otherObj) => mount(
   {
     attachTo: 'body',
     global: {
-      components: {
-        Plus
-      },
       plugins: [
         ElementPlus,
         Vant
@@ -2659,4 +2655,98 @@ describe('Configuration options owned by the config panel', () => {
     expect(wrapper.find(utils.getTestId('configPanel:accordion')).exists()).toBe(false)
     expect(wrapper.find(utils.getTestId('configPanel:contentPosition')).exists()).toBe(true)
   })
+  test('Subform:pc', async () => {
+    value0.value = store.layouts[9].id
+    await nextTick()
+    expect(wrapper.find(utils.getTestId('configPanel:breadcrumb')).findAll('.el-breadcrumb__item').map(e => e.text())).toEqual(['Form Attribute', 'Sub-form'])
+    expect(wrapper.find(utils.getTestId('configPanel:id')).exists()).toBe(true)
+    expect(wrapper.find(utils.getTestId('configPanel:title')).exists()).toBe(true)
+    expect(wrapper.find(utils.getTestId('configPanel:titleWidth')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:defaultValue')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:placeholder')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:isShowTrim')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:required')).exists()).toBe(true)
+    expect(wrapper.find(utils.getTestId('configPanel:disabled')).exists()).toBe(true)
+    expect(wrapper.find(utils.getTestId('configPanel:clearable')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:width')).exists()).toBe(true)
+    expect(wrapper.find(utils.getTestId('configPanel:prepend')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:append')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:textareaHeight')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:wordLimit')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:controls')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:dataEntry')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:displayStyle')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:multiple')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:format')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:dateType')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:dateRange')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:star')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:allowHalf')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:sliderCount')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:step')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:precision')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:filterable')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:anyNode')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:accept')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:uploadLimit')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:fileSize')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:brushColor')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:justify')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:margin')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:padding')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:background')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:borderLine')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:tabsType')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:tabPosition')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:dataEntry3')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:accordion')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:contentPosition')).exists()).toBe(false)
+  })
+  test('Subform:mobile', async () => {
+    value0.value = store.layouts[9].id
+    await nextTick()
+    expect(wrapper.find(utils.getTestId('configPanel:breadcrumb')).findAll('.el-breadcrumb__item').map(e => e.text())).toEqual(['Form Attribute', 'Sub-form'])
+    expect(wrapper.find(utils.getTestId('configPanel:id')).exists()).toBe(true)
+    expect(wrapper.find(utils.getTestId('configPanel:title')).exists()).toBe(true)
+    expect(wrapper.find(utils.getTestId('configPanel:titleWidth')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:defaultValue')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:placeholder')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:isShowTrim')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:required')).exists()).toBe(true)
+    expect(wrapper.find(utils.getTestId('configPanel:disabled')).exists()).toBe(true)
+    expect(wrapper.find(utils.getTestId('configPanel:clearable')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:width')).exists()).toBe(true)
+    expect(wrapper.find(utils.getTestId('configPanel:prepend')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:append')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:textareaHeight')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:wordLimit')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:controls')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:dataEntry')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:displayStyle')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:multiple')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:format')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:dateType')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:dateRange')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:star')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:allowHalf')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:sliderCount')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:step')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:precision')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:filterable')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:anyNode')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:accept')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:uploadLimit')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:fileSize')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:brushColor')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:justify')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:margin')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:padding')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:background')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:borderLine')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:tabsType')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:tabPosition')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:dataEntry3')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:accordion')).exists()).toBe(false)
+    expect(wrapper.find(utils.getTestId('configPanel:contentPosition')).exists()).toBe(false)
+  })
 })
diff --git a/test/erGeneratorData.test.js b/test/erGeneratorData.test.js
index 95bece9..4e840c9 100644
--- a/test/erGeneratorData.test.js
+++ b/test/erGeneratorData.test.js
@@ -96,4 +96,7 @@ describe('Generate field data:', () => {
   test('Divider', () => {
     expect(erGeneratorData(erComponentsConfig.fieldsConfig[2].list[4], true, 'en')).toMatchSnapshot()
   })
+  test('Subform', () => {
+    expect(erGeneratorData(erComponentsConfig.fieldsConfig[2].list[5], true, 'en')).toMatchSnapshot()
+  })
 })
diff --git a/test/formTypes/subform.test.js b/test/formTypes/subform.test.js
new file mode 100644
index 0000000..d12a524
--- /dev/null
+++ b/test/formTypes/subform.test.js
@@ -0,0 +1,277 @@
+import { describe, assert, expect, test, beforeEach, beforeAll, vi } from 'vitest'
+import { mount, flushPromises, enableAutoUnmount, config, DOMWrapper } from '@vue/test-utils'
+import erGeneratorData from '@ER/formEditor/generatorData.js'
+import * as erComponentsConfig from '@ER/formEditor/componentsConfig.js'
+import _ from 'lodash-es'
+import { nextTick, reactive } from 'vue'
+import { _mount, wrapLayoutDataByLayoutType } from '@ER-test/utils.js'
+import { utils } from '@ER/formEditor/index.js'
+describe('Field: subform', () => {
+  let previewWrapper = {}
+  let configWrapper = {}
+  const handleListener = vi.fn()
+  let field = {}
+  const layoutType = 1
+  const store = reactive({
+    fields: [],
+    sector: 'root'
+  })
+  beforeAll(() => {
+    vi.stubEnv('TESTIDTYPE', 'nanoid')
+    previewWrapper = _mount(`
+      <er-form-preview
+        @listener="handleListener"
+        :layoutType="layoutType"
+        ref="EReditorRef"/>
+      `, () => ({
+      handleListener,
+      layoutType
+    })
+    )
+    configWrapper = _mount(`
+      <er-form-config
+        @listener="handleListener"
+        :layoutType="layoutType"
+        :field="store.sector"
+        :fields="store.fields"
+        ref="EReditorRef"/>
+      `, () => ({
+      handleListener,
+      layoutType,
+      store
+    })
+    )
+    return () => {
+      vi.stubEnv('TESTIDTYPE', '')
+    }
+  })
+  beforeEach(() => {
+    field = erGeneratorData(erComponentsConfig.fieldsConfig[1].list[0], true, 'en')
+  })
+  test('No child', async () => {
+    const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en')
+    const list = _.cloneDeep(subForm)
+    list.columns[0] = subForm.columns[0].id
+    const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0]], layoutType)
+    await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(false)
+    expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(0)
+    previewWrapper.find('[data-test="er-complete-button"] button').trigger('click')
+    await flushPromises()
+    expect([list.columns[0]]).toStrictEqual(previewWrapper.findAll('[data-field-id]').map(element => element.element.dataset.fieldId))
+    expect(previewWrapper.find(`[data-field-id="${list.columns[0]}"] .el-form-item`).classes()).not.toContain('is-required')
+    store.fields.push(subForm.columns[0])
+    store.sector = subForm.columns[0]
+    await flushPromises()
+    expect(configWrapper.find(utils.getTestId('configPanel:defaultValue:button')).exists()).toBe(false)
+  })
+  test('No child: Required', async () => {
+    const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en')
+    subForm.columns[0].options.required = true
+    const list = _.cloneDeep(subForm)
+    list.columns[0] = subForm.columns[0].id
+    const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0]], layoutType)
+    await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(false)
+    expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(0)
+    previewWrapper.find('[data-test="er-complete-button"] button').trigger('click')
+    await flushPromises()
+    expect([list.columns[0]]).toStrictEqual(previewWrapper.findAll('[data-field-id]').map(element => element.element.dataset.fieldId))
+    expect(previewWrapper.find(`[data-field-id="${list.columns[0]}"] .el-form-item`).classes()).toContain('is-required')
+  })
+  test('Only one child', async () => {
+    const newField = _.cloneDeep(field)
+    newField.columns[0] = newField.columns[0].id
+    const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en')
+    const list = _.cloneDeep(subForm)
+    list.columns[0] = subForm.columns[0].id
+    subForm.columns[0].list[0].push(newField)
+    const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0], field.columns[0]])
+    await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(true)
+    expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(0)
+    await previewWrapper.find(utils.getTestId('SubformLayout:addButton')).find('button').trigger('click')
+    expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(1)
+    const subForm1 = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en')
+    subForm1.columns[0].list[0].push(_.cloneDeep(field))
+    store.fields.push(subForm1.columns[0])
+    store.sector = subForm1.columns[0]
+    await flushPromises()
+    expect(configWrapper.find(utils.getTestId('configPanel:defaultValue:button')).exists()).toBe(true)
+    await configWrapper.find(utils.getTestId('configPanel:defaultValue:button')).trigger('click')
+    await flushPromises()
+    const setDefaultEl = new DOMWrapper(document.querySelector('.Everright-formEditor-ConfigSubformDefaultValueComponent'))
+    expect(setDefaultEl.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(0)
+  })
+  test('Only one child: field Required', async () => {
+    const newField = _.cloneDeep(field)
+    field.columns[0].options.required = true
+    newField.columns[0] = newField.columns[0].id
+    const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en')
+    const list = _.cloneDeep(subForm)
+    list.columns[0] = subForm.columns[0].id
+    subForm.columns[0].list[0].push(newField)
+    const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0], field.columns[0]])
+    await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(true)
+    expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(0)
+    await previewWrapper.find(utils.getTestId('SubformLayout:addButton')).find('button').trigger('click')
+    expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(1)
+    previewWrapper.find('[data-test="er-complete-button"] button').trigger('click')
+    await flushPromises()
+    expect([list.columns[0], newField.columns[0]]).toStrictEqual(previewWrapper.findAll('[data-field-id]').map(element => element.element.dataset.fieldId))
+    expect(previewWrapper.find(`[data-field-id="${list.columns[0]}"] .el-form-item`).classes()).not.toContain('is-required')
+    expect(previewWrapper.find(`[data-field-id="${newField.columns[0]}"] .el-form-item`).classes()).toContain('is-required')
+  })
+  test('Only one child: field Disabled', async () => {
+    const newField = _.cloneDeep(field)
+    field.columns[0].options.disabled = true
+    newField.columns[0] = newField.columns[0].id
+    const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en')
+    const list = _.cloneDeep(subForm)
+    list.columns[0] = subForm.columns[0].id
+    subForm.columns[0].list[0].push(newField)
+    const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0], field.columns[0]])
+    await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(true)
+    expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(0)
+    await previewWrapper.find(utils.getTestId('SubformLayout:addButton')).find('button').trigger('click')
+    expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(1)
+    previewWrapper.find('[data-test="er-complete-button"] button').trigger('click')
+    await new Promise(resolve => setTimeout(resolve, 1000))
+    expect([list.columns[0], newField.columns[0]]).toStrictEqual(previewWrapper.findAll('[data-field-id]').map(element => element.element.dataset.fieldId))
+    expect(previewWrapper.find(`[data-field-id="${newField.columns[0]}"] .el-input`).classes()).toContain('is-disabled')
+  })
+  test('Only one child: Disabled', async () => {
+    const newField = _.cloneDeep(field)
+    newField.columns[0] = newField.columns[0].id
+    const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en')
+    subForm.columns[0].options.disabled = true
+    const list = _.cloneDeep(subForm)
+    list.columns[0] = subForm.columns[0].id
+    subForm.columns[0].list[0].push(newField)
+    const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0], field.columns[0]])
+    await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(false)
+  })
+  test('Only one child: has 2 default contents', async () => {
+    const values = ['1', '2']
+    const newField = _.cloneDeep(field)
+    newField.columns[0] = newField.columns[0].id
+    const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en')
+    const list = _.cloneDeep(subForm)
+    list.columns[0] = subForm.columns[0].id
+    subForm.columns[0].list[0].push(newField)
+    subForm.columns[0].options.defaultValue = values.map(e => {
+      const result = {}
+      result[field.columns[0].key] = e
+      return result
+    })
+    const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0], field.columns[0]])
+    await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(true)
+    expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(2)
+    await new Promise(resolve => setTimeout(resolve, 1000))
+    expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item')).map(e => e.find('input').element.value)).toEqual(values)
+    await previewWrapper.find(utils.getTestId('SubformLayout:addButton')).find('button').trigger('click')
+    await flushPromises()
+    expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))[2].find('input').element.value).toEqual('')
+    const subForm1 = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en')
+    subForm1.columns[0].list[0].push(_.cloneDeep(field))
+    subForm1.columns[0].options.defaultValue = values.map(e => {
+      const result = {}
+      result[field.columns[0].key] = e
+      return result
+    })
+    store.fields.push(subForm1.columns[0])
+    store.sector = subForm1.columns[0]
+    await flushPromises()
+    expect(configWrapper.find(utils.getTestId('configPanel:defaultValue:button')).exists()).toBe(true)
+    await configWrapper.find(utils.getTestId('configPanel:defaultValue:button')).trigger('click')
+    await new Promise(resolve => setTimeout(resolve, 1000))
+    const setDefaultEl = new DOMWrapper(document.querySelector('.Everright-formEditor-ConfigSubformDefaultValueComponent'))
+    expect(setDefaultEl.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(2)
+    expect(setDefaultEl.findAll(utils.getTestId('SubformLayout:item')).map(e => e.find('input').element.value)).toEqual(values)
+    await setDefaultEl.find(utils.getTestId('configPanel:defaultValue:addButton')).trigger('click')
+    await flushPromises()
+    expect(setDefaultEl.findAll(utils.getTestId('SubformLayout:item'))[2].find('input').element.value).toEqual('')
+  })
+  test('Only one child: has 2 default contents & Disabled', async () => {
+    const values = ['1', '2']
+    const newField = _.cloneDeep(field)
+    newField.columns[0] = newField.columns[0].id
+    const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en')
+    const list = _.cloneDeep(subForm)
+    list.columns[0] = subForm.columns[0].id
+    subForm.columns[0].list[0].push(newField)
+    subForm.columns[0].options.defaultValue = values.map(e => {
+      const result = {}
+      result[field.columns[0].key] = e
+      return result
+    })
+    subForm.columns[0].options.disabled = true
+    const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0], field.columns[0]])
+    await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(false)
+    expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(2)
+    await new Promise(resolve => setTimeout(resolve, 1000))
+    expect(previewWrapper.findAll(`[data-field-id="${newField.columns[0]}"]`).map(e => e.find('.el-input').classes())).toStrictEqual([
+      [
+        'el-input',
+        'el-input--default',
+        'is-disabled',
+        'el-input--suffix'
+      ],
+      [
+        'el-input',
+        'el-input--default',
+        'is-disabled',
+        'el-input--suffix'
+      ]
+    ])
+  })
+  test('Only one child: has 2 default contents && field has default', async () => {
+    const values = ['1', '2']
+    const addValue = 'everright-formeditor'
+    const newField = _.cloneDeep(field)
+    newField.columns[0] = newField.columns[0].id
+    const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en')
+    const list = _.cloneDeep(subForm)
+    list.columns[0] = subForm.columns[0].id
+    subForm.columns[0].list[0].push(newField)
+    subForm.columns[0].options.defaultValue = values.map(e => {
+      const result = {}
+      result[field.columns[0].key] = e
+      return result
+    })
+    field.columns[0].options.defaultValue = addValue
+    const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0], field.columns[0]])
+    await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data)
+    expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(true)
+    expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(2)
+    await new Promise(resolve => setTimeout(resolve, 1000))
+    expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item')).map(e => e.find('input').element.value)).toEqual(values)
+    await previewWrapper.find(utils.getTestId('SubformLayout:addButton')).find('button').trigger('click')
+    await flushPromises()
+    expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))[2].find('input').element.value).toEqual(addValue)
+    const subForm1 = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en')
+    subForm1.columns[0].list[0].push(_.cloneDeep(field))
+    subForm1.columns[0].options.defaultValue = values.map(e => {
+      const result = {}
+      result[field.columns[0].key] = e
+      return result
+    })
+    store.fields.push(subForm1.columns[0])
+    store.sector = subForm1.columns[0]
+    await flushPromises()
+    expect(configWrapper.find(utils.getTestId('configPanel:defaultValue:button')).exists()).toBe(true)
+    await configWrapper.find(utils.getTestId('configPanel:defaultValue:button')).trigger('click')
+    await new Promise(resolve => setTimeout(resolve, 1000))
+    const setDefaultEl = new DOMWrapper(document.querySelector('.Everright-formEditor-ConfigSubformDefaultValueComponent'))
+    expect(setDefaultEl.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(2)
+    expect(setDefaultEl.findAll(utils.getTestId('SubformLayout:item')).map(e => e.find('input').element.value)).toEqual(values)
+    await setDefaultEl.find(utils.getTestId('configPanel:defaultValue:addButton')).trigger('click')
+    await flushPromises()
+    expect(setDefaultEl.findAll(utils.getTestId('SubformLayout:item'))[2].find('input').element.value).toEqual(addValue)
+  })
+})
diff --git a/test/logic/er-form-preview/er-form-preview.test.js b/test/logic/er-form-preview/er-form-preview.test.js
index ca90d32..b2dd6a9 100644
--- a/test/logic/er-form-preview/er-form-preview.test.js
+++ b/test/logic/er-form-preview/er-form-preview.test.js
@@ -3,7 +3,6 @@ import { mount, flushPromises, enableAutoUnmount } from '@vue/test-utils'
 import { nextTick, reactive } from 'vue'
 import _ from 'lodash-es'
 import ElementPlus from 'element-plus'
-import { Plus } from '@element-plus/icons-vue'
 import Vant from 'vant'
 import { erFormPreview } from '@ER/formEditor'
 import previewData from './data/preview.json'
@@ -20,9 +19,6 @@ const _mount = (template, data, otherObj) => mount(
   {
     attachTo: 'body',
     global: {
-      components: {
-        Plus
-      },
       plugins: [
         ElementPlus,
         Vant
diff --git a/test/logic/generateFilterdata/Subform.test.js b/test/logic/generateFilterdata/Subform.test.js
new file mode 100644
index 0000000..1f2e086
--- /dev/null
+++ b/test/logic/generateFilterdata/Subform.test.js
@@ -0,0 +1,14 @@
+import { describe, assert, expect, test, beforeEach, beforeAll } from 'vitest'
+import { generateIfFilterOptionsData } from '@ER/formEditor/components/Panels/Config/components/generateFilterdata.js'
+import _ from 'lodash-es'
+import erGeneratorData from '@ER/formEditor/generatorData.js'
+import * as erComponentsConfig from '@ER/formEditor/componentsConfig.js'
+describe('Generate filter data: Subform', () => {
+  let testData = {}
+  beforeAll(() => {
+    testData = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), false, 'en')
+  })
+  test('default', () => {
+    expect(generateIfFilterOptionsData('', [testData])).toMatchSnapshot()
+  })
+})
diff --git a/test/logic/generateFilterdata/__snapshots__/Subform.test.js.snap b/test/logic/generateFilterdata/__snapshots__/Subform.test.js.snap
new file mode 100644
index 0000000..70b10e5
--- /dev/null
+++ b/test/logic/generateFilterdata/__snapshots__/Subform.test.js.snap
@@ -0,0 +1,60 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Generate filter data: Subform > default 1`] = `
+{
+  "operators": {
+    "Text": [
+      {
+        "en_label": "Equal",
+        "label": "等于",
+        "style": "noop",
+        "value": "equal",
+      },
+      {
+        "en_label": "Not equal",
+        "label": "不等于",
+        "style": "noop",
+        "value": "not_equal",
+      },
+      {
+        "en_label": "Contains",
+        "label": "包含",
+        "style": "tags",
+        "value": "contains",
+      },
+      {
+        "en_label": "Not contain",
+        "label": "不包含",
+        "style": "tags",
+        "value": "not_contain",
+      },
+      {
+        "en_label": "Empty",
+        "label": "为空",
+        "style": "none",
+        "value": "empty",
+      },
+      {
+        "en_label": "Not empty",
+        "label": "不为空",
+        "style": "none",
+        "value": "not_empty",
+      },
+    ],
+  },
+  "options": [
+    {
+      "includeOperator": {
+        "operator": [
+          "empty",
+          "not_empty",
+        ],
+      },
+      "label": "Sub-form",
+      "operatorKey": "Text",
+      "renderType": "NONE",
+      "value": "{{test-id-nanoid}}",
+    },
+  ],
+}
+`;
diff --git a/test/logic/validator/Subform.test.js b/test/logic/validator/Subform.test.js
new file mode 100644
index 0000000..c8d7478
--- /dev/null
+++ b/test/logic/validator/Subform.test.js
@@ -0,0 +1,69 @@
+import { describe, assert, expect, test, beforeAll } from 'vitest'
+import { validator } from '@ER/hooks/use-logic'
+import _ from 'lodash-es'
+import erGeneratorData from '@ER/formEditor/generatorData.js'
+import * as erComponentsConfig from '@ER/formEditor/componentsConfig.js'
+describe('Subform', () => {
+  let field = {}
+  beforeAll(() => {
+    field = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), false, 'en')
+  })
+  test('empty', () => {
+    expect(
+      validator({
+        operator: 'empty'
+      },
+      '',
+      field)).toBeTruthy()
+    expect(
+      validator({
+        operator: 'empty'
+      },
+      undefined,
+      field)).toBeTruthy()
+    expect(
+      validator({
+        operator: 'empty'
+      },
+      null,
+      field)).toBeTruthy()
+    expect(
+      validator({
+        operator: 'empty'
+      },
+      [],
+      field)).toBeTruthy()
+    expect(
+      validator({
+        operator: 'empty'
+      },
+      [{}],
+      field)).toBeFalsy()
+  })
+  test('not_empty', () => {
+    expect(
+      validator({
+        operator: 'not_empty'
+      },
+      '',
+      field)).toBeFalsy()
+    expect(
+      validator({
+        operator: 'not_empty'
+      },
+      undefined,
+      field)).toBeFalsy()
+    expect(
+      validator({
+        operator: 'not_empty'
+      },
+      null,
+      field)).toBeFalsy()
+    expect(
+      validator({
+        operator: 'not_empty'
+      },
+      [{}],
+      field)).toBeTruthy()
+  })
+})
diff --git a/test/utils.js b/test/utils.js
new file mode 100644
index 0000000..a985b05
--- /dev/null
+++ b/test/utils.js
@@ -0,0 +1,38 @@
+import { mount } from '@vue/test-utils'
+import { erFormEditor, erFormPreview, erFormConfig, utils } from '@ER/formEditor/index.js'
+import ElementPlus from 'element-plus'
+import Vant from 'vant'
+import { generateData } from '@ER/utils/generateOptions.js'
+
+export const _mount = (template, data, otherObj) => mount(
+  {
+    components: {
+      erFormEditor,
+      erFormPreview,
+      erFormConfig
+    },
+    template,
+    data,
+    ...otherObj
+  },
+  {
+    attachTo: 'body',
+    global: {
+      plugins: [
+        ElementPlus,
+        Vant
+      ]
+    }
+  }
+)
+export const wrapLayoutDataByLayoutType = (layout, fields = [], layoutType = 1) => {
+  const result = utils.generateData(layoutType)
+  result.fields = fields
+  if (layoutType === 1) {
+    result.list = layout
+  }
+  if (layoutType === 2) {
+    result.layout = layout
+  }
+  return result
+}