feat: align frontend document and rule management flows

This commit is contained in:
wren
2026-05-06 09:40:37 +08:00
parent 8a5044b024
commit c54f84382b
41 changed files with 4239 additions and 2903 deletions
+25 -7
View File
@@ -65,6 +65,8 @@ export interface DocumentUI {
documentNumber: string;
type: string;
typeName: string;
groupId?: number | null;
groupName?: string | null;
size: number;
auditStatus: number; // -1: 不通过, 0: 待审核, 1: 通过, 2: 警告, 3: 审核中
fileStatus: string; // Waiting, Cutting, Extractioning, Failed, Evaluationing, Processed
@@ -106,6 +108,8 @@ export interface DocumentVersionUI {
documentNumber: string;
type: string;
typeName: string;
groupId?: number | null;
groupName?: string | null;
size: number;
auditStatus: number;
fileStatus: string;
@@ -152,6 +156,9 @@ interface LeauditListItem {
previousVersionId?: number | null;
typeId?: number | null;
typeCode?: string | null;
typeName?: string | null;
groupId?: number | null;
groupName?: string | null;
region: string;
normalizedName?: string | null;
fileId?: number | null;
@@ -168,6 +175,9 @@ interface LeauditListItem {
passedCount?: number | null;
failedCount?: number | null;
skippedCount?: number | null;
documentNumber?: string | null;
auditStatus?: number | null;
isTestDocument?: boolean | null;
updatedAt?: string | null;
hasHistory?: boolean;
totalVersions?: number;
@@ -279,7 +289,9 @@ function mapHistoryVersionToUI(history: LeauditHistoryVersion, source: LeauditLi
name: history.fileName || source.fileName || source.normalizedName || '未命名文档',
documentNumber: buildDocumentNumber(history),
type: source.typeId?.toString() || '',
typeName: typeNameFromCode(source.typeCode),
typeName: source.typeName || typeNameFromCode(source.typeCode),
groupId: source.groupId ?? null,
groupName: source.groupName ?? null,
size: 0,
auditStatus: mapLeauditDocToAuditStatus({
processingStatus: history.processingStatus,
@@ -316,7 +328,9 @@ function mapLeauditDocumentToUI(doc: LeauditListItem | LeauditDocumentDetail): D
name: doc.fileName || doc.normalizedName || '未命名文档',
documentNumber: ('documentNumber' in doc && doc.documentNumber) ? doc.documentNumber : buildDocumentNumber(doc),
type: doc.typeId?.toString() || '',
typeName: typeNameFromCode(doc.typeCode),
typeName: doc.typeName || typeNameFromCode(doc.typeCode),
groupId: doc.groupId ?? null,
groupName: doc.groupName ?? null,
size: doc.fileSize || 0,
auditStatus: ('auditStatus' in doc && doc.auditStatus !== null && doc.auditStatus !== undefined)
? doc.auditStatus
@@ -778,6 +792,7 @@ export async function getDocumentsListFromAPI(searchParams: {
name?: string;
documentNumber?: string;
documentTypeIds?: number[]; // 文档类型ID数组
entryModuleId?: number;
auditStatus?: string;
fileStatus?: string;
dateFrom?: string;
@@ -795,6 +810,7 @@ export async function getDocumentsListFromAPI(searchParams: {
name,
documentNumber,
documentTypeIds,
entryModuleId,
auditStatus,
fileStatus,
dateFrom,
@@ -824,11 +840,13 @@ export async function getDocumentsListFromAPI(searchParams: {
params.type_ids = documentTypeIds.join(',');
}
// 下面几个旧筛选项暂未完全对齐:
// - documentNumber
// - auditStatus
void documentNumber;
void auditStatus;
if (entryModuleId && entryModuleId > 0) {
params.entry_module_id = entryModuleId;
}
if (documentNumber) params.documentNumber = documentNumber;
if (auditStatus !== undefined && auditStatus !== "") {
params.auditStatus = Number(auditStatus);
}
if (dateFrom) params.dateFrom = dateFrom;
if (dateTo) params.dateTo = dateTo;
+160 -14
View File
@@ -151,6 +151,21 @@ export interface DocumentType {
ruleSetIds?: number[];
}
export interface DocumentSubtypeGroup {
id: number;
name: string;
code: string;
documentTypeId: number;
documentTypeName?: string | null;
rootGroupId?: number | null;
rootGroupName?: string | null;
entryModuleName?: string | null;
entryModuleId?: number | null;
isDefault?: boolean;
displayName?: string;
displayHint?: string;
}
export interface UploadErrorDetails {
title: string;
summary: string;
@@ -211,6 +226,7 @@ export interface UploadResult {
fileName: string;
fileSize: number;
typeId: number;
groupId?: number | null;
region: string;
processingStatus: string;
duplicateUpload: boolean;
@@ -236,6 +252,7 @@ interface NewUploadResponse {
fileId: number;
typeId: number;
typeCode: string;
groupId?: number | null;
region: string;
fileName: string;
ossUrl: string;
@@ -373,7 +390,7 @@ export function buildUploadErrorDetails(
detailLines,
actionLines: [
'到“系统设置 / 文档类型管理”检查该文档类型是否绑定了正确的规则集。',
'到“规则管理 / 规则集管理”确认对应规则集的可用规则数是否正常。',
'到“规则管理”确认对应规则集的可用规则数是否正常。',
'如果首页入口也异常,请同时到“系统设置 / 入口模块管理”检查入口模块绑定。',
],
rawMessage: message,
@@ -395,7 +412,7 @@ export function buildUploadErrorDetails(
summary: '当前上传入口关联的规则集不可用,文件无法开始审核。',
detailLines,
actionLines: [
'到“规则管理 / 规则集管理”检查对应规则集是否存在、可用规则数是否正常。',
'到“规则管理”检查对应规则集是否存在、可用规则数是否正常。',
'如文档类型绑定了错误的规则集,请到“系统设置 / 文档类型管理”修正绑定关系。',
],
rawMessage: message,
@@ -595,15 +612,12 @@ export async function appendContractAttachments(
formData.append('files', file);
});
// 添加其他参数
formData.append('merge_mode', mergeMode);
formData.append('is_reprocess', isReprocess.toString());
if (remark) {
formData.append('remark', remark);
}
// 新链路仅保留附件追加;mergeMode / remark 在后端暂不消费,但继续保留函数签名兼容旧页面调用。
void mergeMode;
void remark;
// 构建请求URL
const uploadUrl = `${UPLOAD_URL}/contracts/${documentId}/append_attachments`;
const uploadUrl = `${API_BASE_URL}/api/documents/${documentId}/attachments`;
console.log('【合同附件追加】准备发送请求到服务器:', uploadUrl);
// 设置请求头
@@ -627,11 +641,28 @@ export async function appendContractAttachments(
const result = response.data;
console.log('【合同附件追加】服务器返回结果:', result);
if (result.success) {
return { data: result.result };
} else {
return { error: result.error || '附件追加失败' };
if (result?.data) {
if (isReprocess) {
await axios.post(
`${API_BASE_URL}/api/audit/run`,
{
documentId,
force: true,
speed: 'normal',
},
{ headers }
);
}
return {
data: {
success: true,
result: result.data,
error: null,
}
};
}
return { error: result?.message || result?.msg || '附件追加失败' };
} catch (error) {
console.error('【合同附件追加】上传过程中发生错误:', error);
@@ -646,8 +677,10 @@ export async function uploadDocumentToServer(
fileName: string,
fileType: string,
typeId: number,
groupId?: number | null,
region: string = "default",
createdBy?: number,
attachments?: File[],
autoRun: boolean = true,
speed: string = "normal",
jwtToken?: string,
@@ -657,6 +690,12 @@ export async function uploadDocumentToServer(
const blob = new Blob([binaryData], { type: fileType });
formData.append("file", blob, fileName);
formData.append("typeId", String(typeId));
if (groupId) {
formData.append("groupId", String(groupId));
}
(attachments || []).forEach((attachment) => {
formData.append("attachments", attachment);
});
formData.append("region", region);
formData.append("fileRole", "primary");
if (createdBy !== undefined) {
@@ -689,6 +728,7 @@ export async function uploadDocumentToServer(
fileName: uploadData.fileName,
fileSize: binaryData.byteLength,
typeId: uploadData.typeId,
groupId: uploadData.groupId,
region: uploadData.region,
processingStatus: uploadData.processingStatus,
duplicateUpload: uploadData.duplicateUpload,
@@ -819,6 +859,112 @@ export async function getDocumentTypes(token?: string): Promise<{data: DocumentT
}
}
function mapSubtypeChild(child: any, root?: any): DocumentSubtypeGroup {
const rawName = typeof child.name === "string" ? child.name.trim() : "";
const rawCode = typeof child.code === "string" ? child.code.trim() : "";
const isDefault = rawName === "通用" || rawCode.endsWith(".default");
const entryModuleId = child.entry_module_id ?? root?.entry_module_id ?? null;
const entryModuleName = child.entry_module_name ?? root?.entry_module_name ?? null;
const rootGroupName = root?.name ?? null;
return {
id: child.id,
name: child.name,
code: child.code,
documentTypeId: child.document_type_id,
documentTypeName: child.document_type_name,
rootGroupId: root?.id ?? null,
rootGroupName,
entryModuleId: typeof entryModuleId === "number" ? entryModuleId : null,
entryModuleName,
isDefault,
displayName: isDefault ? `默认子类型(${rawName || "通用"}` : rawName || child.name,
displayHint: [rootGroupName, child.document_type_name, entryModuleName, rawCode].filter(Boolean).join(" · "),
};
}
function dedupeSubtypeGroups(groups: DocumentSubtypeGroup[]): DocumentSubtypeGroup[] {
const groupMap = new Map<number, DocumentSubtypeGroup>();
groups.forEach((group) => {
const existing = groupMap.get(group.id);
if (!existing) {
groupMap.set(group.id, group);
return;
}
const currentScore = (group.rootGroupName ? 2 : 0) + (group.entryModuleId ? 1 : 0);
const existingScore = (existing.rootGroupName ? 2 : 0) + (existing.entryModuleId ? 1 : 0);
if (currentScore > existingScore) {
groupMap.set(group.id, group);
}
});
return Array.from(groupMap.values());
}
export async function getDocumentSubtypeGroups(
documentTypeId: number,
token?: string,
entryModuleId?: number | null,
): Promise<{ data: DocumentSubtypeGroup[]; error?: never } | { data?: never; error: string; status?: number }> {
try {
const headers: Record<string, string> = {};
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
const allResponse = await axios.get(`${API_BASE_URL}/api/v3/evaluation-point-groups/all`, {
params: {
include_disabled: false,
with_rule_count: false,
},
headers,
});
const allRoots = extractApiData<any[]>(allResponse.data) || [];
const matchedFromTree = allRoots.flatMap((root: any) => {
if (!Array.isArray(root?.children)) return [];
if (entryModuleId && Number(root?.entry_module_id || 0) !== Number(entryModuleId)) {
return [];
}
return root.children
.filter((child: any) => Number(child?.document_type_id || 0) === Number(documentTypeId))
.map((child: any) => mapSubtypeChild(child, root));
});
if (matchedFromTree.length > 0) {
return { data: dedupeSubtypeGroups(matchedFromTree) };
}
const response = await axios.get(`${API_BASE_URL}/api/v3/evaluation-point-groups/by-document-types`, {
params: {
document_type_ids: String(documentTypeId),
include_disabled: false,
with_rule_count: false,
},
headers,
});
const roots = extractApiData<any[]>(response.data) || [];
const filteredRoots = entryModuleId
? roots.filter((root: any) => Number(root?.entry_module_id || 0) === Number(entryModuleId))
: roots;
const fallbackRoots = filteredRoots.length > 0 ? filteredRoots : roots;
const groups = dedupeSubtypeGroups(
fallbackRoots.flatMap((root: any) =>
Array.isArray(root?.children)
? root.children
.filter((child: any) => Number(child?.document_type_id || 0) === Number(documentTypeId))
.map((child: any) => mapSubtypeChild(child, root))
: [],
),
);
return { data: groups };
} catch (error) {
console.error("获取子类型分组失败:", error);
return {
error: error instanceof Error ? error.message : "获取子类型分组失败",
status: 500,
};
}
}
/**
* 获取指定文档的状态
* @param documentIds 文档ID列表