Ver Fonte

feat(custom): dynamicForm

linyuanjie há 6 dias atrás
pai
commit
8d98330dac

+ 47 - 0
src/components/formParse/components/DynamicForm.tsx

@@ -0,0 +1,47 @@
+import { forwardRef, memo, useImperativeHandle } from 'react'
+import { Form, Input, Select } from 'antd'
+
+export interface DynamicFormRef {}
+
+interface DynamicFormProps {
+  formItems?: API.TmplItem[]
+}
+
+const DynamicForm = memo(
+  forwardRef<DynamicFormRef, DynamicFormProps>(({ formItems = [] }, ref) => {
+    useImperativeHandle(ref, () => {
+      return {}
+    })
+
+    console.log(formItems)
+
+    const renderField = (field: API.TmplItem) => {
+      const { type, label, placeholder, visible, valid } = field.itemStruct!
+      // if (hidden && hidden(form.getFieldsValue())) return null // 条件渲染
+      console.log(visible)
+      console.log(valid)
+
+      switch (type) {
+        case 'INPUT':
+          return (
+            <Form.Item name={name} label={label.text}>
+              <Input placeholder={placeholder} />
+            </Form.Item>
+          )
+        case 'SELECT':
+          return (
+            <Form.Item name={name} label={label.text}>
+              <Select placeholder={placeholder} />
+            </Form.Item>
+          )
+        // 其他组件类型...
+        default:
+          return null
+      }
+    }
+
+    return <Form>{formItems.map(renderField)}</Form>
+  })
+)
+
+export default DynamicForm

+ 267 - 2
src/components/formParse/index.tsx

@@ -1,7 +1,8 @@
-import { forwardRef, memo, useImperativeHandle, useState } from 'react'
+import { forwardRef, memo, useEffect, useImperativeHandle, useState } from 'react'
 import { cloneDeep } from 'lodash-es'
 import { getTenantApply, ITenantApplyInfo } from '@/api/services/riskcontrol/declare'
 import { orderStateMap } from './constants/orderState'
+import DynamicForm from './components/DynamicForm'
 
 export interface FormParseRef {}
 
@@ -14,6 +15,8 @@ interface FormParseProps {
   expiry?: string
   isExpiry?: boolean
   className?: string
+  onFormDataChange: (data: Record<string, any>) => void
+  onFormRuleChange: (rule: any) => void
 }
 
 const checkStateMap = {
@@ -24,7 +27,20 @@ const checkStateMap = {
 
 const FormParse = memo(
   forwardRef<FormParseRef, FormParseProps>(
-    ({ declFormData, formData, formRule, disabled, state, expiry, isExpiry }, ref) => {
+    (
+      {
+        declFormData,
+        formData,
+        formRule,
+        disabled,
+        state,
+        expiry,
+        isExpiry,
+        onFormDataChange,
+        onFormRuleChange
+      },
+      ref
+    ) => {
       console.log(formData)
       console.log(formRule)
       console.log(disabled)
@@ -68,6 +84,253 @@ const FormParse = memo(
         }
       }
 
+      const [isInited, setIsInited] = useState(false)
+      const initFn = () => {
+        console.log('init')
+        // 初始化表单
+        formItemsCopy?.forEach((item) => {
+          // 初始化数据
+          if (item.itemStruct?.type === 'FILE_PICKER') {
+            onFormDataChange((prev: Record<string, any>) => ({
+              ...prev,
+              [item.itemStruct!.widgetId!]: item.itemDeli?.length
+                ? item.itemDeli.map((deli) => ({
+                    fileName: deli.value.fileName, // 用来回显
+                    original: deli.value.fileName, // 文件原始名称, 后续保存提交此参数
+                    name: deli.value.fileName, // 用来回显
+                    url: deli.value.ossLink, // 用来回显
+                    viewUrl: deli.value.ossLink, // 用来图片放大器回显
+                    originUrl: deli.value.url, // 用来保存原始url, 后续保存提交此参数
+                    state: 'old' // 旧数据
+                  }))
+                : []
+            }))
+          } else if (item.itemStruct?.type === 'INVOICE_PICKER') {
+            onFormDataChange((prev: Record<string, any>) => ({
+              ...prev,
+              [item.itemStruct!.widgetId!]: item.itemDeli?.length
+                ? item.itemDeli.map((deli) => ({
+                    fileName: deli.value.fileName, // 用来回显
+                    original: deli.value.fileName, // 文件原始名称, 后续保存提交此参数
+                    name: deli.value.fileName, // 用来回显
+                    url: deli.value.ossLink, // 用来回显
+                    viewUrl: deli.value.ossLink, // 用来图片放大器回显
+                    originUrl: deli.value.url, // 用来保存原始url, 后续保存提交此参数
+                    state: 'old' // 旧数据
+                  }))
+                : []
+            }))
+          } else if (item.itemStruct?.type === 'FINANCE_PICKER') {
+            onFormDataChange((prev: Record<string, any>) => ({
+              ...prev,
+              [item.itemStruct!.widgetId!]: item.itemDeli?.length
+                ? item.itemDeli.map((deli) => ({
+                    fileName: deli.value.fileName, // 用来回显
+                    original: deli.value.fileName, // 文件原始名称, 后续保存提交此参数
+                    name: deli.value.fileName, // 用来回显
+                    url: deli.value.ossLink, // 用来回显
+                    viewUrl: deli.value.ossLink, // 用来图片放大器回显
+                    originUrl: deli.value.url, // 用来保存原始url, 后续保存提交此参数
+                    state: 'old' // 旧数据
+                  }))
+                : []
+            }))
+          } else if (item.itemStruct?.type === 'INPUT') {
+            onFormDataChange((prev: Record<string, any>) => ({
+              ...prev,
+              [item.itemStruct!.widgetId!]: item.itemDeli?.length ? item.itemDeli[0].value : ''
+            }))
+          } else if (item.itemStruct?.type === 'NUMBER_INPUT') {
+            onFormDataChange((prev: Record<string, any>) => ({
+              ...prev,
+              [item.itemStruct!.widgetId!]: item.itemDeli?.length ? item.itemDeli[0].value : ''
+            }))
+          } else if (item.itemStruct?.type === 'AMOUNT_INPUT') {
+            onFormDataChange((prev: Record<string, any>) => ({
+              ...prev,
+              [item.itemStruct!.widgetId!]: item.itemDeli?.length ? item.itemDeli[0].value : ''
+            }))
+          } else if (item.itemStruct?.type === 'RADIO') {
+            onFormDataChange((prev: Record<string, any>) => ({
+              ...prev,
+              [item.itemStruct!.widgetId!]: item.itemDeli?.length ? item.itemDeli[0].value : ''
+            }))
+          } else if (item.itemStruct?.type === 'SELECT') {
+            onFormDataChange((prev: Record<string, any>) => ({
+              ...prev,
+              [item.itemStruct!.widgetId!]: item.itemDeli?.length
+                ? item.itemDeli[0].value
+                : (item.itemStruct?.expand as API.FD_COM['SELECT']).multiple
+                  ? []
+                  : ''
+            }))
+          } else if (item.itemStruct?.type === 'DATETIME_PICKER') {
+            const isRangeType = ['DATERANGE', 'DATETIMERANGE'].includes(
+              (item.itemStruct.expand as API.FD_COM['DATETIME_PICKER']).dateType
+            )
+            onFormDataChange((prev: Record<string, any>) => ({
+              ...prev,
+              [item.itemStruct!.widgetId!]: item.itemDeli?.length
+                ? item.itemDeli[0].value
+                : isRangeType
+                  ? []
+                  : ''
+            }))
+          }
+          // 初始化rules
+          if (formRule) {
+            const rules: any[] = []
+            if (item.itemStruct!.required) {
+              const tipMap = {
+                INPUT: '请输入',
+                FILE_PICKER: '请上传',
+                INVOICE_PICKER: '请上传',
+                NUMBER_INPUT: '请输入数值',
+                AMOUNT_INPUT: '请输入金额',
+                FINANCIAL_PICKER: '请上传',
+                DATETIME_PICKER: '请选择',
+                SELECT: '请选择'
+              }
+
+              const tipText = tipMap[item.itemStruct?.type as keyof typeof tipMap] ?? '请输入'
+              rules.push({
+                required: true,
+                message:
+                  item.itemStruct?.type === 'SELECT' &&
+                  (item.itemStruct.expand as API.FD_COM['SELECT']).multiple
+                    ? '请至少选择一个选项'
+                    : `${tipText}${item.itemStruct?.label.text}`,
+                trigger: 'blur'
+              })
+              // requireLimit
+              if ((item.itemStruct!.expand as API.FD_COM['FILE_PICKER'])?.requiredLimit) {
+                rules.push({
+                  validator: (_rule: any, value: any, callback: any) => {
+                    if (
+                      value.length <
+                      (item.itemStruct!.expand as API.FD_COM['FILE_PICKER'])!.requiredLimit!
+                    ) {
+                      callback(
+                        new Error(
+                          `至少上传${(item.itemStruct!.expand as API.FD_COM['FILE_PICKER'])!
+                            .requiredLimit!}个文件`
+                        )
+                      )
+                    } else {
+                      callback()
+                    }
+                  },
+                  trigger: 'blur'
+                })
+              }
+              if (
+                (item.itemStruct!.expand as API.FD_COM_ENHANCE['INVOICE_PICKER'])?.requiredLimit
+              ) {
+                rules.push({
+                  validator: (_rule: any, value: any, callback: any) => {
+                    if (
+                      value.length <
+                      (item.itemStruct!.expand as API.FD_COM_ENHANCE['INVOICE_PICKER'])!
+                        .requiredLimit!
+                    ) {
+                      callback(
+                        new Error(
+                          `至少上传${(item.itemStruct!
+                            .expand as API.FD_COM_ENHANCE['INVOICE_PICKER'])!.requiredLimit!}个文件`
+                        )
+                      )
+                    } else {
+                      callback()
+                    }
+                  },
+                  trigger: 'blur'
+                })
+              }
+              if (
+                (item.itemStruct!.expand as API.FD_COM_ENHANCE['FINANCE_PICKER'])?.requiredLimit
+              ) {
+                rules.push({
+                  validator: (_rule: any, value: any, callback: any) => {
+                    if (
+                      value.length <
+                      (item.itemStruct!.expand as API.FD_COM_ENHANCE['FINANCE_PICKER'])!
+                        .requiredLimit!
+                    ) {
+                      callback(
+                        new Error(
+                          `至少上传${(item.itemStruct!
+                            .expand as API.FD_COM_ENHANCE['FINANCE_PICKER'])!.requiredLimit!}个文件`
+                        )
+                      )
+                    } else {
+                      callback()
+                    }
+                  },
+                  trigger: 'blur'
+                })
+              }
+            }
+
+            if ((item.itemStruct!.expand as API.FD_COM['INPUT'])?.maxChars) {
+              rules.push({
+                max: parseInt((item.itemStruct!.expand as API.FD_COM['INPUT'])?.maxChars),
+                message: `最多输入${(item.itemStruct!.expand as API.FD_COM['INPUT'])?.maxChars}个字符`,
+                trigger: 'blur'
+              })
+            }
+            if ((item.itemStruct!.expand as API.FD_COM['NUMBER_INPUT'])?.maxChars) {
+              rules.push({
+                max: parseInt((item.itemStruct!.expand as API.FD_COM['NUMBER_INPUT'])?.maxChars),
+                message: `最多输入${
+                  (item.itemStruct!.expand as API.FD_COM['NUMBER_INPUT'])?.maxChars
+                }个字符`,
+                trigger: 'blur'
+              })
+            }
+            if ((item.itemStruct!.expand as API.FD_COM['AMOUNT_INPUT'])?.maxChars) {
+              rules.push({
+                max: parseInt((item.itemStruct!.expand as API.FD_COM['AMOUNT_INPUT'])?.maxChars),
+                message: `最多输入${
+                  (item.itemStruct!.expand as API.FD_COM['AMOUNT_INPUT'])?.maxChars
+                }个字符`,
+                trigger: 'blur'
+              })
+            }
+            // 这里可以根据需要添加其他规则
+            item.itemStruct!.valid?.forEach((validItem) => {
+              rules.push({
+                validator: (_rule: any, value: any, callback: any) => {
+                  const reg = new RegExp(validItem.expression.body, validItem.expression.modifier)
+                  if (!reg.test(value)) {
+                    callback(new Error(validItem.message))
+                  } else {
+                    callback()
+                  }
+                },
+                trigger: 'blur'
+              })
+            })
+            if (Array.isArray(formRule![item.itemStruct!.widgetId!]) && disabled) {
+              onFormRuleChange((prev: any) => ({
+                ...prev,
+                [item.itemStruct!.widgetId!]: [
+                  ...((formRule![item.itemStruct!.widgetId!] as []) || [])
+                ]
+              }))
+            } else {
+              onFormRuleChange((prev: any) => ({
+                ...prev,
+                [item.itemStruct!.widgetId!]: rules as any
+              }))
+            }
+          }
+        })
+        setIsInited(true)
+      }
+      useEffect(() => {
+        initFn()
+      }, [])
+
       return (
         <div>
           {state && (
@@ -126,6 +389,8 @@ const FormParse = memo(
             <span className='ml-20'>审核意见:{declFormData?.checkMsg || '-'}</span>
             <span className='ml-20 break-all'>原因说明:{declFormData?.remarks || '-'}</span>
           </div>
+          {/* 动态生成表单 */}
+          {isInited && <DynamicForm formItems={formItemsCopy} />}
         </div>
       )
     }

+ 5 - 2
src/pages/decl/index.tsx

@@ -21,6 +21,7 @@ const maskDialogWrapperClass =
 function Decl() {
   const [pageState, setPageState] = useState<IPageState>('success')
   const [orderState, setOrderState] = useState('')
+  console.log(orderState)
   const [declFormData, setDeclFormData] = useState<API.OpenDeclFormResult>()
   const [imgOSS] = useState('')
 
@@ -41,7 +42,7 @@ function Decl() {
   const [formData, setFormData] = useState<Record<string, any>>({})
   const [formRule, setFormRule] = useState({})
   type ISubmitType = 'save' | 'submit' | 'onlySave' | 'autoSave'
-  const [resultType, setResultType] = useState<Nullable<ISubmitType>>(null)
+  const [resultType] = useState<Nullable<ISubmitType>>(null)
 
   const [btnLoading, setBtnLoading] = useState(false)
   const confirmSecretFn = async () => {
@@ -71,7 +72,7 @@ function Decl() {
     }
   }, [])
 
-  const [isAutoSaveing, setIsAutoSaveing] = useState(false)
+  const [isAutoSaveing] = useState(false)
 
   // 配置保存数据的格式
   const submitFn = async (type: ISubmitType, isAfterPayment = false) => {
@@ -124,7 +125,9 @@ function Decl() {
                   <FormParse
                     ref={FormParseRef}
                     formData={formData}
+                    onFormDataChange={setFormData}
                     formRule={formRule}
+                    onFormRuleChange={setFormRule}
                     declFormData={declFormData}
                     state={DeclStateText[declFormData.declarationState!]}
                     expiry={declFormData?.expiryDate}