修改时间范围组件,评查详情创建新的数据结构来适配新的返回格式
This commit is contained in:
+178
-40
@@ -1,9 +1,10 @@
|
||||
// app/routes/rule-groups.new.tsx
|
||||
import { redirect, json, type ActionFunctionArgs, type LoaderFunctionArgs, type MetaFunction } from "@remix-run/node";
|
||||
import { redirect, type ActionFunctionArgs, type LoaderFunctionArgs, type MetaFunction } from "@remix-run/node";
|
||||
import { useLoaderData, useActionData, useNavigation, Form } from "@remix-run/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { Button } from "~/components/ui/Button";
|
||||
import { Card } from "~/components/ui/Card";
|
||||
import { toastService } from "~/components/ui/Toast";
|
||||
import ruleGroupsNewStyles from "~/styles/pages/rule-groups_new.css?url";
|
||||
import {
|
||||
getRuleGroups,
|
||||
@@ -199,7 +200,7 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
// 处理API响应
|
||||
if (response.error) {
|
||||
console.error("保存分组失败:", response.error);
|
||||
return json<ActionData>({
|
||||
return Response.json({
|
||||
success: false,
|
||||
errors: {
|
||||
general: response.error
|
||||
@@ -209,10 +210,11 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
}
|
||||
|
||||
// 保存成功,重定向到列表页
|
||||
toastService.success("保存成功");
|
||||
return redirect("/rule-groups");
|
||||
} catch (error) {
|
||||
console.error("保存分组失败:", error);
|
||||
return json<ActionData>({
|
||||
return Response.json({
|
||||
success: false,
|
||||
errors: {
|
||||
general: error instanceof Error ? error.message : "保存分组失败,请稍后重试"
|
||||
@@ -230,31 +232,162 @@ export default function RuleGroupNew() {
|
||||
const navigation = useNavigation();
|
||||
const isSubmitting = navigation.state === "submitting";
|
||||
|
||||
// 表单状态
|
||||
const [groupType, setGroupType] = useState<"primary" | "secondary">("primary");
|
||||
const [showParentSelect, setShowParentSelect] = useState(false);
|
||||
|
||||
// 解构数据
|
||||
const { group, parentGroups, isEdit, error } = data;
|
||||
|
||||
// 初始化表单状态
|
||||
// 表单状态管理 - 使用受控组件
|
||||
const [formValues, setFormValues] = useState<{
|
||||
groupType: "primary" | "secondary";
|
||||
name: string;
|
||||
code: string;
|
||||
parentId: string;
|
||||
description: string;
|
||||
status: string;
|
||||
}>({
|
||||
groupType: group?.parentId ? "secondary" : "primary",
|
||||
name: group?.name || "",
|
||||
code: group?.code || "",
|
||||
parentId: group?.parentId || "",
|
||||
description: group?.description || "",
|
||||
status: group?.status || "active",
|
||||
});
|
||||
|
||||
// 表单验证错误状态
|
||||
const [formErrors, setFormErrors] = useState<{
|
||||
name?: string;
|
||||
code?: string;
|
||||
parentId?: string;
|
||||
general?: string;
|
||||
}>({});
|
||||
|
||||
// 表单引用
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
|
||||
// 字段是否被触摸过(用于确定何时显示错误)
|
||||
const [touchedFields, setTouchedFields] = useState<{
|
||||
name: boolean;
|
||||
code: boolean;
|
||||
parentId: boolean;
|
||||
}>({
|
||||
name: false,
|
||||
code: false,
|
||||
parentId: false
|
||||
});
|
||||
|
||||
// 从 actionData 初始化表单错误
|
||||
useEffect(() => {
|
||||
if (actionData?.errors) {
|
||||
setFormErrors(actionData.errors);
|
||||
}
|
||||
}, [actionData]);
|
||||
|
||||
// 根据加载的组数据初始化表单
|
||||
useEffect(() => {
|
||||
if (group) {
|
||||
if (group.parentId) {
|
||||
setGroupType("secondary");
|
||||
setShowParentSelect(true);
|
||||
} else {
|
||||
setGroupType("primary");
|
||||
setShowParentSelect(false);
|
||||
}
|
||||
setFormValues({
|
||||
groupType: group.parentId ? "secondary" : "primary",
|
||||
name: group.name,
|
||||
code: group.code,
|
||||
parentId: group.parentId || "",
|
||||
description: group.description || "",
|
||||
status: group.status
|
||||
});
|
||||
}
|
||||
}, [group]);
|
||||
|
||||
// 处理分组类型变更
|
||||
// 验证表单字段
|
||||
const validateField = (field: string, value: string) => {
|
||||
switch (field) {
|
||||
case 'name':
|
||||
return value.trim() === "" ? "分组名称不能为空" : "";
|
||||
case 'code':
|
||||
if (value.trim() === "") {
|
||||
return "分组编码不能为空";
|
||||
} else if (!/^[a-zA-Z0-9-_]+$/.test(value)) {
|
||||
return "分组编码只能包含字母、数字、连字符和下划线";
|
||||
}
|
||||
return "";
|
||||
case 'parentId':
|
||||
return formValues.groupType === "secondary" && value.trim() === ""
|
||||
? "请选择上级分组"
|
||||
: "";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
// 处理字段改变
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
|
||||
const { name, value } = e.target;
|
||||
|
||||
setFormValues(prev => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}));
|
||||
|
||||
// 标记字段为已触摸
|
||||
if (['name', 'code', 'parentId'].includes(name)) {
|
||||
setTouchedFields(prev => ({
|
||||
...prev,
|
||||
[name]: true
|
||||
}));
|
||||
}
|
||||
|
||||
// 实时验证
|
||||
const error = validateField(name, value);
|
||||
setFormErrors(prev => ({
|
||||
...prev,
|
||||
[name]: error
|
||||
}));
|
||||
};
|
||||
|
||||
// 处理分组类型更改
|
||||
const handleGroupTypeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value as "primary" | "secondary";
|
||||
setGroupType(value);
|
||||
setShowParentSelect(value === "secondary");
|
||||
|
||||
setFormValues(prev => ({
|
||||
...prev,
|
||||
groupType: value
|
||||
}));
|
||||
|
||||
// 如果切换为一级分组,清除父分组错误
|
||||
if (value === "primary") {
|
||||
setFormErrors(prev => ({
|
||||
...prev,
|
||||
parentId: ""
|
||||
}));
|
||||
} else if (value === "secondary" && touchedFields.parentId) {
|
||||
// 如果切换为二级分组,且父分组字段已被触摸,重新验证
|
||||
const error = validateField('parentId', formValues.parentId);
|
||||
setFormErrors(prev => ({
|
||||
...prev,
|
||||
parentId: error
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
// 处理表单提交前验证
|
||||
const handleBeforeSubmit = (e: React.FormEvent) => {
|
||||
// 标记所有字段为已触摸
|
||||
setTouchedFields({
|
||||
name: true,
|
||||
code: true,
|
||||
parentId: true
|
||||
});
|
||||
|
||||
// 验证所有字段
|
||||
const errors = {
|
||||
name: validateField('name', formValues.name),
|
||||
code: validateField('code', formValues.code),
|
||||
parentId: validateField('parentId', formValues.parentId)
|
||||
};
|
||||
|
||||
setFormErrors(errors);
|
||||
|
||||
// 如果有错误,阻止提交
|
||||
if (errors.name || errors.code || (formValues.groupType === "secondary" && errors.parentId)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
// 如果加载数据时出错,显示错误信息
|
||||
@@ -304,15 +437,15 @@ export default function RuleGroupNew() {
|
||||
</div>
|
||||
|
||||
{/* 错误提示 */}
|
||||
{actionData?.errors?.general && (
|
||||
{formErrors.general && (
|
||||
<div className="general-error">
|
||||
<i className="ri-error-warning-line mr-2"></i>
|
||||
{actionData.errors.general}
|
||||
{formErrors.general}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 表单 */}
|
||||
<Form method="post" id="group-form">
|
||||
<Form method="post" id="group-form" ref={formRef} onSubmit={handleBeforeSubmit}>
|
||||
{/* 如果是编辑模式,添加ID */}
|
||||
{group?.id && <input type="hidden" name="id" value={group.id} />}
|
||||
|
||||
@@ -336,7 +469,7 @@ export default function RuleGroupNew() {
|
||||
name="groupType"
|
||||
className="radio-input"
|
||||
value="primary"
|
||||
checked={groupType === "primary"}
|
||||
checked={formValues.groupType === "primary"}
|
||||
onChange={handleGroupTypeChange}
|
||||
/>
|
||||
<span>一级分组</span>
|
||||
@@ -348,7 +481,7 @@ export default function RuleGroupNew() {
|
||||
name="groupType"
|
||||
className="radio-input"
|
||||
value="secondary"
|
||||
checked={groupType === "secondary"}
|
||||
checked={formValues.groupType === "secondary"}
|
||||
onChange={handleGroupTypeChange}
|
||||
/>
|
||||
<span>二级分组</span>
|
||||
@@ -358,7 +491,7 @@ export default function RuleGroupNew() {
|
||||
</div>
|
||||
|
||||
{/* 上级分组选择 */}
|
||||
{showParentSelect && (
|
||||
{formValues.groupType === "secondary" && (
|
||||
<div className="form-group">
|
||||
<label htmlFor="parentId" className="form-label">
|
||||
上级分组 <span className="required-mark">*</span>
|
||||
@@ -366,8 +499,9 @@ export default function RuleGroupNew() {
|
||||
<select
|
||||
id="parentId"
|
||||
name="parentId"
|
||||
className={`form-select ${actionData?.errors?.parentId ? 'error' : ''}`}
|
||||
defaultValue={group?.parentId || ""}
|
||||
className={`form-select ${touchedFields.parentId && formErrors.parentId ? 'error' : ''}`}
|
||||
value={formValues.parentId}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<option value="">请选择上级分组</option>
|
||||
{parentGroups
|
||||
@@ -378,8 +512,8 @@ export default function RuleGroupNew() {
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{actionData?.errors?.parentId && (
|
||||
<div className="form-error">{actionData.errors.parentId}</div>
|
||||
{touchedFields.parentId && formErrors.parentId && (
|
||||
<div className="form-error">{formErrors.parentId}</div>
|
||||
)}
|
||||
<div className="form-tip">选择此分组所属的上级分组</div>
|
||||
</div>
|
||||
@@ -396,12 +530,13 @@ export default function RuleGroupNew() {
|
||||
type="text"
|
||||
id="code"
|
||||
name="code"
|
||||
className={`form-input ${actionData?.errors?.code ? 'error' : ''}`}
|
||||
defaultValue={group?.code || actionData?.values?.code || ""}
|
||||
className={`form-input ${touchedFields.code && formErrors.code ? 'error' : ''}`}
|
||||
value={formValues.code}
|
||||
onChange={handleChange}
|
||||
placeholder="请输入分组编码,如contract-base"
|
||||
/>
|
||||
{actionData?.errors?.code && (
|
||||
<div className="form-error">{actionData.errors.code}</div>
|
||||
{touchedFields.code && formErrors.code && (
|
||||
<div className="form-error">{formErrors.code}</div>
|
||||
)}
|
||||
<div className="form-tip">编码只能包含字母、数字、连字符和下划线,且必须唯一</div>
|
||||
</div>
|
||||
@@ -415,12 +550,13 @@ export default function RuleGroupNew() {
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
className={`form-input ${actionData?.errors?.name ? 'error' : ''}`}
|
||||
defaultValue={group?.name || actionData?.values?.name || ""}
|
||||
className={`form-input ${touchedFields.name && formErrors.name ? 'error' : ''}`}
|
||||
value={formValues.name}
|
||||
onChange={handleChange}
|
||||
placeholder="请输入分组名称,如合同基本要素检查"
|
||||
/>
|
||||
{actionData?.errors?.name && (
|
||||
<div className="form-error">{actionData.errors.name}</div>
|
||||
{touchedFields.name && formErrors.name && (
|
||||
<div className="form-error">{formErrors.name}</div>
|
||||
)}
|
||||
<div className="form-tip">请使用简洁明了的名称,不超过30个字符</div>
|
||||
</div>
|
||||
@@ -443,7 +579,8 @@ export default function RuleGroupNew() {
|
||||
id="description"
|
||||
name="description"
|
||||
className="form-textarea"
|
||||
defaultValue={group?.description || actionData?.values?.description || ""}
|
||||
value={formValues.description}
|
||||
onChange={handleChange}
|
||||
placeholder="请输入分组描述,包括适用场景、分组目的等"
|
||||
></textarea>
|
||||
<div className="form-tip">详细描述有助于其他用户了解该分组的用途</div>
|
||||
@@ -456,7 +593,8 @@ export default function RuleGroupNew() {
|
||||
id="status"
|
||||
name="status"
|
||||
className="form-select"
|
||||
defaultValue={group?.status || actionData?.values?.status || "active"}
|
||||
value={formValues.status}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<option value="active">启用</option>
|
||||
<option value="inactive">禁用</option>
|
||||
@@ -472,7 +610,7 @@ export default function RuleGroupNew() {
|
||||
id="sortOrder"
|
||||
name="sortOrder"
|
||||
className="form-input"
|
||||
defaultValue={group?.sortOrder?.toString() || actionData?.values?.sortOrder || "0"}
|
||||
defaultValue={group?.sortOrder?.toString() || "0"}
|
||||
placeholder="请输入排序值,数字越小排序越靠前"
|
||||
min="0"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user