修改时间范围组件,评查详情创建新的数据结构来适配新的返回格式

This commit is contained in:
2025-04-22 20:49:18 +08:00
parent cd2f060d87
commit 6261950356
14 changed files with 678 additions and 299 deletions
+178 -40
View File
@@ -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"
/>