From 37134ff650c1bdcb9c2f0c8e70eecd7014a399ca Mon Sep 17 00:00:00 2001 From: Wenyan Date: Tue, 25 Nov 2025 13:23:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(evaluation):=20=E5=AE=8C=E6=88=90=E6=A8=A1?= =?UTF-8?q?=E5=9D=972.6=20-=20=E8=AF=84=E6=9F=A5=E7=82=B9=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E7=BB=84=E4=BB=B6=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rules.list.tsx 新增批量操作功能: * 添加批量选择复选框 * 实现批量启用/禁用评查点 * 实现批量删除评查点 * 添加操作结果提示和部分失败处理 - BasicInfo.tsx 新增异步编码验证: * 实现500ms防抖的实时编码唯一性验证 * 集成 getRulesList API 进行编码查重 * 编辑模式下排除当前评查点 * 添加验证中状态和错误提示UI - 通过TypeScript类型检查,无新增类型错误 - 批量操作支持部分成功场景,详细报告结果 - 改善用户体验,提供实时反馈 --- app/components/rules/new/BasicInfo.tsx | 88 ++++++++++++- app/routes/rules.list.tsx | 165 +++++++++++++++++++++++-- app/routes/rules.new.tsx | 3 + typecheck_result_module2_6.txt | 66 ++++++++++ 4 files changed, 307 insertions(+), 15 deletions(-) create mode 100644 typecheck_result_module2_6.txt diff --git a/app/components/rules/new/BasicInfo.tsx b/app/components/rules/new/BasicInfo.tsx index 91e8f36..9ae05c8 100644 --- a/app/components/rules/new/BasicInfo.tsx +++ b/app/components/rules/new/BasicInfo.tsx @@ -1,15 +1,26 @@ import React, { useState, useEffect } from 'react'; import type { EvaluationPoint } from '~/models/evaluation_points'; import type { EvaluationPointGroup } from '~/models/evaluation_point_groups'; +import { getRulesList } from '~/api/evaluation_points/rules'; + interface BasicInfoProps { onChange?: (data: Record) => void; initialData?: EvaluationPoint; evaluationPointGroups?: EvaluationPointGroup[]; riskOptions?: Array<{value: string, label: string}>; + frontendJWT?: string; + evaluationPointId?: number | string; } // 评查点基本信息组件 -export function BasicInfo({ onChange, initialData, evaluationPointGroups = [], riskOptions = [] }: BasicInfoProps) { +export function BasicInfo({ + onChange, + initialData, + evaluationPointGroups = [], + riskOptions = [], + frontendJWT, + evaluationPointId +}: BasicInfoProps) { const [formData, setFormData] = useState({ risk: 'medium', // 风险等级 默认中风险 is_enabled: true, // 是否启用 默认启用 @@ -21,6 +32,47 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [], r ...(initialData || {}) // 合并初始数据 }); + // 编码验证状态 + const [codeValidating, setCodeValidating] = useState(false); + const [codeError, setCodeError] = useState(''); + const [codeValidationTimer, setCodeValidationTimer] = useState(null); + + // 异步验证编码唯一性 + const validateCodeUnique = async (code: string): Promise => { + if (!code.trim()) { + return ''; // 空值不验证 + } + + setCodeValidating(true); + setCodeError(''); + + try { + const response = await getRulesList({ + keyword: code.trim(), + pageSize: 10, + token: frontendJWT + }); + + if (response.data && response.data.rules && response.data.rules.length > 0) { + // 检查是否有完全匹配的编码(排除当前编辑的评查点) + const isDuplicate = response.data.rules.some(rule => + rule.code === code.trim() && String(rule.id) !== String(evaluationPointId) + ); + + if (isDuplicate) { + return '该编码已被使用,请使用其他编码'; + } + } + + return ''; + } catch (error) { + console.error('验证编码唯一性失败:', error); + return ''; // 验证失败不阻止用户输入 + } finally { + setCodeValidating(false); + } + }; + // 找到当前评查点类型对应的code const getCheckpointTypeCode = () => { if (!formData.evaluation_point_groups_pid) return ""; @@ -80,11 +132,23 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [], r const newData = { ...formData }; // 映射id到表单字段名 switch(id) { - case 'rule-name': + case 'rule-name': newData.name = value; break; - case 'rule-code': + case 'rule-code': newData.code = value; + // 清除之前的验证定时器 + if (codeValidationTimer) { + clearTimeout(codeValidationTimer); + } + // 清除错误信息 + setCodeError(''); + // 设置新的验证定时器(500ms后触发验证) + const timer = setTimeout(async () => { + const error = await validateCodeUnique(value); + setCodeError(error); + }, 500); + setCodeValidationTimer(timer); break; case 'risk-level': newData.risk = value; @@ -197,6 +261,15 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [], r // } // }, [filteredRuleGroups, onChange]); + // 清理验证定时器 + useEffect(() => { + return () => { + if (codeValidationTimer) { + clearTimeout(codeValidationTimer); + } + }; + }, [codeValidationTimer]); + return (
@@ -221,16 +294,21 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [], r
-
用于系统标识的唯一编码
+ {codeError ? ( +
{codeError}
+ ) : ( +
用于系统标识的唯一编码
+ )}
)}
- {isDeveloper && ( - - )} +
+ {/* 批量操作按钮(仅在有选择时显示) */} + {isDeveloper && selectedIds.length > 0 && ( + <> + + + + + )} + {/* 新增按钮 */} + {isDeveloper && ( + + )} +
{/* 筛选区域 */} diff --git a/app/routes/rules.new.tsx b/app/routes/rules.new.tsx index 7e50f17..dfa9e5d 100644 --- a/app/routes/rules.new.tsx +++ b/app/routes/rules.new.tsx @@ -52,6 +52,7 @@ import { postgrestGet, postgrestPost, postgrestPut } from "~/api/postgrest-clien import { toastService } from '~/components/ui/Toast'; import type { UserRole } from '~/root'; import { getPromptTemplateOptions } from '~/api/prompts/prompts'; +import { getRulesList } from '~/api/evaluation_points/rules'; export const meta: MetaFunction = () => { return [ @@ -1051,6 +1052,8 @@ export default function RuleNew() { initialData={formData} evaluationPointGroups={evaluationPointGroups} riskOptions={EVALUATION_OPTIONS.riskLevelOptions} + frontendJWT={frontendJWT} + evaluationPointId={formData.id} /> diff --git a/typecheck_result_module2_6.txt b/typecheck_result_module2_6.txt new file mode 100644 index 0000000..2905426 --- /dev/null +++ b/typecheck_result_module2_6.txt @@ -0,0 +1,66 @@ + +> typecheck +> tsc + +app/api/db-client.server.ts(3,10): error TS2305: Module '"./postgrest-client"' has no exported member 'runWithContext'. +app/api/entry-modules/entry-modules.ts(133,7): error TS2322: Type 'string | null | undefined' is not assignable to type 'string | undefined'. + Type 'null' is not assignable to type 'string | undefined'. +app/api/entry-modules/entry-modules.ts(166,7): error TS2345: Argument of type 'string | null | undefined' is not assignable to parameter of type 'string | undefined'. + Type 'null' is not assignable to type 'string | undefined'. +app/api/entry-modules/entry-modules.ts(198,7): error TS2345: Argument of type 'string | null | undefined' is not assignable to parameter of type 'string | undefined'. + Type 'null' is not assignable to type 'string | undefined'. +app/api/entry-modules/entry-modules.ts(227,7): error TS2554: Expected 1-2 arguments, but got 3. +app/api/files/documents.ts(190,3): error TS2739: Type '{ id: number; name: string; documentNumber: string; type: string; typeName: string; size: number; auditStatus: number; fileStatus: "warning" | "waiting" | "processing" | "pass" | "fail"; issues: number; ... 7 more ...; ocrResult: { ...; } | undefined; }' is missing the following properties from type 'DocumentUI': pass_count, warning_count, error_count, manual_count +app/api/files/documents.ts(702,11): error TS2322: Type '{ id: any; name: any; documentNumber: any; type: any; typeName: any; size: any; auditStatus: any; fileStatus: any; issues: any; issuesDiff: number | undefined; issuesDiffType: "increase" | "decrease" | "same" | undefined; ... 7 more ...; versionNumber: number; }[]' is not assignable to type 'DocumentVersionUI[]'. + Type '{ id: any; name: any; documentNumber: any; type: any; typeName: any; size: any; auditStatus: any; fileStatus: any; issues: any; issuesDiff: number | undefined; issuesDiffType: "increase" | "decrease" | "same" | undefined; ... 7 more ...; versionNumber: number; }' is missing the following properties from type 'DocumentVersionUI': pass_count, warning_count, error_count, manual_count +app/api/role-permissions/role-permissions.ts(44,41): error TS2552: Cannot find name 'ApiResponse'. Did you mean 'Response'? +app/api/role-permissions/role-permissions.ts(506,28): error TS2304: Cannot find name 'get'. +app/api/role-permissions/role-permissions.ts(1032,28): error TS2304: Cannot find name 'get'. +app/api/role-permissions/role-permissions.ts(1051,28): error TS2304: Cannot find name 'get'. +app/api/role-permissions/role-permissions.ts(1072,28): error TS2304: Cannot find name 'post'. +app/api/role-permissions/role-permissions.ts(1111,28): error TS2304: Cannot find name 'put'. +app/api/role-permissions/role-permissions.ts(1132,28): error TS2304: Cannot find name 'del'. +app/api/role-permissions/role-permissions.ts(1153,28): error TS2304: Cannot find name 'get'. +app/api/role-permissions/role-permissions.ts(1182,28): error TS2304: Cannot find name 'post'. +app/api/role-permissions/role-permissions.ts(1215,28): error TS2304: Cannot find name 'put'. +app/api/role-permissions/role-permissions.ts(1241,28): error TS2304: Cannot find name 'del'. +app/config/api-config-b.ts(386,47): error TS2367: This comparison appears to be unintentional because the types '"test" | "production"' and '"testing"' have no overlap. +app/config/api-config.ts(398,47): error TS2367: This comparison appears to be unintentional because the types '"test" | "production"' and '"testing"' have no overlap. +app/routes/_index.tsx(43,7): error TS7034: Variable 'entryModules' implicitly has type 'any[]' in some locations where its type cannot be determined. +app/routes/_index.tsx(66,46): error TS7005: Variable 'entryModules' implicitly has an 'any[]' type. +app/routes/_index.tsx(145,48): error TS7006: Parameter 'dt' implicitly has an 'any' type. +app/routes/_index.tsx(293,47): error TS7006: Parameter 'module' implicitly has an 'any' type. +app/routes/api.file-upload.tsx(7,17): error TS2339: Property 'user' does not exist on type '{ sessionId: any; session: Session; }'. +app/routes/config-lists._index.tsx(11,87): error TS2307: Cannot find module '~/api/system_setting/config-lists' or its corresponding type declarations. +app/routes/config-lists.new.tsx(7,79): error TS2307: Cannot find module '~/api/system_setting/config-lists' or its corresponding type declarations. +app/routes/documents.list.tsx(1504,65): error TS2554: Expected 2 arguments, but got 3. +app/routes/entry-modules._index.tsx(355,13): error TS2322: Type '"link"' is not assignable to type 'ButtonType | undefined'. +app/routes/entry-modules._index.tsx(364,13): error TS2322: Type '"link"' is not assignable to type 'ButtonType | undefined'. +app/routes/entry-modules._index.tsx(399,22): error TS2322: Type '{ children: Element[]; onReset: () => void; }' is not assignable to type 'IntrinsicAttributes & FilterPanelProps'. + Property 'onReset' does not exist on type 'IntrinsicAttributes & FilterPanelProps'. +app/routes/entry-modules._index.tsx(402,13): error TS2322: Type '{ placeholder: string; defaultValue: string; onSearch: (value: string) => void; }' is not assignable to type 'IntrinsicAttributes & SearchFilterProps'. + Property 'defaultValue' does not exist on type 'IntrinsicAttributes & SearchFilterProps'. +app/routes/entry-modules._index.tsx(417,11): error TS2322: Type '{ columns: ({ key: string; title: string; width: string; render: (row: EntryModule) => number | undefined; } | { key: string; title: string; width: string; render: (row: EntryModule) => Element; } | { ...; })[]; data: EntryModule[]; loading: false; emptyText: string; }' is not assignable to type 'IntrinsicAttributes & TableProps>'. + Property 'data' does not exist on type 'IntrinsicAttributes & TableProps>'. +app/routes/entry-modules._index.tsx(425,13): error TS2322: Type '{ current: number; pageSize: number; total: number; onPageChange: (page: number) => void; onPageSizeChange: (size: number) => void; }' is not assignable to type 'IntrinsicAttributes & PaginationProps'. + Property 'current' does not exist on type 'IntrinsicAttributes & PaginationProps'. +app/routes/entry-modules.new.tsx(222,57): error TS2345: Argument of type '{ name: string; description: string | undefined; path: string | null; areas: string[]; }' is not assignable to parameter of type 'Partial>'. + Types of property 'path' are incompatible. + Type 'string | null' is not assignable to type 'string | undefined'. + Type 'null' is not assignable to type 'string | undefined'. +app/routes/entry-modules.new.tsx(224,42): error TS2345: Argument of type '{ name: string; description: string | undefined; path: string | null; areas: string[]; }' is not assignable to parameter of type 'Omit'. + Types of property 'path' are incompatible. + Type 'string | null' is not assignable to type 'string | undefined'. + Type 'null' is not assignable to type 'string | undefined'. +app/routes/entry-modules.new.tsx(373,13): error TS2322: Type '{ children: string; type: "primary"; onClick: () => Promise; loading: boolean; disabled: boolean; }' is not assignable to type 'IntrinsicAttributes & ButtonProps & Omit, "type">'. + Property 'loading' does not exist on type 'IntrinsicAttributes & ButtonProps & Omit, "type">'. +app/routes/entry-modules.new.tsx(397,15): error TS2322: Type '{ children: string; type: "primary"; danger: true; onClick: () => void; }' is not assignable to type 'IntrinsicAttributes & ButtonProps & Omit, "type">'. + Property 'danger' does not exist on type 'IntrinsicAttributes & ButtonProps & Omit, "type">'. +app/routes/pdf-demo.tsx(856,13): error TS2322: Type '"canvas" | "svg"' is not assignable to type 'RenderMode | undefined'. + Type '"svg"' is not assignable to type 'RenderMode | undefined'. +app/routes/role-permissions._index.tsx(1035,25): error TS2322: Type 'boolean | undefined' is not assignable to type 'boolean'. + Type 'undefined' is not assignable to type 'boolean'. +app/routes/role-permissions._index.tsx(1399,15): error TS2322: Type '{ children: string; variant: string; onClick: () => void; }' is not assignable to type 'IntrinsicAttributes & ButtonProps & Omit, "type">'. + Property 'variant' does not exist on type 'IntrinsicAttributes & ButtonProps & Omit, "type">'. +app/routes/role-permissions._index.tsx(1408,15): error TS2322: Type '{ children: string; variant: string; onClick: () => void; disabled: boolean; }' is not assignable to type 'IntrinsicAttributes & ButtonProps & Omit, "type">'. + Property 'variant' does not exist on type 'IntrinsicAttributes & ButtonProps & Omit, "type">'.