Browse Source

wip: Search重构中

kailong321200875 1 year ago
parent
commit
c76f8bc494

+ 0 - 1
src/components/Form/index.ts

@@ -1,5 +1,4 @@
 import Form from './src/Form.vue'
-import { ElForm } from 'element-plus'
 import type { FormSchema, FormSetProps } from './src/types'
 export type {
   ComponentNameEnum,

+ 2 - 1
src/components/Form/src/Form.vue

@@ -200,7 +200,7 @@ export default defineComponent({
       const { schema = [], isCol } = unref(getProps)
 
       return schema
-        .filter((v) => !v.hidden)
+        .filter((v) => !v.remove)
         .map((item) => {
           // 如果是 Divider 组件,需要自己占用一行
           const isDivider = item.component === 'Divider'
@@ -312,6 +312,7 @@ export default defineComponent({
       }
       return (
         <ElFormItem
+          v-show={!item.hidden}
           ref={(el: any) => setFormItemRefMap(el, item.field)}
           {...(item.formItemProps || {})}
           prop={item.field}

+ 1 - 1
src/components/Form/src/helper/index.ts

@@ -144,7 +144,7 @@ export const initModel = (schema: FormSchema[], formModel: Recordable) => {
   const model: Recordable = { ...formModel }
   schema.map((v) => {
     // 如果是hidden,就删除对应的值
-    if (v.hidden) {
+    if (v.remove) {
       delete model[v.field]
     } else if (v.component && v.component !== 'Divider') {
       const hasField = Reflect.has(model, v.field)

+ 6 - 6
src/components/Form/src/types/index.ts

@@ -785,11 +785,6 @@ export interface FormSchema {
    */
   label?: string
 
-  /**
-   * 提示信息
-   */
-  labelMessage?: string
-
   /**
    * col组件属性
    */
@@ -834,7 +829,12 @@ export interface FormSchema {
   value?: any
 
   /**
-   * 是否隐藏
+   * 是否隐藏,如果为true,会连同值一同删除,类似v-if
+   */
+  remove?: boolean
+
+  /**
+   * 样式隐藏,不会把值一同删掉,类似v-show
    */
   hidden?: boolean
 

+ 108 - 49
src/components/Search/src/Search.vue

@@ -1,16 +1,19 @@
-<script setup lang="ts">
-import { Form } from '@/components/Form'
-import { PropType, computed, unref, ref } from 'vue'
+<script setup lang="tsx">
+import { Form, FormSchema } from '@/components/Form'
+import { PropType, computed, unref, ref, watch, onMounted } from 'vue'
 import { propTypes } from '@/utils/propTypes'
 import { ElButton } from 'element-plus'
 import { useI18n } from '@/hooks/web/useI18n'
 import { useForm } from '@/hooks/web/useForm'
+import { useIcon } from '@/hooks/web/useIcon'
 import { findIndex } from '@/utils'
 import { cloneDeep } from 'lodash-es'
-import { FormSchema } from '@/types/form'
+import { initModel } from '@/components/Form/src/helper'
 
 const { t } = useI18n()
 
+const formExpose = ref<ComponentRef<typeof Form>>()
+
 const props = defineProps({
   // 生成Form的布局结构数组
   schema: {
@@ -24,7 +27,7 @@ const props = defineProps({
   // 操作按钮风格位置
   layout: propTypes.string.validate((v: string) => ['inline', 'bottom'].includes(v)).def('inline'),
   // 底部按钮的对齐方式
-  buttomPosition: propTypes.string
+  buttonPosition: propTypes.string
     .validate((v: string) => ['left', 'center', 'right'].includes(v))
     .def('center'),
   showSearch: propTypes.bool.def(true),
@@ -34,31 +37,69 @@ const props = defineProps({
   // 伸缩的界限字段
   expandField: propTypes.string.def(''),
   inline: propTypes.bool.def(true),
+  // 是否去除空值项
+  removeNoValueItem: propTypes.bool.def(true),
   model: {
     type: Object as PropType<Recordable>,
     default: () => ({})
   }
 })
 
-const emit = defineEmits(['search', 'reset'])
+const emit = defineEmits(['search', 'reset', 'register'])
 
 const visible = ref(true)
 
+// 表单数据
+const formModel = ref<Recordable>({})
+
 const newSchema = computed(() => {
   let schema: FormSchema[] = cloneDeep(props.schema)
   if (props.expand && props.expandField && !unref(visible)) {
     const index = findIndex(schema, (v: FormSchema) => v.field === props.expandField)
-    if (index > -1) {
-      const length = schema.length
-      schema.splice(index + 1, length)
-    }
+    schema.map((v, i) => {
+      if (i >= index) {
+        v.hidden = true
+      } else {
+        v.hidden = false
+      }
+      return v
+    })
   }
   if (props.layout === 'inline') {
     schema = schema.concat([
       {
         field: 'action',
         formItemProps: {
-          labelWidth: '0px'
+          labelWidth: '0px',
+          slots: {
+            default: () => {
+              return (
+                <div>
+                  {props.showSearch ? (
+                    <ElButton type="primary" onClick={search} icon={useIcon({ icon: 'ep:search' })}>
+                      {t('common.query')}
+                    </ElButton>
+                  ) : null}
+                  {props.showReset ? (
+                    <ElButton onClick={reset} icon={useIcon({ icon: 'ep:refresh-right' })}>
+                      {t('common.reset')}
+                    </ElButton>
+                  ) : null}
+                  {props.expand ? (
+                    <ElButton
+                      text
+                      onClick={setVisible}
+                      icon={useIcon({
+                        icon: visible.value ? 'ant-design:up-outlined' : 'ant-design:down-outlined'
+                      })}
+                    >
+                      {t(visible.value ? 'common.shrink' : 'common.expand')}
+                    </ElButton>
+                  ) : null}
+                </div>
+              )
+            }
+          }
         }
       }
     ])
@@ -66,41 +107,69 @@ const newSchema = computed(() => {
   return schema
 })
 
-const { register, elFormRef, methods } = useForm({
-  model: props.model || {}
-})
+const { register, methods } = useForm()
+const { getElFormExpose, getFormData } = methods
+
+// 监听表单结构化数组,重新生成formModel
+watch(
+  () => unref(newSchema),
+  async (schema = []) => {
+    formModel.value = initModel(schema, unref(formModel))
+  },
+  {
+    immediate: true,
+    deep: true
+  }
+)
+
+const filterModel = async () => {
+  const model = await getFormData()
+  props.removeNoValueItem &&
+    Object.keys(model).forEach((key) => {
+      if (model[key] === void 0 || model[key] === '') {
+        delete model[key]
+      }
+    })
+  return model
+}
 
 const search = async () => {
-  await unref(elFormRef)?.validate(async (isValid) => {
+  const elFormExpose = await getElFormExpose()
+  await elFormExpose?.validate(async (isValid) => {
     if (isValid) {
-      const { getFormData } = methods
-      const model = await getFormData()
+      const model = await filterModel()
       emit('search', model)
     }
   })
 }
 
 const reset = async () => {
-  unref(elFormRef)?.resetFields()
-  const { getFormData } = methods
-  const model = await getFormData()
+  const elFormExpose = await getElFormExpose()
+  elFormExpose?.resetFields()
+  const model = await filterModel()
   emit('reset', model)
 }
 
-const bottonButtonStyle = computed(() => {
+const bottomButtonStyle = computed(() => {
   return {
-    textAlign: props.buttomPosition as unknown as 'left' | 'center' | 'right'
+    textAlign: props.buttonPosition as unknown as 'left' | 'center' | 'right'
   }
 })
 
-const setVisible = () => {
-  unref(elFormRef)?.resetFields()
+const setVisible = async () => {
   visible.value = !unref(visible)
 }
+
+onMounted(async () => {
+  const elFormExpose = await getElFormExpose()
+  emit('register', formExpose, elFormExpose)
+})
 </script>
 
 <template>
   <Form
+    ref="formExpose"
+    :model="formModel"
     :is-custom="false"
     :label-width="labelWidth"
     hide-required-asterisk
@@ -108,38 +177,28 @@ const setVisible = () => {
     :is-col="isCol"
     :schema="newSchema"
     @register="register"
-  >
-    <template #action>
-      <div v-if="layout === 'inline'">
-        <ElButton v-if="showSearch" type="primary" @click="search">
-          <Icon icon="ep:search" class="mr-5px" />
-          {{ t('common.query') }}
-        </ElButton>
-        <ElButton v-if="showReset" @click="reset">
-          <Icon icon="ep:refresh-right" class="mr-5px" />
-          {{ t('common.reset') }}
-        </ElButton>
-        <ElButton v-if="expand" text @click="setVisible">
-          {{ t(visible ? 'common.shrink' : 'common.expand') }}
-          <Icon :icon="visible ? 'ant-design:up-outlined' : 'ant-design:down-outlined'" />
-        </ElButton>
-      </div>
-    </template>
-  </Form>
+  />
 
   <template v-if="layout === 'bottom'">
-    <div :style="bottonButtonStyle">
-      <ElButton v-if="showSearch" type="primary" @click="search">
-        <Icon icon="ep:search" class="mr-5px" />
+    <div :style="bottomButtonStyle">
+      <ElButton
+        v-if="showSearch"
+        type="primary"
+        :icon="useIcon({ icon: 'ep:search' })"
+        @click="search"
+      >
         {{ t('common.query') }}
       </ElButton>
-      <ElButton v-if="showReset" @click="reset">
-        <Icon icon="ep:refresh-right" class="mr-5px" />
+      <ElButton v-if="showReset" :icon="useIcon({ icon: 'ep:refresh-right' })" @click="reset">
         {{ t('common.reset') }}
       </ElButton>
-      <ElButton v-if="expand" text @click="setVisible">
+      <ElButton
+        v-if="expand"
+        :icon="useIcon({ icon: visible ? 'ant-design:up-outlined' : 'ant-design:down-outlined' })"
+        text
+        @click="setVisible"
+      >
         {{ t(visible ? 'common.shrink' : 'common.expand') }}
-        <Icon :icon="visible ? 'ant-design:up-outlined' : 'ant-design:down-outlined'" />
       </ElButton>
     </div>
   </template>

+ 125 - 0
src/hooks/web/useSearch.ts

@@ -0,0 +1,125 @@
+import type { Form, FormExpose } from '@/components/Form'
+import type { ElForm, ElFormItem } from 'element-plus'
+import { ref, unref, nextTick } from 'vue'
+import { FormSchema, FormSetProps, FormProps } from '@/components/Form'
+
+export const useSearch = () => {
+  // Search实例
+  const formRef = ref<typeof Form & FormExpose>()
+
+  // ElForm实例
+  const elFormRef = ref<ComponentRef<typeof ElForm>>()
+
+  /**
+   * @param ref Form实例
+   * @param elRef ElForm实例
+   */
+  const register = (ref: typeof Form & FormExpose, elRef: ComponentRef<typeof ElForm>) => {
+    formRef.value = ref
+    elFormRef.value = elRef
+  }
+
+  const getForm = async () => {
+    await nextTick()
+    const form = unref(formRef)
+    if (!form) {
+      console.error('The Search is not registered. Please use the register method to register')
+    }
+    return form
+  }
+
+  // 一些内置的方法
+  const methods = {
+    /**
+     * @description 设置form组件的props
+     * @param field FormItem的field
+     */
+    setProps: async (props: FormProps = {}) => {
+      const form = await getForm()
+      form?.setProps(props)
+      if (props.model) {
+        form?.setValues(props.model)
+      }
+    },
+
+    /**
+     * @description 设置form的值
+     * @param data 需要设置的数据
+     */
+    setValues: async (data: Recordable) => {
+      const form = await getForm()
+      form?.setValues(data)
+    },
+
+    /**
+     * @description 设置schema
+     * @param schemaProps 需要设置的schemaProps
+     */
+    setSchema: async (schemaProps: FormSetProps[]) => {
+      const form = await getForm()
+      form?.setSchema(schemaProps)
+    },
+
+    /**
+     * @description 新增schema
+     * @param formSchema 需要新增数据
+     * @param index 在哪里新增
+     */
+    addSchema: async (formSchema: FormSchema, index?: number) => {
+      const form = await getForm()
+      form?.addSchema(formSchema, index)
+    },
+
+    /**
+     * @description 删除schema
+     * @param field 删除哪个数据
+     */
+    delSchema: async (field: string) => {
+      const form = await getForm()
+      form?.delSchema(field)
+    },
+
+    /**
+     * @description 获取表单数据
+     * @returns form data
+     */
+    getFormData: async <T = Recordable>(): Promise<T> => {
+      const form = await getForm()
+      return form?.formModel as T
+    },
+
+    /**
+     * @description 获取表单组件的实例
+     * @param field 表单项唯一标识
+     * @returns component instance
+     */
+    getComponentExpose: async (field: string) => {
+      const form = await getForm()
+      return form?.getComponentExpose(field)
+    },
+
+    /**
+     * @description 获取formItem组件的实例
+     * @param field 表单项唯一标识
+     * @returns formItem instance
+     */
+    getFormItemExpose: async (field: string) => {
+      const form = await getForm()
+      return form?.getFormItemExpose(field) as ComponentRef<typeof ElFormItem>
+    },
+
+    /**
+     * @description 获取ElForm组件的实例
+     * @returns ElForm instance
+     */
+    getElFormExpose: async () => {
+      await getForm()
+      return unref(elFormRef)
+    }
+  }
+
+  return {
+    register,
+    methods
+  }
+}

+ 8 - 16
src/router/index.ts

@@ -157,14 +157,6 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
               title: 'UseForm'
             }
           }
-          // {
-          //   path: 'ref-form',
-          //   component: () => import('@/views/Components/Form/RefForm.vue'),
-          //   name: 'RefForm',
-          //   meta: {
-          //     title: 'RefForm'
-          //   }
-          // }
         ]
       },
       // {
@@ -222,16 +214,16 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
             }
           }
         ]
+      },
+      {
+        path: 'search',
+        component: () => import('@/views/Components/Search.vue'),
+        name: 'Search',
+        meta: {
+          title: t('router.search')
+        }
       }
       // {
-      //   path: 'search',
-      //   component: () => import('@/views/Components/Search.vue'),
-      //   name: 'Search',
-      //   meta: {
-      //     title: t('router.search')
-      //   }
-      // },
-      // {
       //   path: 'descriptions',
       //   component: () => import('@/views/Components/Descriptions.vue'),
       //   name: 'Descriptions',

+ 43 - 61
src/views/Components/Search.vue

@@ -3,23 +3,21 @@ import { ContentWrap } from '@/components/ContentWrap'
 import { useI18n } from '@/hooks/web/useI18n'
 import { Search } from '@/components/Search'
 import { reactive, ref, unref } from 'vue'
-import { useValidator } from '@/hooks/web/useValidator'
 import { ElButton } from 'element-plus'
 import { getDictOneApi } from '@/api/common'
-import { FormSchema } from '@/types/form'
-
-const { required } = useValidator()
+import { FormSchema } from '@/components/Form'
+import { useSearch } from '@/hooks/web/useSearch'
 
 const { t } = useI18n()
 
+const { register, methods } = useSearch()
+const { setSchema } = methods
+
 const schema = reactive<FormSchema[]>([
   {
     field: 'field1',
     label: t('formDemo.input'),
-    component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    }
+    component: 'Input'
   },
   {
     field: 'field2',
@@ -36,15 +34,17 @@ const schema = reactive<FormSchema[]>([
           value: '2'
         }
       ],
-      onChange: (value: string) => {
-        console.log(value)
+      on: {
+        change: (value: string) => {
+          console.log(value)
+        }
       }
     }
   },
   {
     field: 'field3',
     label: t('formDemo.radio'),
-    component: 'Radio',
+    component: 'RadioGroup',
     componentProps: {
       options: [
         {
@@ -74,90 +74,57 @@ const schema = reactive<FormSchema[]>([
   {
     field: 'field8',
     label: t('formDemo.input'),
-    component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    }
+    component: 'Input'
   },
   {
     field: 'field9',
     label: t('formDemo.input'),
-    component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    }
+    component: 'Input'
   },
   {
     field: 'field10',
     label: t('formDemo.input'),
-    component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    }
+    component: 'Input'
   },
   {
     field: 'field11',
     label: t('formDemo.input'),
-    component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    }
+    component: 'Input'
   },
   {
     field: 'field12',
     label: t('formDemo.input'),
-    component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    }
+    component: 'Input'
   },
   {
     field: 'field13',
     label: t('formDemo.input'),
-    component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    }
+    component: 'Input'
   },
   {
     field: 'field14',
     label: t('formDemo.input'),
-    component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    }
+    component: 'Input'
   },
   {
     field: 'field15',
     label: t('formDemo.input'),
-    component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    }
+    component: 'Input'
   },
   {
     field: 'field16',
     label: t('formDemo.input'),
-    component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    }
+    component: 'Input'
   },
   {
     field: 'field17',
     label: t('formDemo.input'),
-    component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    }
+    component: 'Input'
   },
   {
     field: 'field18',
     label: t('formDemo.input'),
-    component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    }
+    component: 'Input'
   }
 ])
 
@@ -173,24 +140,36 @@ const changeLayout = () => {
   layout.value = unref(layout) === 'inline' ? 'bottom' : 'inline'
 }
 
-const buttomPosition = ref('left')
+const buttonPosition = ref('left')
 
 const changePosition = (position: string) => {
   layout.value = 'bottom'
-  buttomPosition.value = position
+  buttonPosition.value = position
 }
 
 const getDictOne = async () => {
   const res = await getDictOneApi()
   if (res) {
-    schema[1].componentProps!.options = res.data
-    console.log(res.data)
+    setSchema([
+      {
+        field: 'field2',
+        path: 'componentPorps.options',
+        value: res.data
+      }
+    ])
   }
 }
+
+const handleSearch = (data: any) => {
+  console.log(data)
+}
 </script>
 
 <template>
-  <ContentWrap :title="`${t('searchDemo.search')} ${t('searchDemo.operate')}`">
+  <ContentWrap
+    :title="`${t('searchDemo.search')} ${t('searchDemo.operate')}`"
+    style="margin-bottom: 20px"
+  >
     <ElButton @click="changeGrid(true)">{{ t('searchDemo.grid') }}</ElButton>
     <ElButton @click="changeGrid(false)">
       {{ t('searchDemo.restore') }} {{ t('searchDemo.grid') }}
@@ -219,9 +198,12 @@ const getDictOne = async () => {
       :schema="schema"
       :is-col="isGrid"
       :layout="layout"
-      :buttom-position="buttomPosition"
+      :button-position="buttonPosition"
       expand
       expand-field="field6"
+      @search="handleSearch"
+      @reset="handleSearch"
+      @register="register"
     />
   </ContentWrap>
 </template>