diff --git a/app/routes/rule-groups.new.tsx b/app/routes/rule-groups.new.tsx index af712d0..ad59e11 100644 --- a/app/routes/rule-groups.new.tsx +++ b/app/routes/rule-groups.new.tsx @@ -100,12 +100,18 @@ export async function loader({ request }: LoaderFunctionArgs) { // console.log("获取到的ID参数:", id); // 获取一级分组列表 (用于选择父级分组) - const parentGroupsResponse = await getRuleGroups(frontendJWT); + // 🆕 使用增强的 getRuleGroups API,仅获取一级分组且已启用的分组 + const parentGroupsResponse = await getRuleGroups({ + pid: null, // 仅获取一级分组(pid为null表示顶级分组) + is_enabled: true, // 仅获取已启用的分组 + pageSize: 100, // 获取足够多的分组 + token: frontendJWT + }); if (parentGroupsResponse.error) { console.error("获取父分组列表失败:", parentGroupsResponse.error); throw new Error(parentGroupsResponse.error); } - + const parentGroups: ParentGroup[] = parentGroupsResponse.data ? parentGroupsResponse.data.map(group => ({ id: group.id, name: group.name @@ -194,7 +200,7 @@ export async function action({ request }: ActionFunctionArgs) { code: code.trim(), description: description?.trim() || "", is_enabled: status === "active", - pid: parentId || undefined // 🆕 NULL/undefined 表示顶级分组 + pid: parentId || null // 🆕 NULL 表示顶级分组 }; try { @@ -274,10 +280,14 @@ export default function RuleGroupNew() { parentId?: string; general?: string; }>({}); - + + // 🆕 编码唯一性验证状态 + const [codeValidating, setCodeValidating] = useState(false); + const [codeValidationTimer, setCodeValidationTimer] = useState(null); + // 表单引用 const formRef = useRef(null); - + // 字段是否被触摸过(用于确定何时显示错误) const [touchedFields, setTouchedFields] = useState<{ name: boolean; @@ -310,6 +320,44 @@ export default function RuleGroupNew() { } }, [group]); + // 🆕 异步验证编码唯一性 + const validateCodeUnique = async (code: string): Promise => { + if (!code || code.trim() === "") { + return ""; + } + + try { + setCodeValidating(true); + const response = await getRuleGroups({ + code: code.trim(), + pageSize: 10, + token: data.frontendJWT + }); + + if (response.error) { + console.error("验证编码唯一性失败:", response.error); + return ""; // 验证失败时不显示错误,避免干扰用户 + } + + if (response.data && response.data.length > 0) { + // 在编辑模式下,排除当前分组自身 + const isDuplicate = response.data.some(g => + g.id !== group?.id && g.code === code.trim() + ); + if (isDuplicate) { + return "该编码已被使用,请使用其他编码"; + } + } + + return ""; + } catch (error) { + console.error("验证编码唯一性出错:", error); + return ""; + } finally { + setCodeValidating(false); + } + }; + // 验证表单字段 const validateField = (field: string, value: string) => { switch (field) { @@ -323,8 +371,8 @@ export default function RuleGroupNew() { } return ""; case 'parentId': - return formValues.groupType === "secondary" && value.trim() === "" - ? "请选择上级分组" + return formValues.groupType === "secondary" && value.trim() === "" + ? "请选择上级分组" : ""; default: return ""; @@ -334,12 +382,12 @@ export default function RuleGroupNew() { // 处理字段改变 const handleChange = (e: React.ChangeEvent) => { const { name, value } = e.target; - + setFormValues(prev => ({ ...prev, [name]: value })); - + // 标记字段为已触摸 if (['name', 'code', 'parentId'].includes(name)) { setTouchedFields(prev => ({ @@ -347,13 +395,34 @@ export default function RuleGroupNew() { [name]: true })); } - + // 实时验证 const error = validateField(name, value); setFormErrors(prev => ({ ...prev, [name]: error })); + + // 🆕 编码字段特殊处理:异步验证唯一性(防抖处理) + if (name === 'code' && !error) { + // 清除之前的定时器 + if (codeValidationTimer) { + clearTimeout(codeValidationTimer); + } + + // 设置新的定时器,500ms后执行验证 + const timer = setTimeout(async () => { + const uniqueError = await validateCodeUnique(value); + if (uniqueError) { + setFormErrors(prev => ({ + ...prev, + code: uniqueError + })); + } + }, 500); + + setCodeValidationTimer(timer); + } }; // 处理分组类型更改 @@ -551,6 +620,7 @@ export default function RuleGroupNew() {