"use client"; import React, { useState, useCallback, useEffect, useRef } from "react"; import { AdminPanelConfig, AdminPanelState, UseAdminPanelOptions, UseAdminPanelReturn, FieldConfig, ValidationRule } from "@/types/admin-panel"; import { useForm } from "react-hook-form"; import { configFormSchema, ConfigFormValues } from "@/types/config"; import { zodResolver } from "@hookform/resolvers/zod"; // Helper function to get nested value function getNestedValue(obj: any, path: string): any { return path.split('.').reduce((current, key) => current?.[key], obj); } // Helper function to set nested value function setNestedValue(obj: any, path: string, value: any): any { const keys = path.split('.'); const result = { ...obj }; let current = result; for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; current[key] = current[key] ? { ...current[key] } : {}; current = current[key]; } current[keys[keys.length - 1]] = value; return result; } // Helper function to validate a field function validateField(field: FieldConfig, value: any): string | null { if (!field.validation) return null; const { validation } = field; // Required validation if (validation.required && (value === undefined || value === null || value === '')) { return `${field.label}是必填项`; } // Skip other validations if value is empty and not required if (value === undefined || value === null || value === '') { return null; } // Min/Max validation for numbers if (typeof value === 'number') { if (validation.min !== undefined && value < validation.min) { return `${field.label}不能小于${validation.min}`; } if (validation.max !== undefined && value > validation.max) { return `${field.label}不能大于${validation.max}`; } } // Length validation for strings if (typeof value === 'string') { if (validation.minLength !== undefined && value.length < validation.minLength) { return `${field.label}长度不能少于${validation.minLength}个字符`; } if (validation.maxLength !== undefined && value.length > validation.maxLength) { return `${field.label}长度不能超过${validation.maxLength}个字符`; } } // Pattern validation if (validation.pattern && typeof value === 'string' && !validation.pattern.test(value)) { return `${field.label}格式不正确`; } // Custom validation if (validation.custom) { return validation.custom(value); } return null; } // Get all fields from config function getAllFields(config: AdminPanelConfig): FieldConfig[] { return config.tabs.flatMap(tab => tab.sections.flatMap(section => section.fields) ); } export function useAdminPanel(options: UseAdminPanelOptions): UseAdminPanelReturn { const { config, initialValues = {}, onSubmit } = options; // 计算初始值,包含字段默认值 const computedInitialValues = React.useMemo(() => { const fields = getAllFields(config); const values: Record = { ...initialValues }; fields.forEach(field => { if (getNestedValue(values, field.id) === undefined) { setNestedValue(values, field.id, field.value); } }); return values; }, [config, initialValues]); // 使用 react-hook-form 作为唯一的数据源 const form = useForm({ resolver: zodResolver(configFormSchema), defaultValues: computedInitialValues, }); // 通过 form.watch() 监听所有表单数据变化 const values = form.watch(); // 简化的状态,只保留非表单数据 const [state, setState] = useState>({ errors: {}, loading: false, saving: false, }); // Auto-save timer 和上次保存的值 const autoSaveTimer = useRef(null); const lastSavedValues = useRef>(computedInitialValues); // 使用 form.formState.dirtyFields 来判断字段是否已修改 const { formState } = form; const { dirtyFields } = formState; // 重新设置初始值(当配置或初始值变化时) useEffect(() => { form.reset(computedInitialValues); lastSavedValues.current = computedInitialValues; }, [computedInitialValues]); // 移除 form 依赖 // Auto-save 功能 useEffect(() => { if (!config.autoSave || !onSubmit) return; const delay = config.autoSaveDelay || 2000; if (autoSaveTimer.current) { clearTimeout(autoSaveTimer.current); } // 检查是否有变化 const hasChanges = JSON.stringify(values) !== JSON.stringify(lastSavedValues.current); const hasDirtyFields = Object.keys(dirtyFields).length > 0; if (hasChanges && hasDirtyFields) { autoSaveTimer.current = setTimeout(async () => { try { const currentValues = form.getValues(); await onSubmit(currentValues); lastSavedValues.current = currentValues; // 标记所有字段为干净状态,但不改变值 form.reset(currentValues, { keepValues: true }); } catch (error) { console.error('Auto-save failed:', error); } }, delay); } return () => { if (autoSaveTimer.current) { clearTimeout(autoSaveTimer.current); } }; }, [values, dirtyFields, config.autoSave, config.autoSaveDelay, onSubmit]); // 移除 form 依赖 // 缓存字段配置以避免循环依赖 const fields = React.useMemo(() => getAllFields(config), [config]); const validateOnChange = config.validateOnChange; // 防抖验证,避免频繁验证 const validationTimer = useRef(null); // 执行字段验证的函数 const performValidation = useCallback(() => { const errors: Record = {}; fields.forEach(field => { // 跳过禁用或只读字段的验证 if (field.disabled || field.readOnly) return; // 检查条件渲染 if (field.showWhen && !field.showWhen(values)) return; const value = getNestedValue(values, field.id); const error = validateField(field, value); if (error) { errors[field.id] = error; } }); setState(prev => ({ ...prev, errors })); }, [fields, values]); // 初始验证 - 组件挂载时和配置变化时执行一次 useEffect(() => { performValidation(); }, [fields, computedInitialValues]); // 当字段配置或初始值变化时重新验证 // 监听值变化进行实时验证 useEffect(() => { if (!validateOnChange) return; // 清除之前的验证定时器 if (validationTimer.current) { clearTimeout(validationTimer.current); } // 延迟验证,避免频繁触发 validationTimer.current = setTimeout(() => { performValidation(); }, 300); // 300ms 防抖 return () => { if (validationTimer.current) { clearTimeout(validationTimer.current); } }; }, [values, validateOnChange, performValidation]); // Actions - 使用 form 的方法来更新值 const setValue = useCallback((path: string, value: any) => { (form.setValue as any)(path, value, { shouldDirty: true, shouldValidate: validateOnChange }); // 清除该字段的错误 setState(prev => { const newErrors = { ...prev.errors }; delete newErrors[path]; return { ...prev, errors: newErrors }; }); // 调用 onChange 回调 if (config.onValueChange) { const currentValues = form.getValues(); const newValues = setNestedValue(currentValues, path, value); config.onValueChange(path, value, newValues); } }, [form, config.onValueChange, validateOnChange]); // 移除 values 依赖 const setValues = useCallback((newValues: Record) => { Object.entries(newValues).forEach(([path, value]) => { (form.setValue as any)(path, value, { shouldDirty: true }); }); }, [form]); const resetValue = useCallback((path: string) => { const field = fields.find(f => f.id === path); if (field) { (form.setValue as any)(path, field.value, { shouldDirty: false }); // 清除该字段的错误 setState(prev => { const newErrors = { ...prev.errors }; delete newErrors[path]; return { ...prev, errors: newErrors }; }); } }, [fields, form]); const resetAll = useCallback(() => { form.reset(computedInitialValues); setState(prev => ({ ...prev, errors: {} })); lastSavedValues.current = computedInitialValues; if (config.onReset) { config.onReset(); } }, [computedInitialValues, config.onReset, form]); const validate = useCallback((): boolean => { const currentValues = form.getValues(); const errors: Record = {}; fields.forEach(field => { // 跳过禁用或只读字段的验证 if (field.disabled || field.readOnly) return; // 检查条件渲染 if (field.showWhen && !field.showWhen(currentValues)) return; const value = getNestedValue(currentValues, field.id); const error = validateField(field, value); if (error) { errors[field.id] = error; } }); // 自定义验证 if (config.onValidate) { const customErrors = config.onValidate(currentValues); Object.assign(errors, customErrors); } setState(prev => ({ ...prev, errors })); return Object.keys(errors).length === 0; }, [fields, config.onValidate, form]); const save = useCallback(async () => { debugger if (!onSubmit) return; // 验证(如果需要) // if (config.validateOnSubmit !== false) { // const isValid = validate(); // if (!isValid) return; // } setState(prev => ({ ...prev, saving: true })); const currentValues = form.getValues(); try { await onSubmit(currentValues); lastSavedValues.current = currentValues; // 标记所有字段为干净状态,但不改变值 form.reset(currentValues, { keepValues: true }); setState(prev => ({ ...prev, saving: false, errors: {} })); if (config.onSave) { await config.onSave(currentValues); } } catch (error) { setState(prev => ({ ...prev, saving: false })); throw error; } }, [config.validateOnSubmit, config.onSave, onSubmit, validate, form]); const clearErrors = useCallback(() => { setState(prev => ({ ...prev, errors: {} })); }, []); // Helpers - 直接使用 form.watch() 的数据 const getValue = useCallback((path: string) => { return getNestedValue(values, path); }, [values]); const getError = useCallback((path: string) => { return state.errors[path] || getNestedValue(form.formState.errors, path)?.message; }, [state.errors, form.formState.errors]); const isDirty = useCallback((path?: string) => { if (path) { return Boolean(getNestedValue(dirtyFields, path)); } return Object.keys(dirtyFields).length > 0; }, [dirtyFields]); const isValid = useCallback((path?: string) => { if (path) { return !state.errors[path] && !getNestedValue(form.formState.errors, path); } return Object.keys(state.errors).length === 0 && form.formState.isValid; }, [state.errors, form.formState.errors, form.formState.isValid]); // 构建返回的状态,包含 values const adminPanelState: AdminPanelState = { ...state, values, dirty: dirtyFields, }; return { form, state: adminPanelState, actions: { setValue, setValues, resetValue, resetAll, save, validate, clearErrors, }, helpers: { getValue, getError, isDirty, isValid, }, }; }