feat: 1. 完善评查点分组的删除逻辑,会涉及文档类型绑定的一级分组,分组绑定的评查点规则。新增一个评查点分组换绑的。

2. 修复交叉评查的任务中的文档列表的历史文档的查看跳转路径。
3. 修复评查点新增中评查点类型只能显示当前文档类型绑定的这几个一级分组。评查点类型=一级分组。
4. 修复文档列表关于pdf的下载失败的问题。
This commit is contained in:
2025-12-19 00:21:49 +08:00
parent 38f17fb3ed
commit 616f059f1e
14 changed files with 626 additions and 117 deletions
+1
View File
@@ -28,6 +28,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
// 构建完整的文件 URL
const fileUrl = `${DOCUMENT_URL}${filePath}`;
// console.log('fileUrl:', fileUrl)
// 使用 JWT 认证获取文件
const response = await fetch(fileUrl, {
+1 -1
View File
@@ -173,7 +173,7 @@ export default function DocumentTypesList() {
// 处理测试loader返回的信息
useEffect(() => {
console.log('返回的父级评查点分组数据',parentGroups)
// console.log('返回的父级评查点分组数据',parentGroups)
}, [parentGroups])
// 处理loader加载数据的时候的错误
+16 -34
View File
@@ -575,42 +575,24 @@ export default function DocumentsIndex() {
};
// 下载文档
const handleDownload = async (path: string) => {
try {
// 使用 PDF 代理路由获取文件,自动添加 JWT 认证
const downloadUrl = `/api/pdf-proxy?path=${encodeURIComponent(path)}`;
const handleDownload = (path: string) => {
// 使用 PDF 代理路由获取文件,自动添加 JWT 认证
const downloadUrl = `/api/pdf-proxy?path=${encodeURIComponent(path)}`;
// 使用fetch获取文件内容
const response = await fetch(downloadUrl);
if (!response.ok) {
throw new Error(`下载失败: ${response.status} ${response.statusText}`);
}
// 直接使用链接下载,避免 fetch + blob 在生产环境下对 PDF 的兼容问题
const a = document.createElement('a');
a.style.display = 'none';
a.href = downloadUrl;
// 从路径中获取文件名
const fileName = path.split('/').pop() || 'document';
a.download = decodeURIComponent(fileName);
document.body.appendChild(a);
a.click();
// 将响应转换为Blob
const blob = await response.blob();
// 创建Blob URL
const blobUrl = URL.createObjectURL(blob);
// 创建一个隐藏的a标签并点击它
const a = document.createElement('a');
a.style.display = 'none';
a.href = blobUrl;
// 从路径中获取文件名
const fileName = path.split('/').pop() || 'document';
a.download = decodeURIComponent(fileName);
document.body.appendChild(a);
a.click();
// 清理
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(blobUrl);
}, 100);
} catch (error) {
console.error('下载文件失败:', error);
toastService.error(`下载文件失败: ${error instanceof Error ? error.message : '未知错误'}`);
}
// 清理
setTimeout(() => {
document.body.removeChild(a);
}, 100);
};
// 删除文档
+121 -6
View File
@@ -12,10 +12,13 @@ import {
getEvaluationPointGroups,
getChildGroups,
type RuleGroup,
type DocTypeInfo,
deleteEvaluationPointGroup,
rebindEvaluationPointGroup,
batchUpdateEvaluationPointGroupStatus,
batchDeleteEvaluationPointGroups
} from "~/api/evaluation_points/rule-groups";
import { RebindModal } from "~/components/rule-groups/RebindModal";
import { toastService, messageService } from "~/components/ui";
import { usePermission } from "~/hooks/usePermission";
@@ -90,6 +93,17 @@ export default function RuleGroupsIndex() {
const [initialLoading, setInitialLoading] = useState<boolean>(true);
const [selectedIds, setSelectedIds] = useState<string[]>([]); // 🆕 批量选择状态
// 换绑弹窗状态
const [rebindModalVisible, setRebindModalVisible] = useState(false);
const [rebindModalData, setRebindModalData] = useState<{
groupId: string;
groupName: string;
pointsCount: number;
singleBoundDocTypes: DocTypeInfo[];
multiBoundDocTypes: DocTypeInfo[];
} | null>(null);
const [rebindLoading, setRebindLoading] = useState(false);
// ✅ 使用权限 Hook
const { canCreate, canUpdate, canDelete, canBatch } = usePermission();
const canCreateGroup = canCreate('evaluation_group');
@@ -252,13 +266,29 @@ export default function RuleGroupsIndex() {
return;
}
// 检查是否是一级分组
const parentGroup = groups.find(g => g.id === groupId);
const isFirstLevel = !!parentGroup;
// 如果是一级分组,检查是否还有二级分组
if (isFirstLevel && parentGroup.children && parentGroup.children.length > 0) {
messageService.show({
title: "无法删除",
message: "尚未完全删除该一级分组下的所有二级分组,不可进行删除操作,请检查。",
type: "error",
confirmText: "知道了"
});
return;
}
// 一级分组无子分组 或 二级分组,显示确认删除提示
messageService.show({
title: "确认删除",
message: "确定要删除该分组吗?此操作将同时除该分组下的所有评查点,且不可恢复。",
message: "确定要删除该分组吗?此操作将同时除该分组下的已绑定的所有评查点。",
type: "warning",
confirmText: "删除",
cancelText: "取消",
confirmDelay: 4,
confirmDelay: 2,
onConfirm: async () => {
try {
const result = await deleteEvaluationPointGroup(groupId, frontendJWT);
@@ -285,9 +315,19 @@ export default function RuleGroupsIndex() {
// 显示成功消息
toastService.success('删除成功')
} else if (result.need_rebind) {
// 需要换绑,打开换绑弹窗
setRebindModalData({
groupId: groupId,
groupName: parentGroup?.name || '未知分组',
pointsCount: result.points_count || 0,
singleBoundDocTypes: result.single_bound_doc_types || [],
multiBoundDocTypes: result.multi_bound_doc_types || []
});
setRebindModalVisible(true);
} else {
toastService.error(`删除失败: ${result.error}`);
console.error(`删除失败: ${result.error}`);
// 其他失败情况(如有二级分组)
toastService.error(result.message || result.error || '删除失败');
}
} catch (error) {
console.error('删除分组失败:', error);
@@ -297,6 +337,49 @@ export default function RuleGroupsIndex() {
});
};
// 处理换绑确认
const handleRebindConfirm = async (newGroupId: string) => {
if (!rebindModalData) return;
setRebindLoading(true);
try {
// 1. 执行换绑
const rebindResult = await rebindEvaluationPointGroup(
rebindModalData.groupId,
newGroupId,
frontendJWT
);
if (!rebindResult.success) {
toastService.error(rebindResult.error || '换绑失败');
return;
}
toastService.success(rebindResult.message || '换绑成功');
// 2. 换绑成功后,再次调用删除
const deleteResult = await deleteEvaluationPointGroup(rebindModalData.groupId, frontendJWT);
if (deleteResult.success) {
toastService.success(deleteResult.message || '删除成功');
// 关闭弹窗
setRebindModalVisible(false);
setRebindModalData(null);
// 刷新页面
window.location.reload();
} else {
toastService.error(deleteResult.message || deleteResult.error || '删除失败');
}
} catch (error) {
console.error('换绑并删除失败:', error);
toastService.error('操作失败,请稍后重试');
} finally {
setRebindLoading(false);
}
};
// 🆕 批量启用/禁用
const handleBatchEnable = async (enable: boolean) => {
// ✅ 检查更新权限
@@ -317,7 +400,8 @@ export default function RuleGroupsIndex() {
// 刷新页面以重新加载数据
window.location.reload();
} else {
toastService.error(`批量操作失败:${result.failed_ids.length} 个分组操作失败`);
// toastService.error(`批量操作失败:${result.failed_ids.length} 个分组操作失败`);
toastService.error(`批量操作失败`);
}
} catch (error) {
console.error('批量操作失败:', error);
@@ -338,9 +422,23 @@ export default function RuleGroupsIndex() {
return;
}
// 检查选中的分组中是否存在一级分组 - 完全禁止批量删除一级分组
const selectedFirstLevelGroups = groups.filter(g => selectedIds.includes(g.id));
if (selectedFirstLevelGroups.length > 0) {
messageService.show({
title: "无法批量删除",
message: "批量删除不支持删除一级分组,请单独删除一级分组或仅选择二级分组进行批量删除。",
type: "error",
confirmText: "知道了"
});
return;
}
// 只允许批量删除二级分组
messageService.show({
title: "确认批量删除",
message: `确定要删除选中的 ${selectedIds.length} 个分组吗?此操作不可恢复`,
message: `确定要删除选中的 ${selectedIds.length}二级分组吗?此操作将同时解除这些分组下的已绑定的所有评查点`,
type: "warning",
confirmText: "删除",
cancelText: "取消",
@@ -872,6 +970,23 @@ export default function RuleGroupsIndex() {
</>
)}
</Card>
{/* 换绑弹窗 */}
<RebindModal
visible={rebindModalVisible}
groupId={rebindModalData?.groupId || ''}
groupName={rebindModalData?.groupName || ''}
pointsCount={rebindModalData?.pointsCount || 0}
singleBoundDocTypes={rebindModalData?.singleBoundDocTypes || []}
multiBoundDocTypes={rebindModalData?.multiBoundDocTypes || []}
allFirstLevelGroups={groups}
loading={rebindLoading}
onClose={() => {
setRebindModalVisible(false);
setRebindModalData(null);
}}
onConfirm={handleRebindConfirm}
/>
</div>
);
}
+2 -2
View File
@@ -293,7 +293,7 @@ export default function RulesIndex() {
if (typeResponse.data) {
loadedRuleTypes = typeResponse.data;
setRuleTypes(loadedRuleTypes);
console.log("📋 [fetchData] 获取到评查点类型:", loadedRuleTypes);
// console.log("📋 [fetchData] 获取到评查点类型:", loadedRuleTypes);
}
} catch (error) {
console.error('加载评查点类型失败:', error);
@@ -308,7 +308,7 @@ export default function RulesIndex() {
} else if (loadedRuleTypes && loadedRuleTypes.length > 0) {
// 选择"全部"或未选择,使用刚加载的评查点类型的 id
finalRuleType = loadedRuleTypes.map(type => type.id).join(',');
console.log("📋 [fetchData] 选择全部类型,使用 loadedRuleTypes 的 id 组合:", finalRuleType);
// console.log("📋 [fetchData] 选择全部类型,使用 loadedRuleTypes 的 id 组合:", finalRuleType);
}
const queryParams = {