Browse Source

新增formCard组件,容器组件支持插槽

tongshangming 2 năm trước cách đây
mục cha
commit
00af5a97ea

+ 1 - 0
src/components.d.ts

@@ -27,6 +27,7 @@ declare module '@vue/runtime-core' {
     ElDict: typeof import('./components/form/ElDict.vue')['default']
     ElEditor: typeof import('./components/form/ElEditor.vue')['default']
     ElEmployees: typeof import('./components/form/ElEmployees.vue')['default']
+    ElFormCard: typeof import('./components/form/ElFormCard.vue')['default']
     ElFormLayout: typeof import('./components/form/ElFormLayout.vue')['default']
     ElFormTabs: typeof import('./components/form/ElFormTabs.vue')['default']
     ElImageUpload: typeof import('./components/form/ElImageUpload.vue')['default']

+ 11 - 8
src/components/core/ProTable.vue

@@ -9,6 +9,7 @@ import router from '@/router'
 import { ElMessage, ElMessageBox, type DialogProps } from 'element-plus'
 import type { AdvancedForm, BasicForm, ICRUD, FormSlot } from '@/types/form'
 import type { VXEComponent, VxeToolbarProps, VxeToolbarEventProps } from 'vxe-table'
+import { buildFormSlots } from '@/utils/utils'
 
 interface CustomTable {
   showOperate?: boolean
@@ -21,7 +22,7 @@ interface Props {
   crud: ICRUD
   pageSize?: number
   selection?: boolean
-  formConfig: BasicForm | AdvancedForm
+  formConfig: BasicForm
   dialogConfig?: DialogProps
   tableConfig?: CustomTable
   toolbarConfig?: VXEComponent<VxeToolbarProps, VxeToolbarEventProps>
@@ -199,13 +200,14 @@ const handleFormSuccess = () => {
 
 // 构造表单插槽
 const formSlots = ref<FormSlot[]>([])
-if (props.formConfig.advanced) {
-  props.formConfig.formItems.forEach((item: any) => {
-    item.group.forEach((subItem: any) => Array.prototype.push.apply(formSlots.value, subItem.slots))
-  })
-} else {
-  props.formConfig.formItems.forEach((item: any) => Array.prototype.push.apply(formSlots.value, item.slots))
-}
+buildFormSlots(props.formConfig.formItems, formSlots.value)
+// if (props.formConfig.advanced) {
+//   props.formConfig.formItems.forEach((item: any) => {
+//     item.group.forEach((subItem: any) => Array.prototype.push.apply(formSlots.value, subItem.slots))
+//   })
+// } else {
+//   props.formConfig.formItems.forEach((item: any) => Array.prototype.push.apply(formSlots.value, item.slots))
+// }
 // ============== 表单部分结束 ===============
 
 defineExpose({
@@ -301,6 +303,7 @@ defineExpose({
       :dialogConfig="dialogConfig"
       :formConfig="formConfig"
       :formData="formData"
+      :formSlots="formSlots"
       :create="crud.create"
       :update="crud.update"
       @success="handleFormSuccess"

+ 4 - 16
src/components/core/form/AdvancedForm.vue

@@ -1,13 +1,15 @@
 <script setup lang="ts">
-import type { AdvancedForm } from '@/types/form'
+import type { AdvancedForm, FormSlot } from '@/types/form'
+import { containerTypes } from '@/utils/constants'
+import { buildFormSlots } from '@/utils/utils'
 
 interface Props {
   formConfig: AdvancedForm
   formData: any
+  formSlots?: Array<FormSlot>
 }
 const props = defineProps<Props>()
 
-const containerTypes = ['form-tabs', 'form-layout']
 const formData = computed(() => {
   if (props.formData.id) {
     return props.formData
@@ -36,20 +38,6 @@ const formData = computed(() => {
     return res
   }
 })
-
-// 构造formTabs插槽
-props.formConfig.formItems.forEach(item => {
-  item.group.forEach(child => {
-    if (containerTypes.includes(child.type)) {
-      child.slots = []
-      child.children?.forEach(tab => {
-        tab.children?.forEach((child: any) => {
-          Array.prototype.push.apply(child.slots, child.slots)
-        })
-      })
-    }
-  })
-})
 </script>
 
 <template>

+ 14 - 28
src/components/core/form/BasicForm.vue

@@ -1,5 +1,7 @@
 <script setup lang="ts">
-import type { BasicForm } from '@/types/form'
+import type { BasicForm, BasicFormItem, FormSlot } from '@/types/form'
+import { containerTypes, notFormItem } from '@/utils/constants'
+import { buildContainerSlots } from '@/utils/utils'
 
 interface Props {
   formConfig: BasicForm
@@ -7,44 +9,28 @@ interface Props {
 }
 const props = defineProps<Props>()
 
-const containerTypes = ['form-tabs', 'form-layout']
 const formData = computed(() => {
   if (props.formData.id) {
     return props.formData
   } else {
+    // 构造表单值
     const res = props.formData
-    props.formConfig?.formItems.forEach(item => {
-      if (containerTypes.includes(item.type)) {
-        item.children?.forEach(tab => {
-          tab.children?.forEach(child => {
-            if (!child.notFormItem) {
-              res[child.name] =
-                res[child.name] !== undefined && child.value !== undefined ? res[child.name] : child.value
-            }
-          })
-        })
-      } else {
-        if (!item.notFormItem) {
+    const buildFormData = (formItems: Array<BasicFormItem>, res: any) => {
+      formItems.forEach(item => {
+        if (!item.notFormItem || !notFormItem.includes(item.type)) {
           // 避免修改当前表单项value重置其他表单项的value
           res[item.name] = res[item.name] !== undefined && item.value !== undefined ? res[item.name] : item.value
         }
-      }
-    })
+        item.children && buildFormData(item.children, res)
+      })
+    }
+    buildFormData(props.formConfig.formItems, res)
     return res
   }
 })
 
-// 构造formTabs插槽
-props.formConfig.formItems.forEach(item => {
-  if (containerTypes.includes(item.type)) {
-    item.slots = []
-    item.children?.forEach(tab => {
-      tab.children?.forEach((child: any) => {
-        Array.prototype.push.apply(item.slots, child.slots)
-      })
-    })
-  }
-})
+// 构造表单容器插槽
+buildContainerSlots(props.formConfig.formItems)
 </script>
 
 <template>
@@ -55,7 +41,7 @@ props.formConfig.formItems.forEach(item => {
         v-if="containerTypes.includes(item.type)"
         v-model="item.value"
         v-bind="item.props"
-        :children="item.children"
+        :formItem="item"
         :formData="formData"
         :formConfig="formConfig"
         v-on="item.events || {}"

+ 14 - 9
src/components/core/form/DialogForm.vue

@@ -1,11 +1,13 @@
 <script setup lang="ts">
 import type { BasicForm, AdvancedForm, FormSlot } from '@/types/form'
 import type { DialogProps } from 'element-plus'
+import { buildFormSlots } from '@/utils/utils'
 
 interface Props {
   modelValue: boolean
   dialogConfig?: DialogProps
-  formConfig: BasicForm | AdvancedForm
+  formSlots?: Array<FormSlot>
+  formConfig: BasicForm
   formData: any
   create: Function
   update: Function
@@ -22,13 +24,9 @@ watchEffect(() => {
 const formRef = ref()
 
 // 构造表单插槽
-const formSlots = ref<FormSlot[]>([])
-if (props.formConfig.advanced) {
-  props.formConfig.formItems.forEach((item: any) => {
-    item.group.forEach((subItem: any) => Array.prototype.push.apply(formSlots.value, subItem.slots))
-  })
-} else {
-  props.formConfig.formItems.forEach((item: any) => Array.prototype.push.apply(formSlots.value, item.slots))
+const formSlots = ref<FormSlot[]>(props.formSlots || [])
+if (!props.formSlots) {
+  buildFormSlots(props.formConfig.formItems, formSlots.value)
 }
 
 const dialogTitle = computed(() => (props.formData.id ? '编辑' : '新增'))
@@ -60,7 +58,14 @@ const submit = async () => {
     @close="closeDialog"
     :close-on-click-modal="false"
   >
-    <pro-form ref="formRef" :formConfig="formConfig" :formData="formInitData" :create="create" :update="update">
+    <pro-form
+      ref="formRef"
+      :formConfig="formConfig"
+      :formData="formInitData"
+      :formSlots="formSlots"
+      :create="create"
+      :update="update"
+    >
       <template #[slot.alias]="slotProps" v-for="slot in formSlots" :key="slot.alias">
         <slot :name="slot.alias" v-bind="slotProps"></slot>
       </template>

+ 6 - 18
src/components/core/form/ProForm.vue

@@ -1,10 +1,12 @@
 <script setup lang="ts">
 import type { BasicForm, AdvancedForm, FormSlot } from '@/types/form'
 import { ElMessage } from 'element-plus'
+import { buildFormSlots } from '@/utils/utils'
 
 interface Props {
-  formConfig: BasicForm | AdvancedForm
+  formConfig: BasicForm
   formData: any
+  formSlots?: Array<FormSlot>
   create: Function
   update: Function
 }
@@ -24,23 +26,9 @@ const formInitData = computed(() => {
 const formRef = ref()
 
 // 构造表单插槽
-const formSlots = ref<FormSlot[]>([])
-if (props.formConfig.advanced) {
-  props.formConfig.formItems.forEach((item: any) => {
-    item.group.forEach((subItem: any) => Array.prototype.push.apply(formSlots.value, subItem.slots))
-  })
-} else {
-  props.formConfig.formItems.forEach((item: any) => {
-    if (item.type === 'form-tabs') {
-      item.children.forEach((tab: any) => {
-        tab.children.forEach((child: any) => {
-          Array.prototype.push.apply(formSlots.value, child.slots)
-        })
-      })
-    } else {
-      Array.prototype.push.apply(formSlots.value, item.slots)
-    }
-  })
+const formSlots = ref<FormSlot[]>(props.formSlots || [])
+if (!props.formSlots) {
+  buildFormSlots(props.formConfig.formItems, formSlots.value)
 }
 
 const submit = async () => {

+ 48 - 0
src/components/form/ElFormCard.vue

@@ -0,0 +1,48 @@
+<script setup lang="ts">
+import type { BasicForm, BasicFormItem } from '@/types/form'
+import { containerTypes } from '@/utils/constants'
+
+interface Props {
+  formItem: BasicFormItem
+  formConfig: BasicForm
+  formData: any
+}
+defineProps<Props>()
+</script>
+
+<template>
+  <el-card>
+    <template #[slot.name]="slotProps" v-for="slot in formItem.slots" :key="slot.alias">
+      <slot :name="slot.alias" v-bind="slotProps"></slot>
+    </template>
+    <el-row :gutter="20">
+      <el-col :span="item.span || formConfig.span || 12" v-for="(item, index) in formItem.children" :key="index">
+        <component
+          :is="'el-' + item.type"
+          v-if="containerTypes.includes(item.type)"
+          v-model="item.value"
+          v-bind="item.props"
+          :children="item.children"
+          :formData="formData"
+          :formConfig="formConfig"
+          v-on="item.events || {}"
+        >
+          <template #[slot.alias]="slotProps" v-for="slot in item.slots" :key="slot.alias">
+            <slot :name="slot.alias" v-bind="slotProps"></slot>
+          </template>
+        </component>
+        <template v-else>
+          <el-form-item :label="item.label" :rules="item.rules" :prop="item.name" v-show="!item.hidden">
+            <form-comp :item="item" v-model="formData[item.name]">
+              <template #[slot.alias]="slotProps" v-for="slot in item.slots" :key="slot.alias">
+                <slot :name="slot.alias" v-bind="slotProps"></slot>
+              </template>
+            </form-comp>
+          </el-form-item>
+        </template>
+      </el-col>
+    </el-row>
+  </el-card>
+</template>
+
+<style lang="scss" scoped></style>

+ 2 - 2
src/components/form/ElFormLayout.vue

@@ -2,7 +2,7 @@
 import type { BasicForm, BasicFormItem } from '@/types/form'
 
 interface Props {
-  children: Array<BasicFormItem>
+  formItem: BasicFormItem
   formConfig: BasicForm
   formData: any
 }
@@ -11,7 +11,7 @@ defineProps<Props>()
 
 <template>
   <el-row :gutter="20">
-    <el-col v-bind="item.props" v-for="(item, index) in children" :key="index">
+    <el-col v-bind="item.props" v-for="(item, index) in formItem.children" :key="index">
       <el-form-item
         v-for="child in item.children"
         :label="child.label"

+ 30 - 13
src/components/form/ElFormTabs.vue

@@ -1,13 +1,10 @@
 <script setup lang="ts">
 import type { BasicForm, BasicFormItem } from '@/types/form'
+const containerTypes = ['form-tabs', 'form-layout', 'form-card']
 
 interface Props {
   modelValue: string
-  children: Array<{
-    label: string
-    name: string
-    children: Array<BasicFormItem>
-  }>
+  formItem: BasicFormItem
   formConfig: BasicForm
   formData: any
 }
@@ -22,16 +19,36 @@ const modelValue = computed({
 
 <template>
   <el-tabs v-model="modelValue" style="width: 100%">
-    <el-tab-pane :label="tab.label" :name="tab.name" v-for="tab in children" :key="tab.name">
+    <el-tab-pane :label="tab.label" :name="tab.name" v-for="tab in formItem.children" :key="tab.name">
+      <template #[slot.name] v-for="slot in tab.slots" :key="slot.alias">
+        <slot :name="slot.alias"></slot>
+      </template>
+
       <el-row :gutter="20">
         <el-col :span="item.span || formConfig.span || 12" v-for="(item, index) in tab.children" :key="index">
-          <el-form-item :label="item.label" :rules="item.rules" :prop="item.name" v-show="!item.hidden">
-            <form-comp :item="item" v-model="formData[item.name]">
-              <template #[slot.alias]="slotProps" v-for="slot in item.slots" :key="slot.alias">
-                <slot :name="slot.alias" v-bind="slotProps"></slot>
-              </template>
-            </form-comp>
-          </el-form-item>
+          <component
+            :is="'el-' + item.type"
+            v-if="containerTypes.includes(item.type)"
+            v-model="item.value"
+            v-bind="item.props"
+            :children="item.children"
+            :formData="formData"
+            :formConfig="formConfig"
+            v-on="item.events || {}"
+          >
+            <template #[slot.alias]="slotProps" v-for="slot in item.slots" :key="slot.alias">
+              <slot :name="slot.alias" v-bind="slotProps"></slot>
+            </template>
+          </component>
+          <template v-else>
+            <el-form-item :label="item.label" :rules="item.rules" :prop="item.name" v-show="!item.hidden">
+              <form-comp :item="item" v-model="formData[item.name]">
+                <template #[slot.alias]="slotProps" v-for="slot in item.slots" :key="slot.alias">
+                  <slot :name="slot.alias" v-bind="slotProps"></slot>
+                </template>
+              </form-comp>
+            </el-form-item>
+          </template>
         </el-col>
       </el-row>
     </el-tab-pane>

+ 1 - 0
src/types/form.ts

@@ -29,6 +29,7 @@ export type BasicFormItem = {
 export type AdvancedFormItem = {
   label: string
   group: BasicFormItem[]
+  slots?: Array<FormSlot>
 }
 
 type Form = {

+ 3 - 0
src/utils/constants.ts

@@ -81,3 +81,6 @@ export const themeNavList = [
 ]
 
 export const ACCESS_TOKEN = 'access_token'
+
+export const containerTypes = ['form-tabs', 'form-layout', 'form-card']
+export const notFormItem = ['form-tabs', 'tab-pane', 'form-layout', 'row', 'col', 'form-card', 'divider']

+ 26 - 1
src/utils/utils.ts

@@ -1,6 +1,7 @@
 import dayjs from 'dayjs'
-import type { BasicFormItem } from '@/types/form'
+import type { AdvancedFormItem, BasicFormItem, FormSlot } from '@/types/form'
 import { useFormDesignerStore } from '@/stores/designer'
+import { containerTypes } from './constants'
 
 export const formatDate = (date: any, format = 'YYYY-MM-DD HH:mm') => {
   return dayjs(date).format(format)
@@ -76,3 +77,27 @@ export const placeholder = (item: BasicFormItem) => {
     return '请输入' + item.label
   }
 }
+
+// 构造表单插槽
+export const buildFormSlots = (formItems: Array<BasicFormItem>, formSlots: Array<FormSlot>) => {
+  formItems.forEach((formItem: BasicFormItem) => {
+    formItem.slots && Array.prototype.push.apply(formSlots, formItem.slots)
+    formItem.children && buildFormSlots(formItem.children, formSlots)
+  })
+}
+
+// 构造表单容器插槽
+export const buildContainerSlots = (formItems: Array<BasicFormItem>) => {
+  const recursionSlots = (formItems: Array<BasicFormItem>, formSlots: Array<FormSlot>) => {
+    formItems.forEach((formItem: BasicFormItem) => {
+      formItem.slots && Array.prototype.push.apply(formSlots, formItem.slots)
+      formItem.children && recursionSlots(formItem.children, formSlots)
+    })
+  }
+  formItems.forEach((formItem: BasicFormItem) => {
+    if (containerTypes.includes(formItem.type)) {
+      formItem.slots = formItem.slots || []
+      formItem.children && recursionSlots(formItem.children, formItem.slots)
+    }
+  })
+}

+ 195 - 1
src/views/form/Tab.vue

@@ -12,7 +12,6 @@ const update = (data: any) => {
 
 const proFormRef = ref<any>()
 const handleSave = () => {
-  console.log(formData)
   proFormRef.value.submit()
 }
 const formConfig = reactive<BasicForm>({
@@ -24,6 +23,38 @@ const formConfig = reactive<BasicForm>({
       type: 'input',
       rules: [{ required: true, message: '请输入用户名', trigger: 'blur' }]
     },
+    {
+      label: '卡片',
+      value: '',
+      name: 'form-card_SJdlee00',
+      type: 'form-card',
+      children: [
+        {
+          type: 'input',
+          label: '登录名',
+          name: 'loginName',
+          value: 'a',
+          rules: [{ required: true, message: '请输入登录名', trigger: 'blur' }],
+          slots: [
+            {
+              name: 'prepend',
+              alias: 'cprepend'
+            }
+          ]
+        }
+      ],
+      rules: [],
+      props: {
+        header: '卡片标题'
+      },
+      span: 24,
+      slots: [
+        {
+          name: 'header',
+          alias: 'cardHeader'
+        }
+      ]
+    },
     {
       type: 'form-tabs',
       label: '',
@@ -66,6 +97,12 @@ const formConfig = reactive<BasicForm>({
                 'inactive-value': '0'
               }
             }
+          ],
+          slots: [
+            {
+              name: 'label',
+              alias: 'clabel'
+            }
           ]
         },
         {
@@ -150,9 +187,151 @@ const formConfig = reactive<BasicForm>({
                 ]
               }
             }
+          ],
+          slots: [
+            {
+              name: 'label',
+              alias: 'dlabel'
+            }
           ]
         }
       ]
+    },
+    {
+      type: 'form-layout',
+      label: '',
+      name: '',
+      value: 'label1',
+      span: 24,
+      children: [
+        {
+          type: 'col',
+          label: 'tab1',
+          name: 'label1',
+          value: '',
+          children: [
+            {
+              type: 'input',
+              label: '登录名',
+              name: 'loginName',
+              value: 'a',
+              rules: [{ required: true, message: '请输入登录名', trigger: 'blur' }],
+              slots: [
+                {
+                  name: 'prepend',
+                  alias: 'lprepend'
+                }
+              ]
+            },
+            {
+              type: 'input',
+              label: '密码',
+              name: 'password',
+              value: ''
+            },
+            {
+              label: '启用',
+              value: '1',
+              name: 'loginFlag',
+              type: 'switch',
+              props: {
+                'active-value': '1',
+                'inactive-value': '0'
+              }
+            }
+          ],
+          props: {
+            span: 8
+          }
+        },
+        {
+          type: 'col',
+          label: 'tab2',
+          name: 'label2',
+          value: '',
+          children: [
+            {
+              type: 'input',
+              label: '手机号',
+              name: 'phone',
+              value: ''
+            },
+            {
+              label: '字段名',
+              value: '',
+              name: 'field3',
+              type: 'cascader',
+              props: {
+                options: [
+                  {
+                    value: 'guide',
+                    label: 'Guide',
+                    children: [
+                      {
+                        value: 'disciplines',
+                        label: 'Disciplines',
+                        children: [
+                          {
+                            value: 'consistency',
+                            label: 'Consistency'
+                          },
+                          {
+                            value: 'feedback',
+                            label: 'Feedback'
+                          },
+                          {
+                            value: 'efficiency',
+                            label: 'Efficiency'
+                          },
+                          {
+                            value: 'controllability',
+                            label: 'Controllability'
+                          }
+                        ]
+                      },
+                      {
+                        value: 'navigation',
+                        label: 'Navigation',
+                        children: [
+                          {
+                            value: 'side nav',
+                            label: 'Side Navigation'
+                          },
+                          {
+                            value: 'top nav',
+                            label: 'Top Navigation'
+                          }
+                        ]
+                      }
+                    ]
+                  },
+                  {
+                    value: 'resource',
+                    label: 'Resource',
+                    children: [
+                      {
+                        value: 'axure',
+                        label: 'Axure Components'
+                      },
+                      {
+                        value: 'sketch',
+                        label: 'Sketch Templates'
+                      },
+                      {
+                        value: 'docs',
+                        label: 'Design Documentation'
+                      }
+                    ]
+                  }
+                ]
+              }
+            }
+          ],
+          props: {
+            span: 8
+          }
+        }
+      ]
     }
   ]
 })
@@ -162,6 +341,21 @@ const formConfig = reactive<BasicForm>({
   <div class="h-full bg-white p-16px overflow-auto">
     <pro-form :formConfig="formConfig" :formData="formData" :create="create" :update="update" ref="proFormRef">
       <template #prepend>test</template>
+      <template #cprepend>testc</template>
+      <template #lprepend>testl</template>
+      <template #cardHeader>test</template>
+      <template #clabel>
+        <span class="custom-tabs-label">
+          <el-icon><calendar /></el-icon>
+          <span>Route</span>
+        </span>
+      </template>
+      <template #dlabel>
+        <span class="custom-tabs-label">
+          <el-icon><calendar /></el-icon>
+          <span>Route2</span>
+        </span>
+      </template>
     </pro-form>
     <div class="text-center">
       <el-button type="primary" @click="handleSave">保存</el-button>