Files
TERES_web_frontend/src/pages/knowledge/components/GeneralForm.tsx

219 lines
6.2 KiB
TypeScript
Raw Normal View History

import React, { useRef } from 'react';
import { useFormContext, Controller, type UseFormReturn } from 'react-hook-form';
import {
Box,
Typography,
TextField,
FormControl,
InputLabel,
Select,
MenuItem,
Grid,
Avatar,
Button,
IconButton,
} from '@mui/material';
import {
PhotoCamera as PhotoCameraIcon,
Delete as DeleteIcon,
} from '@mui/icons-material';
import { useTranslation } from 'react-i18next';
interface GeneralFormProps {
form?: UseFormReturn;
onSubmit?: (data: any) => void;
isSubmitting?: boolean;
onCancel?: () => void;
submitButtonText?: string;
cancelButtonText?: string;
}
function GeneralForm({
form: propForm,
onSubmit,
isSubmitting,
onCancel,
submitButtonText,
cancelButtonText,
}: GeneralFormProps = {}) {
const { t } = useTranslation();
const defaultSubmitButtonText = submitButtonText || t('common.save');
const defaultCancelButtonText = cancelButtonText || t('common.cancel');
// 优先使用props传递的form否则使用FormProvider的context
let contextForm: UseFormReturn | null = null;
try {
contextForm = useFormContext();
} catch (error) {
contextForm = null;
}
const form = propForm || contextForm;
if (!form) {
console.error('GeneralForm: No form context found. Component must be used within a FormProvider or receive a form prop.');
return (
<Box sx={{ p: 2, textAlign: 'center' }}>
<Typography color="error">
{t('form.configurationError')}
</Typography>
</Box>
);
}
const { control, watch, setValue, handleSubmit } = form;
const fileInputRef = useRef<HTMLInputElement>(null);
const handleAvatarUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file && form) {
const reader = new FileReader();
reader.onload = (e) => {
form.setValue('avatar', e.target?.result as string);
};
reader.readAsDataURL(file);
}
};
const handleAvatarDelete = () => {
if (form) {
form.setValue('avatar', undefined);
}
};
const handleAvatarClick = () => {
fileInputRef.current?.click();
};
const avatar = watch('avatar', '');
return (
<Box sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
{t('knowledge.basicInfo')}
</Typography>
<Grid container spacing={3}>
<Grid size={{xs:12, md:6}}>
<Box sx={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 2 }}>
<Avatar
src={avatar}
sx={{ width: 120, height: 120, cursor: 'pointer' }}
onClick={handleAvatarClick}
>
{!avatar && <PhotoCameraIcon sx={{ fontSize: 40 }} />}
</Avatar>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button
variant="outlined"
size="small"
startIcon={<PhotoCameraIcon />}
onClick={handleAvatarClick}
>
{t('knowledge.uploadAvatar')}
</Button>
{avatar && (
<IconButton
size="small"
color="error"
onClick={handleAvatarDelete}
>
<DeleteIcon />
</IconButton>
)}
</Box>
<input
ref={fileInputRef}
type="file"
accept="image/*"
style={{ display: 'none' }}
onChange={handleAvatarUpload}
/>
</Box>
</Grid>
{/* 表单字段 */}
<Grid size={{xs:12,md:8}}>
<Grid container spacing={2}>
<Grid size={{xs:12}}>
<Controller
name="name"
control={control}
rules={{ required: t('knowledge.nameRequired') }}
render={({ field, fieldState: { error } }) => (
<TextField
{...field}
label={t('knowledge.knowledgeBaseName')}
fullWidth
required
error={!!error}
helperText={error?.message}
/>
)}
/>
</Grid>
<Grid size={{xs:12}}>
<Controller
name="description"
control={control}
render={({ field }) => (
<TextField
{...field}
label={t('common.description')}
fullWidth
multiline
rows={3}
placeholder={t('knowledge.descriptionPlaceholder')}
/>
)}
/>
</Grid>
<Grid size={{xs:12}}>
<Controller
name="permission"
control={control}
render={({ field }) => (
<FormControl fullWidth>
<InputLabel>{t('knowledge.permissionSettings')}</InputLabel>
<Select {...field} label={t('knowledge.permissionSettings')}>
<MenuItem value="me">{t('knowledge.onlyMe')}</MenuItem>
<MenuItem value="team">{t('knowledge.teamMembers')}</MenuItem>
</Select>
</FormControl>
)}
/>
</Grid>
</Grid>
</Grid>
</Grid>
{/* 表单操作按钮 - 仅在有onSubmit回调时显示 */}
{onSubmit && (
<Box sx={{ mt: 4, display: 'flex', justifyContent: 'flex-end', gap: 2 }}>
{onCancel && (
<Button
variant="outlined"
onClick={onCancel}
disabled={isSubmitting}
>
{defaultCancelButtonText}
</Button>
)}
<Button
variant="contained"
onClick={form ? form.handleSubmit(onSubmit) : undefined}
disabled={isSubmitting || !form}
>
{isSubmitting ? t('common.submitting') : defaultSubmitButtonText}
</Button>
</Box>
)}
</Box>
);
}
export default GeneralForm;