2025-10-13 18:25:44 +08:00
|
|
|
|
import React, { useRef } from 'react';
|
2025-10-16 18:52:20 +08:00
|
|
|
|
import { useFormContext, Controller, type UseFormReturn } from 'react-hook-form';
|
2025-10-13 18:25:44 +08:00
|
|
|
|
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';
|
2025-10-29 16:40:20 +08:00
|
|
|
|
import { useTranslation } from 'react-i18next';
|
2025-10-13 18:25:44 +08:00
|
|
|
|
|
2025-10-16 18:52:20 +08:00
|
|
|
|
interface GeneralFormProps {
|
|
|
|
|
|
form?: UseFormReturn;
|
|
|
|
|
|
onSubmit?: (data: any) => void;
|
|
|
|
|
|
isSubmitting?: boolean;
|
|
|
|
|
|
onCancel?: () => void;
|
|
|
|
|
|
submitButtonText?: string;
|
|
|
|
|
|
cancelButtonText?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function GeneralForm({
|
|
|
|
|
|
form: propForm,
|
|
|
|
|
|
onSubmit,
|
|
|
|
|
|
isSubmitting,
|
|
|
|
|
|
onCancel,
|
2025-10-29 16:40:20 +08:00
|
|
|
|
submitButtonText,
|
|
|
|
|
|
cancelButtonText,
|
2025-10-16 18:52:20 +08:00
|
|
|
|
}: GeneralFormProps = {}) {
|
2025-10-29 16:40:20 +08:00
|
|
|
|
const { t } = useTranslation();
|
|
|
|
|
|
|
|
|
|
|
|
const defaultSubmitButtonText = submitButtonText || t('common.save');
|
|
|
|
|
|
const defaultCancelButtonText = cancelButtonText || t('common.cancel');
|
2025-10-16 18:52:20 +08:00
|
|
|
|
// 优先使用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">
|
2025-10-29 16:40:20 +08:00
|
|
|
|
{t('form.configurationError')}
|
2025-10-16 18:52:20 +08:00
|
|
|
|
</Typography>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const { control, watch, setValue, handleSubmit } = form;
|
2025-10-13 18:25:44 +08:00
|
|
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
|
|
|
|
|
|
|
|
|
|
const handleAvatarUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
|
|
|
const file = event.target.files?.[0];
|
2025-10-16 18:52:20 +08:00
|
|
|
|
if (file && form) {
|
2025-10-13 18:25:44 +08:00
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
|
reader.onload = (e) => {
|
2025-10-16 18:52:20 +08:00
|
|
|
|
form.setValue('avatar', e.target?.result as string);
|
2025-10-13 18:25:44 +08:00
|
|
|
|
};
|
|
|
|
|
|
reader.readAsDataURL(file);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleAvatarDelete = () => {
|
2025-10-16 18:52:20 +08:00
|
|
|
|
if (form) {
|
|
|
|
|
|
form.setValue('avatar', undefined);
|
|
|
|
|
|
}
|
2025-10-13 18:25:44 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-15 16:24:53 +08:00
|
|
|
|
const handleAvatarClick = () => {
|
|
|
|
|
|
fileInputRef.current?.click();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-16 18:52:20 +08:00
|
|
|
|
const avatar = watch('avatar', '');
|
2025-10-13 18:25:44 +08:00
|
|
|
|
|
|
|
|
|
|
return (
|
2025-10-15 16:24:53 +08:00
|
|
|
|
<Box sx={{ p: 3 }}>
|
2025-10-13 18:25:44 +08:00
|
|
|
|
<Typography variant="h6" gutterBottom>
|
2025-10-29 16:40:20 +08:00
|
|
|
|
{t('knowledge.basicInfo')}
|
2025-10-13 18:25:44 +08:00
|
|
|
|
</Typography>
|
|
|
|
|
|
|
|
|
|
|
|
<Grid container spacing={3}>
|
2025-10-15 16:24:53 +08:00
|
|
|
|
<Grid size={{xs:12, md:6}}>
|
|
|
|
|
|
<Box sx={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 2 }}>
|
2025-10-13 18:25:44 +08:00
|
|
|
|
<Avatar
|
2025-10-15 16:24:53 +08:00
|
|
|
|
src={avatar}
|
|
|
|
|
|
sx={{ width: 120, height: 120, cursor: 'pointer' }}
|
|
|
|
|
|
onClick={handleAvatarClick}
|
2025-10-13 18:25:44 +08:00
|
|
|
|
>
|
2025-10-15 16:24:53 +08:00
|
|
|
|
{!avatar && <PhotoCameraIcon sx={{ fontSize: 40 }} />}
|
2025-10-13 18:25:44 +08:00
|
|
|
|
</Avatar>
|
2025-10-15 16:24:53 +08:00
|
|
|
|
|
|
|
|
|
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
2025-10-13 18:25:44 +08:00
|
|
|
|
<Button
|
|
|
|
|
|
variant="outlined"
|
|
|
|
|
|
size="small"
|
2025-10-15 16:24:53 +08:00
|
|
|
|
startIcon={<PhotoCameraIcon />}
|
|
|
|
|
|
onClick={handleAvatarClick}
|
2025-10-13 18:25:44 +08:00
|
|
|
|
>
|
2025-10-29 16:40:20 +08:00
|
|
|
|
{t('knowledge.uploadAvatar')}
|
2025-10-13 18:25:44 +08:00
|
|
|
|
</Button>
|
2025-10-15 16:24:53 +08:00
|
|
|
|
{avatar && (
|
2025-10-13 18:25:44 +08:00
|
|
|
|
<IconButton
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
color="error"
|
2025-10-15 16:24:53 +08:00
|
|
|
|
onClick={handleAvatarDelete}
|
2025-10-13 18:25:44 +08:00
|
|
|
|
>
|
|
|
|
|
|
<DeleteIcon />
|
|
|
|
|
|
</IconButton>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
|
2025-10-15 16:24:53 +08:00
|
|
|
|
<input
|
|
|
|
|
|
ref={fileInputRef}
|
|
|
|
|
|
type="file"
|
|
|
|
|
|
accept="image/*"
|
|
|
|
|
|
style={{ display: 'none' }}
|
|
|
|
|
|
onChange={handleAvatarUpload}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Box>
|
2025-10-13 18:25:44 +08:00
|
|
|
|
</Grid>
|
|
|
|
|
|
|
2025-10-15 16:24:53 +08:00
|
|
|
|
{/* 表单字段 */}
|
|
|
|
|
|
<Grid size={{xs:12,md:8}}>
|
|
|
|
|
|
<Grid container spacing={2}>
|
|
|
|
|
|
<Grid size={{xs:12}}>
|
|
|
|
|
|
<Controller
|
|
|
|
|
|
name="name"
|
|
|
|
|
|
control={control}
|
2025-10-29 16:40:20 +08:00
|
|
|
|
rules={{ required: t('knowledge.nameRequired') }}
|
2025-10-15 16:24:53 +08:00
|
|
|
|
render={({ field, fieldState: { error } }) => (
|
|
|
|
|
|
<TextField
|
|
|
|
|
|
{...field}
|
2025-10-29 16:40:20 +08:00
|
|
|
|
label={t('knowledge.knowledgeBaseName')}
|
2025-10-15 16:24:53 +08:00
|
|
|
|
fullWidth
|
|
|
|
|
|
required
|
|
|
|
|
|
error={!!error}
|
|
|
|
|
|
helperText={error?.message}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Grid>
|
2025-10-13 18:25:44 +08:00
|
|
|
|
|
2025-10-15 16:24:53 +08:00
|
|
|
|
<Grid size={{xs:12}}>
|
|
|
|
|
|
<Controller
|
|
|
|
|
|
name="description"
|
|
|
|
|
|
control={control}
|
|
|
|
|
|
render={({ field }) => (
|
|
|
|
|
|
<TextField
|
|
|
|
|
|
{...field}
|
2025-10-29 16:40:20 +08:00
|
|
|
|
label={t('common.description')}
|
2025-10-15 16:24:53 +08:00
|
|
|
|
fullWidth
|
|
|
|
|
|
multiline
|
|
|
|
|
|
rows={3}
|
2025-10-29 16:40:20 +08:00
|
|
|
|
placeholder={t('knowledge.descriptionPlaceholder')}
|
2025-10-15 16:24:53 +08:00
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Grid>
|
2025-10-13 18:25:44 +08:00
|
|
|
|
|
2025-10-15 16:24:53 +08:00
|
|
|
|
<Grid size={{xs:12}}>
|
|
|
|
|
|
<Controller
|
|
|
|
|
|
name="permission"
|
|
|
|
|
|
control={control}
|
|
|
|
|
|
render={({ field }) => (
|
|
|
|
|
|
<FormControl fullWidth>
|
2025-10-29 16:40:20 +08:00
|
|
|
|
<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>
|
2025-10-15 16:24:53 +08:00
|
|
|
|
</Select>
|
|
|
|
|
|
</FormControl>
|
|
|
|
|
|
)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Grid>
|
|
|
|
|
|
</Grid>
|
2025-10-13 18:25:44 +08:00
|
|
|
|
</Grid>
|
|
|
|
|
|
</Grid>
|
2025-10-16 18:52:20 +08:00
|
|
|
|
|
|
|
|
|
|
{/* 表单操作按钮 - 仅在有onSubmit回调时显示 */}
|
|
|
|
|
|
{onSubmit && (
|
|
|
|
|
|
<Box sx={{ mt: 4, display: 'flex', justifyContent: 'flex-end', gap: 2 }}>
|
|
|
|
|
|
{onCancel && (
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="outlined"
|
|
|
|
|
|
onClick={onCancel}
|
|
|
|
|
|
disabled={isSubmitting}
|
|
|
|
|
|
>
|
2025-10-29 16:40:20 +08:00
|
|
|
|
{defaultCancelButtonText}
|
2025-10-16 18:52:20 +08:00
|
|
|
|
</Button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="contained"
|
|
|
|
|
|
onClick={form ? form.handleSubmit(onSubmit) : undefined}
|
|
|
|
|
|
disabled={isSubmitting || !form}
|
|
|
|
|
|
>
|
2025-10-29 16:40:20 +08:00
|
|
|
|
{isSubmitting ? t('common.submitting') : defaultSubmitButtonText}
|
2025-10-16 18:52:20 +08:00
|
|
|
|
</Button>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
)}
|
2025-10-13 18:25:44 +08:00
|
|
|
|
</Box>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default GeneralForm;
|