This commit is contained in:
zxhlyh 2025-07-14 16:42:23 +08:00
parent fd0a8d5834
commit 2572e99a4b
10 changed files with 182 additions and 58 deletions

View File

@ -99,7 +99,7 @@ const BaseField = ({
id={field.name}
name={field.name}
className={cn(inputClassName)}
value={value}
value={value || ''}
onChange={e => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
disabled={disabled}
@ -114,7 +114,7 @@ const BaseField = ({
name={field.name}
type='password'
className={cn(inputClassName)}
value={value}
value={value || ''}
onChange={e => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
disabled={disabled}
@ -129,7 +129,7 @@ const BaseField = ({
name={field.name}
type='number'
className={cn(inputClassName)}
value={value}
value={value || ''}
onChange={e => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
disabled={disabled}

View File

@ -6,6 +6,7 @@ import {
import type {
AnyFieldApi,
} from '@tanstack/react-form'
import { useTranslation } from 'react-i18next'
import { useForm } from '@tanstack/react-form'
import type {
FormRef,
@ -18,6 +19,7 @@ import type {
BaseFieldProps,
} from '.'
import cn from '@/utils/classnames'
import { useGetFormValues } from '@/app/components/base/form/hooks'
export type BaseFormProps = {
formSchemas?: FormSchema[]
@ -28,7 +30,7 @@ export type BaseFormProps = {
} & Pick<BaseFieldProps, 'fieldClassName' | 'labelClassName' | 'inputContainerClassName' | 'inputClassName'>
const BaseForm = ({
formSchemas,
formSchemas = [],
defaultValues,
formClassName,
fieldClassName,
@ -38,17 +40,22 @@ const BaseForm = ({
ref,
disabled,
}: BaseFormProps) => {
const { t } = useTranslation()
const form = useForm({
defaultValues,
})
const { getFormValues } = useGetFormValues(form)
useImperativeHandle(ref, () => {
return {
getForm() {
return form
},
getFormValues: (option) => {
return getFormValues(formSchemas, option)
},
}
}, [form])
}, [form, formSchemas, getFormValues])
const renderField = useCallback((field: AnyFieldApi) => {
const formSchema = formSchemas?.find(schema => schema.name === field.name)
@ -73,17 +80,37 @@ const BaseForm = ({
const renderFieldWrapper = useCallback((formSchema: FormSchema) => {
const {
name,
validators,
required,
} = formSchema
let mergedValidators = validators
if (required && !validators) {
mergedValidators = {
onMount: ({ value }: any) => {
if (!value)
return t('common.errorMsg.fieldRequired', { field: name })
},
onChange: ({ value }: any) => {
if (!value)
return t('common.errorMsg.fieldRequired', { field: name })
},
onBlur: ({ value }: any) => {
if (!value)
return t('common.errorMsg.fieldRequired', { field: name })
},
}
}
return (
<form.Field
key={name}
name={name}
validators={mergedValidators}
>
{renderField}
</form.Field>
)
}, [renderField, form])
}, [renderField, form, t])
if (!formSchemas?.length)
return null

View File

@ -0,0 +1,2 @@
export * from './use-check-validated'
export * from './use-get-form-values'

View File

@ -0,0 +1,36 @@
import { useCallback } from 'react'
import type { AnyFormApi } from '@tanstack/react-form'
import { useToastContext } from '@/app/components/base/toast'
export const useCheckValidated = (form: AnyFormApi) => {
const { notify } = useToastContext()
const checkValidated = useCallback(() => {
const allError = form?.getAllErrors()
if (allError) {
const fields = allError.fields
const errorArray = Object.keys(fields).reduce((acc: string[], key: string) => {
const errors: any[] = fields[key].errors
return [...acc, ...errors]
}, [] as string[])
if (errorArray.length) {
notify({
type: 'error',
message: errorArray[0],
})
return false
}
return true
}
return true
}, [form, notify])
return {
checkValidated,
}
}

View File

@ -0,0 +1,45 @@
import { useCallback } from 'react'
import type { AnyFormApi } from '@tanstack/react-form'
import { useCheckValidated } from './use-check-validated'
import type {
FormSchema,
GetValuesOptions,
} from '../types'
import { getTransformedValuesWhenSecretInputPristine } from '../utils'
export const useGetFormValues = (form: AnyFormApi) => {
const { checkValidated } = useCheckValidated(form)
const getFormValues = useCallback((
formSchemas: FormSchema[],
{
needCheckValidatedValues,
needTransformWhenSecretFieldIsPristine,
}: GetValuesOptions,
) => {
const values = form?.store.state.values || {}
if (!needCheckValidatedValues) {
return {
values,
isCheckValidated: false,
}
}
if (checkValidated()) {
return {
values: needTransformWhenSecretFieldIsPristine ? getTransformedValuesWhenSecretInputPristine(formSchemas, form) : values,
isCheckValidated: true,
}
}
else {
return {
values: {},
isCheckValidated: false,
}
}
}, [form, checkValidated])
return {
getFormValues,
}
}

View File

@ -60,7 +60,15 @@ export type FormSchema = {
export type FormValues = Record<string, any>
export type GetValuesOptions = {
needTransformWhenSecretFieldIsPristine?: boolean
needCheckValidatedValues?: boolean
}
export type FormRefObject = {
getForm: () => AnyFormApi
getFormValues: (obj: GetValuesOptions) => {
values: Record<string, any>
isCheckValidated: boolean
}
}
export type FormRef = ForwardedRef<FormRefObject>

View File

@ -0,0 +1 @@
export * from './secret-input'

View File

@ -0,0 +1,29 @@
import type { AnyFormApi } from '@tanstack/react-form'
import type { FormSchema } from '@/app/components/base/form/types'
import { FormTypeEnum } from '@/app/components/base/form/types'
export const transformFormSchemasSecretInput = (isPristineSecretInputNames: string[], values: Record<string, any>) => {
const transformedValues: Record<string, any> = { ...values }
isPristineSecretInputNames.forEach((name) => {
if (transformedValues[name])
transformedValues[name] = '[__HIDDEN__]'
})
return transformedValues
}
export const getTransformedValuesWhenSecretInputPristine = (formSchemas: FormSchema[], form: AnyFormApi) => {
const values = form?.store.state.values || {}
const isPristineSecretInputNames: string[] = []
for (let i = 0; i < formSchemas.length; i++) {
const schema = formSchemas[i]
if (schema.type === FormTypeEnum.secretInput) {
const fieldMeta = form?.getFieldMeta(schema.name)
if (fieldMeta?.isPristine)
isPristineSecretInputNames.push(schema.name)
}
}
return transformFormSchemasSecretInput(isPristineSecretInputNames, values)
}

View File

@ -9,7 +9,6 @@ import { RiExternalLinkLine } from '@remixicon/react'
import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security'
import Modal from '@/app/components/base/modal/modal'
import { CredentialTypeEnum } from '../types'
import { transformFormSchemasSecretInput } from '../utils'
import AuthForm from '@/app/components/base/form/form-scenarios/auth'
import type { FormRefObject } from '@/app/components/base/form/types'
import { FormTypeEnum } from '@/app/components/base/form/types'
@ -64,42 +63,32 @@ const ApiKeyModal = ({
const invalidatePluginCredentialInfo = useInvalidPluginCredentialInfoHook(pluginPayload)
const formRef = useRef<FormRefObject>(null)
const handleConfirm = useCallback(async () => {
const form = formRef.current?.getForm()
const store = form?.store
const {
isCheckValidated,
values,
} = formRef.current?.getFormValues({
needCheckValidatedValues: true,
needTransformWhenSecretFieldIsPristine: true,
}) || { isCheckValidated: false, values: {} }
if (!isCheckValidated)
return
const {
__name__,
__credential_id__,
...values
} = store?.state.values
const isPristineSecretInputNames: string[] = []
for (let i = 0; i < formSchemas.length; i++) {
const schema = formSchemas[i]
if (schema.required && !values[schema.name]) {
notify({
type: 'error',
message: t('common.errorMsg.fieldRequired', { field: schema.name }),
})
return
}
if (schema.type === FormTypeEnum.secretInput) {
const fieldMeta = form?.getFieldMeta(schema.name)
if (fieldMeta?.isPristine)
isPristineSecretInputNames.push(schema.name)
}
}
const transformedValues = transformFormSchemasSecretInput(isPristineSecretInputNames, values)
...restValues
} = values
if (editValues) {
await updatePluginCredential({
credentials: transformedValues,
credentials: restValues,
credential_id: __credential_id__,
name: __name__ || '',
})
}
else {
await addPluginCredential({
credentials: transformedValues,
credentials: restValues,
type: CredentialTypeEnum.API_KEY,
name: __name__ || '',
})
@ -111,7 +100,7 @@ const ApiKeyModal = ({
onClose?.()
invalidatePluginCredentialInfo()
}, [addPluginCredential, onClose, invalidatePluginCredentialInfo, updatePluginCredential, notify, t, editValues, formSchemas])
}, [addPluginCredential, onClose, invalidatePluginCredentialInfo, updatePluginCredential, notify, t, editValues])
return (
<Modal

View File

@ -15,8 +15,6 @@ import type {
FormRefObject,
FormSchema,
} from '@/app/components/base/form/types'
import { FormTypeEnum } from '@/app/components/base/form/types'
import { transformFormSchemasSecretInput } from '../utils'
import { useToastContext } from '@/app/components/base/toast'
type OAuthClientSettingsProps = {
@ -46,33 +44,22 @@ const OAuthClientSettings = ({
const invalidatePluginCredentialInfo = useInvalidPluginCredentialInfoHook(pluginPayload)
const formRef = useRef<FormRefObject>(null)
const handleConfirm = useCallback(async () => {
const form = formRef.current?.getForm()
const store = form?.store
const {
isCheckValidated,
values,
} = formRef.current?.getFormValues({
needCheckValidatedValues: true,
needTransformWhenSecretFieldIsPristine: true,
}) || { isCheckValidated: false, values: {} }
if (!isCheckValidated)
return
const {
__oauth_client__,
...values
} = store?.state.values
const isPristineSecretInputNames: string[] = []
for (let i = 0; i < schemas.length; i++) {
const schema = schemas[i]
if (schema.required && !values[schema.name]) {
notify({
type: 'error',
message: t('common.errorMsg.fieldRequired', { field: schema.name }),
})
return
}
if (schema.type === FormTypeEnum.secretInput) {
const fieldMeta = form?.getFieldMeta(schema.name)
if (fieldMeta?.isPristine)
isPristineSecretInputNames.push(schema.name)
}
}
const transformedValues = transformFormSchemasSecretInput(isPristineSecretInputNames, values)
...restValues
} = values
await setPluginOAuthCustomClient({
client_params: transformedValues,
client_params: restValues,
enable_oauth_custom_client: __oauth_client__ === 'custom',
})
notify({
@ -82,7 +69,7 @@ const OAuthClientSettings = ({
onClose?.()
invalidatePluginCredentialInfo()
}, [onClose, invalidatePluginCredentialInfo, setPluginOAuthCustomClient, notify, t, schemas])
}, [onClose, invalidatePluginCredentialInfo, setPluginOAuthCustomClient, notify, t])
const handleConfirmAndAuthorize = useCallback(async () => {
await handleConfirm()