Commit b678010e by light5980 Committed by GitHub

fix(certification): certification form performance optimization (#7385)

parent 672e5b4a
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { Box, Button, Flex, Grid, Input, Text, Textarea } from '@chakra-ui/react'; import { Box, Button, Flex, Grid, Input, Text, Textarea } from '@chakra-ui/react';
import { Controller, type UseFormReturn, useWatch } from 'react-hook-form'; import {
get,
type FieldPath,
type RegisterOptions,
type UseFormReturn,
useController,
useFormState,
useWatch
} from 'react-hook-form';
import type { TFunction } from 'next-i18next'; import type { TFunction } from 'next-i18next';
import MySelect from '@fastgpt/web/components/common/MySelect'; import MySelect from '@fastgpt/web/components/common/MySelect';
import type { StartEnterpriseAuthBodyType } from '@fastgpt/global/openapi/support/user/team/enterpriseAuth/api'; import type { StartEnterpriseAuthBodyType } from '@fastgpt/global/openapi/support/user/team/enterpriseAuth/api';
...@@ -31,141 +39,208 @@ type EnterpriseAuthInfoFormProps = { ...@@ -31,141 +39,208 @@ type EnterpriseAuthInfoFormProps = {
reloadBanks: () => void; reloadBanks: () => void;
}; };
const EnterpriseAuthInfoForm = ({ type EnterpriseAuthFieldName = FieldPath<StartEnterpriseAuthBodyType>;
t,
type EnterpriseAuthFieldOptions<TFieldName extends EnterpriseAuthFieldName> = {
startForm: UseFormReturn<StartEnterpriseAuthBodyType>;
name: TFieldName;
rules: RegisterOptions<StartEnterpriseAuthBodyType, TFieldName>;
};
/**
* 为单个字段订阅值和校验状态,避免其他字段变化时触发表单布局组件重渲染。
*/
const useEnterpriseAuthField = <TFieldName extends EnterpriseAuthFieldName>({
startForm, startForm,
bankOptions, name,
hasSubmittedStartForm, rules
hasBankLoadError, }: EnterpriseAuthFieldOptions<TFieldName>) => {
isBankLoading, const value = useWatch<StartEnterpriseAuthBodyType, TFieldName>({
reloadBanks
}: EnterpriseAuthInfoFormProps) => {
const [hasBlurredUnifiedCreditCode, setHasBlurredUnifiedCreditCode] = useState(false);
const [hasBlurredBankAccount, setHasBlurredBankAccount] = useState(false);
const unifiedCreditCode = useWatch({
control: startForm.control, control: startForm.control,
name: 'unifiedCreditCode' name
}); });
const bankAccount = useWatch({ const { errors, isSubmitted } = useFormState<StartEnterpriseAuthBodyType>({
control: startForm.control, control: startForm.control,
name: 'bankAccount' name
}); });
const watchedFields = useWatch({ const fieldRegister = useMemo(() => startForm.register(name, rules), [name, rules, startForm]);
control: startForm.control
return {
value: String(value ?? ''),
error: get(errors, name),
isSubmitted,
fieldRegister
};
};
type EnterpriseAuthInputFieldProps<TFieldName extends EnterpriseAuthFieldName> =
EnterpriseAuthFieldOptions<TFieldName> & {
label: string;
placeholder: string;
hasSubmittedStartForm: boolean;
colSpan?: number;
};
const EnterpriseAuthInputField = <TFieldName extends EnterpriseAuthFieldName>({
startForm,
name,
rules,
label,
placeholder,
hasSubmittedStartForm,
colSpan
}: EnterpriseAuthInputFieldProps<TFieldName>) => {
const { value, error, fieldRegister } = useEnterpriseAuthField({
startForm,
name,
rules
}); });
const unifiedCreditCodeRegister = useMemo( const isInvalid = hasSubmittedStartForm && !value.trim();
() =>
startForm.register('unifiedCreditCode', {
...fieldRules.unifiedCreditCode,
setValueAs: normalizeUnifiedCreditCode
}),
[startForm]
);
const bankAccountRegister = useMemo(
() =>
startForm.register('bankAccount', {
...fieldRules.bankAccount,
setValueAs: normalizeBankAccount
}),
[startForm]
);
const shouldShowUnifiedCreditCodeError =
!!unifiedCreditCode?.trim() &&
(hasBlurredUnifiedCreditCode || startForm.formState.isSubmitted) &&
!isUnifiedCreditCode(unifiedCreditCode);
const shouldShowBankAccountError =
!!bankAccount?.trim() &&
(hasBlurredBankAccount || startForm.formState.isSubmitted) &&
!isBankAccount(bankAccount);
const fieldErrors = startForm.formState.errors;
const isEmptyAfterSubmit = (value?: string) => hasSubmittedStartForm && !value?.trim();
const shouldShowUnifiedCreditCodeEmptyError =
isEmptyAfterSubmit(unifiedCreditCode) ||
(!!fieldErrors.unifiedCreditCode && !unifiedCreditCode?.trim());
const shouldShowBankAccountEmptyError =
isEmptyAfterSubmit(bankAccount) || (!!fieldErrors.bankAccount && !bankAccount?.trim());
const shouldShowBankNameEmptyError =
isEmptyAfterSubmit(watchedFields.bankName) ||
(!!fieldErrors.bankName && !watchedFields.bankName?.trim());
return ( return (
<Flex flexDirection={'column'} gap={6}> <Field label={label} colSpan={colSpan}>
<Section title={t('account_team:enterprise_auth_enterprise_info')}>
<Grid templateColumns={['1fr', 'repeat(2, minmax(0, 1fr))']} gap={4}>
<Field label={t('account_team:enterprise_auth_enterprise_name')}>
<Input <Input
placeholder={t('account_team:enterprise_auth_enterprise_name_placeholder')} placeholder={placeholder}
{...inputStyles} {...inputStyles}
isInvalid={ isInvalid={isInvalid || !!error}
isEmptyAfterSubmit(watchedFields.enterpriseName) || !!fieldErrors.enterpriseName
}
_invalid={invalidInputStyles} _invalid={invalidInputStyles}
{...startForm.register('enterpriseName', fieldRules.enterpriseName)} {...fieldRegister}
/> />
</Field> </Field>
);
};
type EnterpriseAuthUnifiedCreditCodeFieldProps = {
t: TFunction;
startForm: UseFormReturn<StartEnterpriseAuthBodyType>;
hasSubmittedStartForm: boolean;
};
const enterpriseAuthUnifiedCreditCodeRules = {
...fieldRules.unifiedCreditCode,
setValueAs: normalizeUnifiedCreditCode
};
const EnterpriseAuthUnifiedCreditCodeField = ({
t,
startForm,
hasSubmittedStartForm
}: EnterpriseAuthUnifiedCreditCodeFieldProps) => {
const [hasBlurred, setHasBlurred] = useState(false);
const { value, error, isSubmitted, fieldRegister } = useEnterpriseAuthField({
startForm,
name: 'unifiedCreditCode',
rules: enterpriseAuthUnifiedCreditCodeRules
});
const shouldShowFormatError =
!!value.trim() && (hasBlurred || isSubmitted) && !isUnifiedCreditCode(value);
const shouldShowEmptyError = (hasSubmittedStartForm || !!error) && !value.trim();
return (
<Field <Field
label={t('account_team:enterprise_auth_unified_credit_code')} label={t('account_team:enterprise_auth_unified_credit_code')}
errorText={ errorText={
shouldShowUnifiedCreditCodeError shouldShowFormatError ? t('account_team:enterprise_auth_invalid_format_tip') : undefined
? t('account_team:enterprise_auth_invalid_format_tip')
: undefined
} }
> >
<Input <Input
placeholder={t('account_team:enterprise_auth_unified_credit_code_placeholder')} placeholder={t('account_team:enterprise_auth_unified_credit_code_placeholder')}
{...inputStyles} {...inputStyles}
isInvalid={shouldShowUnifiedCreditCodeEmptyError} isInvalid={shouldShowEmptyError}
_invalid={invalidInputStyles} _invalid={invalidInputStyles}
{...unifiedCreditCodeRegister} {...fieldRegister}
onBlur={(event) => { onBlur={(event) => {
setHasBlurredUnifiedCreditCode(true); setHasBlurred(true);
unifiedCreditCodeRegister.onBlur(event); fieldRegister.onBlur(event);
}} }}
/> />
</Field> </Field>
<Field label={t('account_team:enterprise_auth_legal_person')}> );
<Input };
placeholder={t('account_team:enterprise_auth_legal_person_placeholder')}
{...inputStyles} type EnterpriseAuthBankAccountFieldProps = {
isInvalid={ t: TFunction;
isEmptyAfterSubmit(watchedFields.legalPersonName) || !!fieldErrors.legalPersonName startForm: UseFormReturn<StartEnterpriseAuthBodyType>;
} hasSubmittedStartForm: boolean;
_invalid={invalidInputStyles} };
{...startForm.register('legalPersonName', fieldRules.legalPersonName)}
/> const enterpriseAuthBankAccountRules = {
</Field> ...fieldRules.bankAccount,
setValueAs: normalizeBankAccount
};
const EnterpriseAuthBankAccountField = ({
t,
startForm,
hasSubmittedStartForm
}: EnterpriseAuthBankAccountFieldProps) => {
const [hasBlurred, setHasBlurred] = useState(false);
const { value, error, isSubmitted, fieldRegister } = useEnterpriseAuthField({
startForm,
name: 'bankAccount',
rules: enterpriseAuthBankAccountRules
});
const shouldShowFormatError =
!!value.trim() && (hasBlurred || isSubmitted) && !isBankAccount(value);
const shouldShowEmptyError = (hasSubmittedStartForm || !!error) && !value.trim();
return (
<Field <Field
label={t('account_team:enterprise_auth_bank_account')} label={t('account_team:enterprise_auth_bank_account')}
errorText={ errorText={
shouldShowBankAccountError shouldShowFormatError ? t('account_team:enterprise_auth_invalid_format_tip') : undefined
? t('account_team:enterprise_auth_invalid_format_tip')
: undefined
} }
> >
<Input <Input
placeholder={t('account_team:enterprise_auth_bank_account_placeholder')} placeholder={t('account_team:enterprise_auth_bank_account_placeholder')}
{...inputStyles} {...inputStyles}
isInvalid={shouldShowBankAccountEmptyError} isInvalid={shouldShowEmptyError}
_invalid={invalidInputStyles} _invalid={invalidInputStyles}
{...bankAccountRegister} {...fieldRegister}
onBlur={(event) => { onBlur={(event) => {
setHasBlurredBankAccount(true); setHasBlurred(true);
bankAccountRegister.onBlur(event); fieldRegister.onBlur(event);
}} }}
/> />
</Field> </Field>
);
};
type EnterpriseAuthBankNameFieldProps = {
t: TFunction;
startForm: UseFormReturn<StartEnterpriseAuthBodyType>;
bankOptions: EnterpriseAuthBankOption[];
hasSubmittedStartForm: boolean;
hasBankLoadError: boolean;
isBankLoading: boolean;
reloadBanks: () => void;
};
const EnterpriseAuthBankNameField = ({
t,
startForm,
bankOptions,
hasSubmittedStartForm,
hasBankLoadError,
isBankLoading,
reloadBanks
}: EnterpriseAuthBankNameFieldProps) => {
const { field, fieldState } = useController({
control: startForm.control,
name: 'bankName',
rules: fieldRules.bankName
});
const value = String(field.value ?? '');
const shouldShowEmptyError = (hasSubmittedStartForm || !!fieldState.error) && !value.trim();
return (
<Field label={t('account_team:enterprise_auth_bank_name')}> <Field label={t('account_team:enterprise_auth_bank_name')}>
<Controller
control={startForm.control}
name={'bankName'}
rules={fieldRules.bankName}
render={({ field }) => (
<MySelect<string> <MySelect<string>
value={field.value} value={field.value}
list={bankOptions} list={bankOptions}
isSearch isSearch
isDisabled={hasBankLoadError} isDisabled={hasBankLoadError}
isInvalid={shouldShowBankNameEmptyError} isInvalid={shouldShowEmptyError}
isLoading={isBankLoading} isLoading={isBankLoading}
placeholder={ placeholder={
hasBankLoadError hasBankLoadError
...@@ -184,26 +259,18 @@ const EnterpriseAuthInfoForm = ({ ...@@ -184,26 +259,18 @@ const EnterpriseAuthInfoForm = ({
borderColor: 'myGray.100', borderColor: 'myGray.100',
color: 'myGray.400' color: 'myGray.400'
}} }}
_hover={ _hover={shouldShowEmptyError ? { borderColor: 'red.500' } : { borderColor: 'primary.300' }}
shouldShowBankNameEmptyError
? { borderColor: 'red.500' }
: { borderColor: 'primary.300' }
}
size={'sm'} size={'sm'}
h={'32px'} h={'32px'}
borderColor={shouldShowBankNameEmptyError ? 'red.500' : 'borderColor.low'} borderColor={shouldShowEmptyError ? 'red.500' : 'borderColor.low'}
onChange={(value) => { onChange={(value) => {
field.onChange(value); field.onChange(value);
startForm.clearErrors('bankName'); startForm.clearErrors('bankName');
}} }}
/> />
)}
/>
{hasBankLoadError && ( {hasBankLoadError && (
<Flex mt={2} alignItems={'center'} gap={2}> <Flex mt={2} alignItems={'center'} gap={2}>
<Text {...formErrorTextStyles}> <Text {...formErrorTextStyles}>{t('account_team:enterprise_auth_bank_load_failed')}</Text>
{t('account_team:enterprise_auth_bank_load_failed')}
</Text>
<Button <Button
size={'xs'} size={'xs'}
variant={'link'} variant={'link'}
...@@ -217,6 +284,86 @@ const EnterpriseAuthInfoForm = ({ ...@@ -217,6 +284,86 @@ const EnterpriseAuthInfoForm = ({
</Flex> </Flex>
)} )}
</Field> </Field>
);
};
type EnterpriseAuthDemandFieldProps = {
t: TFunction;
startForm: UseFormReturn<StartEnterpriseAuthBodyType>;
hasSubmittedStartForm: boolean;
};
const EnterpriseAuthDemandField = ({
t,
startForm,
hasSubmittedStartForm
}: EnterpriseAuthDemandFieldProps) => {
const { value, error, fieldRegister } = useEnterpriseAuthField({
startForm,
name: 'demand',
rules: fieldRules.demand
});
return (
<Field label={t('account_team:enterprise_auth_demand')} colSpan={2}>
<Textarea
{...textareaStyles}
placeholder={t('account_team:enterprise_auth_demand_placeholder')}
isInvalid={(hasSubmittedStartForm && !value.trim()) || !!error}
_invalid={invalidInputStyles}
{...fieldRegister}
/>
</Field>
);
};
const EnterpriseAuthInfoForm = ({
t,
startForm,
bankOptions,
hasSubmittedStartForm,
hasBankLoadError,
isBankLoading,
reloadBanks
}: EnterpriseAuthInfoFormProps) => (
<Flex flexDirection={'column'} gap={6}>
<Section title={t('account_team:enterprise_auth_enterprise_info')}>
<Grid templateColumns={['1fr', 'repeat(2, minmax(0, 1fr))']} gap={4}>
<EnterpriseAuthInputField
startForm={startForm}
name={'enterpriseName'}
rules={fieldRules.enterpriseName}
label={t('account_team:enterprise_auth_enterprise_name')}
placeholder={t('account_team:enterprise_auth_enterprise_name_placeholder')}
hasSubmittedStartForm={hasSubmittedStartForm}
/>
<EnterpriseAuthUnifiedCreditCodeField
t={t}
startForm={startForm}
hasSubmittedStartForm={hasSubmittedStartForm}
/>
<EnterpriseAuthInputField
startForm={startForm}
name={'legalPersonName'}
rules={fieldRules.legalPersonName}
label={t('account_team:enterprise_auth_legal_person')}
placeholder={t('account_team:enterprise_auth_legal_person_placeholder')}
hasSubmittedStartForm={hasSubmittedStartForm}
/>
<EnterpriseAuthBankAccountField
t={t}
startForm={startForm}
hasSubmittedStartForm={hasSubmittedStartForm}
/>
<EnterpriseAuthBankNameField
t={t}
startForm={startForm}
bankOptions={bankOptions}
hasSubmittedStartForm={hasSubmittedStartForm}
hasBankLoadError={hasBankLoadError}
isBankLoading={isBankLoading}
reloadBanks={reloadBanks}
/>
</Grid> </Grid>
</Section> </Section>
...@@ -226,50 +373,39 @@ const EnterpriseAuthInfoForm = ({ ...@@ -226,50 +373,39 @@ const EnterpriseAuthInfoForm = ({
<Section title={t('account_team:enterprise_auth_contact_info')}> <Section title={t('account_team:enterprise_auth_contact_info')}>
<Grid templateColumns={['1fr', 'repeat(2, minmax(0, 1fr))']} gap={4}> <Grid templateColumns={['1fr', 'repeat(2, minmax(0, 1fr))']} gap={4}>
<Field label={t('account_team:enterprise_auth_contact_name')}> <EnterpriseAuthInputField
<Input startForm={startForm}
name={'contactName'}
rules={fieldRules.contactName}
label={t('account_team:enterprise_auth_contact_name')}
placeholder={t('account_team:enterprise_auth_contact_name_placeholder')} placeholder={t('account_team:enterprise_auth_contact_name_placeholder')}
{...inputStyles} hasSubmittedStartForm={hasSubmittedStartForm}
isInvalid={isEmptyAfterSubmit(watchedFields.contactName) || !!fieldErrors.contactName}
_invalid={invalidInputStyles}
{...startForm.register('contactName', fieldRules.contactName)}
/> />
</Field> <EnterpriseAuthInputField
<Field label={t('account_team:enterprise_auth_contact_title')}> startForm={startForm}
<Input name={'contactTitle'}
rules={fieldRules.contactTitle}
label={t('account_team:enterprise_auth_contact_title')}
placeholder={t('account_team:enterprise_auth_contact_title_placeholder')} placeholder={t('account_team:enterprise_auth_contact_title_placeholder')}
{...inputStyles} hasSubmittedStartForm={hasSubmittedStartForm}
isInvalid={
isEmptyAfterSubmit(watchedFields.contactTitle) || !!fieldErrors.contactTitle
}
_invalid={invalidInputStyles}
{...startForm.register('contactTitle', fieldRules.contactTitle)}
/> />
</Field> <EnterpriseAuthInputField
<Field label={t('account_team:enterprise_auth_contact_phone')} colSpan={2}> startForm={startForm}
<Input name={'contactPhone'}
rules={fieldRules.contactPhone}
label={t('account_team:enterprise_auth_contact_phone')}
placeholder={t('account_team:enterprise_auth_contact_phone_placeholder')} placeholder={t('account_team:enterprise_auth_contact_phone_placeholder')}
{...inputStyles} hasSubmittedStartForm={hasSubmittedStartForm}
isInvalid={ colSpan={2}
isEmptyAfterSubmit(watchedFields.contactPhone) || !!fieldErrors.contactPhone
}
_invalid={invalidInputStyles}
{...startForm.register('contactPhone', fieldRules.contactPhone)}
/> />
</Field> <EnterpriseAuthDemandField
<Field label={t('account_team:enterprise_auth_demand')} colSpan={2}> t={t}
<Textarea startForm={startForm}
{...textareaStyles} hasSubmittedStartForm={hasSubmittedStartForm}
placeholder={t('account_team:enterprise_auth_demand_placeholder')}
isInvalid={isEmptyAfterSubmit(watchedFields.demand) || !!fieldErrors.demand}
_invalid={invalidInputStyles}
{...startForm.register('demand', fieldRules.demand)}
/> />
</Field>
</Grid> </Grid>
</Section> </Section>
</Flex> </Flex>
); );
};
export default React.memo(EnterpriseAuthInfoForm); export default React.memo(EnterpriseAuthInfoForm);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment