feat(evaluation): 完成模块2.6 - 评查点前端组件增强

- rules.list.tsx 新增批量操作功能:
  * 添加批量选择复选框
  * 实现批量启用/禁用评查点
  * 实现批量删除评查点
  * 添加操作结果提示和部分失败处理

- BasicInfo.tsx 新增异步编码验证:
  * 实现500ms防抖的实时编码唯一性验证
  * 集成 getRulesList API 进行编码查重
  * 编辑模式下排除当前评查点
  * 添加验证中状态和错误提示UI

- 通过TypeScript类型检查,无新增类型错误
- 批量操作支持部分成功场景,详细报告结果
- 改善用户体验,提供实时反馈
This commit is contained in:
2025-11-25 13:23:36 +08:00
parent eb5a0c8b47
commit 37134ff650
4 changed files with 307 additions and 15 deletions
+155 -10
View File
@@ -15,13 +15,15 @@ import { FilterPanel, FilterSelect, SearchFilter } from '~/components/ui/FilterP
import { Pagination } from '~/components/ui/Pagination';
import { messageService } from '~/components/ui/MessageModal';
import { toastService } from '~/components/ui/Toast';
import {
getRulesList,
deleteRule,
getRuleTypes,
import {
getRulesList,
deleteRule,
getRuleTypes,
getRuleGroupsByType,
batchUpdateRuleStatus,
batchDeleteRules,
type RuleType as ApiRuleType,
type RuleGroup
type RuleGroup
} from '~/api/evaluation_points/rules';
import type { UserRole } from '~/root';
@@ -209,6 +211,9 @@ export default function RulesIndex() {
// 添加一个状态来跟踪是否执行了删除操作
const [isDeleting, setIsDeleting] = useState(false);
// 批量选择状态
const [selectedIds, setSelectedIds] = useState<string[]>([]);
// 使用 ref 跟踪是否正在加载数据,避免重复加载
const isLoadingRef = useRef(false);
@@ -541,6 +546,92 @@ export default function RulesIndex() {
navigate(`/rules/new?id=${rule.id}&mode=copy`);
};
// 批量选择处理
const handleSelectAll = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.checked) {
setSelectedIds(filteredRules.map(rule => rule.id));
} else {
setSelectedIds([]);
}
};
const handleSelectRow = (id: string) => {
setSelectedIds(prev =>
prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id]
);
};
// 批量启用/禁用
const handleBatchEnable = async (isEnabled: boolean) => {
if (selectedIds.length === 0) {
toastService.warning('请先选择要操作的评查点');
return;
}
try {
setLoading(true);
const result = await batchUpdateRuleStatus(selectedIds, isEnabled, loaderData.frontendJWT);
if (result.success) {
toastService.success(`成功${isEnabled ? '启用' : '禁用'} ${result.updated_count} 个评查点`);
if (result.failed_ids.length > 0) {
toastService.warning(`${result.failed_ids.length} 个评查点操作失败`);
}
// 清空选择
setSelectedIds([]);
// 重新加载数据
fetchData();
} else {
toastService.error('批量操作失败');
}
} catch (error) {
console.error('批量操作失败:', error);
toastService.error('批量操作失败');
} finally {
setLoading(false);
}
};
// 批量删除
const handleBatchDelete = async () => {
if (selectedIds.length === 0) {
toastService.warning('请先选择要删除的评查点');
return;
}
messageService.show({
title: "确认批量删除",
message: `确定要删除选中的 ${selectedIds.length} 个评查点吗?此操作不可恢复。`,
type: "warning",
confirmText: "删除",
cancelText: "取消",
onConfirm: async () => {
try {
setLoading(true);
const result = await batchDeleteRules(selectedIds, loaderData.frontendJWT);
if (result.success) {
toastService.success(`成功删除 ${result.deleted_count} 个评查点`);
if (result.failed_ids.length > 0) {
toastService.warning(`${result.failed_ids.length} 个评查点删除失败`);
}
// 清空选择
setSelectedIds([]);
// 重新加载数据
fetchData();
} else {
toastService.error('批量删除失败');
}
} catch (error) {
console.error('批量删除失败:', error);
toastService.error('批量删除失败');
} finally {
setLoading(false);
}
}
});
};
const handlePageChange = (page: number) => {
const newParams = new URLSearchParams(searchParams);
newParams.set('page', page.toString());
@@ -573,6 +664,28 @@ export default function RulesIndex() {
// 定义表格列配置
const columns = [
// 添加复选框列(仅开发者可见)
...(isDeveloper ? [{
title: (
<input
type="checkbox"
checked={selectedIds.length > 0 && selectedIds.length === filteredRules.length && filteredRules.length > 0}
onChange={handleSelectAll}
disabled={filteredRules.length === 0}
/>
),
key: "selection",
align: "center" as const,
width: "50px",
render: (_: unknown, record: Rule) => (
<input
type="checkbox"
checked={selectedIds.includes(record.id)}
onChange={() => handleSelectRow(record.id)}
onClick={(e) => e.stopPropagation()}
/>
)
}] : []),
{
title: "评查点编码",
dataIndex: "code" as keyof Rule,
@@ -691,11 +804,43 @@ export default function RulesIndex() {
</div>
)}
</div>
{isDeveloper && (
<Button type="primary" icon="ri-add-line" to="/rules/new" className="btn-add-rule">
</Button>
)}
<div className="flex items-center gap-2">
{/* 批量操作按钮(仅在有选择时显示) */}
{isDeveloper && selectedIds.length > 0 && (
<>
<Button
type="default"
icon="ri-check-line"
onClick={() => handleBatchEnable(true)}
className="btn-batch-enable"
>
({selectedIds.length})
</Button>
<Button
type="default"
icon="ri-close-line"
onClick={() => handleBatchEnable(false)}
className="btn-batch-disable"
>
({selectedIds.length})
</Button>
<Button
type="danger"
icon="ri-delete-bin-line"
onClick={handleBatchDelete}
className="btn-batch-delete"
>
({selectedIds.length})
</Button>
</>
)}
{/* 新增按钮 */}
{isDeveloper && (
<Button type="primary" icon="ri-add-line" to="/rules/new" className="btn-add-rule">
</Button>
)}
</div>
</div>
{/* 筛选区域 */}
+3
View File
@@ -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}
/>
</div>