From 9d8920047c52490a28d9d0f74bd0ad885ca9165f Mon Sep 17 00:00:00 2001 From: zhouhaibin Date: Mon, 14 Jul 2025 13:37:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=8E=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/configs/contractTaskConfigs.json | 195 ++++ public/configs/documentTaskConfigs.json | 250 +++++ public/configs/tenderTaskConfigs.json | 186 ++++ .../ContractualClauseConfig/index.ts | 57 ++ .../ContractualClauseConfig/model.ts | 48 + .../ContractualClauseType/index.ts | 57 ++ .../ContractualClauseType/model.ts | 43 + .../contractReview/ContractualTasks/index.ts | 35 +- src/components/UniversalResultDrawer.vue | 265 ++++- src/configs/jsonConfigLoader.ts | 157 +++ src/configs/taskConfigs.ts | 119 ++- .../ContractClauseConfig/index.vue | 924 ++++++++++++++++++ .../components/ConsistencyContent.vue | 740 +++++++++++--- .../components/InferenceReview.vue | 9 +- .../TenderTask/TenderTask.data.ts | 47 +- src/views/tenderReview/TenderTask/index.vue | 18 +- 16 files changed, 2923 insertions(+), 227 deletions(-) create mode 100644 public/configs/contractTaskConfigs.json create mode 100644 public/configs/documentTaskConfigs.json create mode 100644 public/configs/tenderTaskConfigs.json create mode 100644 src/api/contractReview/ContractualClauseConfig/index.ts create mode 100644 src/api/contractReview/ContractualClauseConfig/model.ts create mode 100644 src/api/contractReview/ContractualClauseType/index.ts create mode 100644 src/api/contractReview/ContractualClauseType/model.ts create mode 100644 src/configs/jsonConfigLoader.ts create mode 100644 src/views/contractReview/ContractClauseConfig/index.vue diff --git a/public/configs/contractTaskConfigs.json b/public/configs/contractTaskConfigs.json new file mode 100644 index 0000000..2b649f9 --- /dev/null +++ b/public/configs/contractTaskConfigs.json @@ -0,0 +1,195 @@ +{ + "contractSubstantiveReview": { + "taskType": "contractSubstantiveReview", + "name": "实质性审查", + "mode": "tabs", + "pdfConfig": { + "layout": "single", + "sources": [ + { "id": "contract", "title": "合同文件", "apiField": "id" } + ] + }, + "tabs": [ + { + "key": "substantive", + "label": "实质性审查", + "pdfConfig": { + "layout": "single", + "sources": [ + { "id": "contract", "title": "合同文件", "apiField": "id" } + ] + }, + "fields": [ + { + "field": "originalText", + "title": "原文", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "contract", + "required": true + }, + { + "field": "modifiedContent", + "title": "修改建议", + "dataType": "string", + "displayType": "markdown", + "showComparison": true, + "comparisonConfig": { + "comparisonField": "modificationDisplay", + "comparisonTitle": "修改情况展示", + "showButton": true + } + }, + { + "field": "reviewBasis", + "title": "审查依据", + "dataType": "json", + "displayType": "markdown", + "jsonConfig": { + "extractFields": ["review_points", "review_content"], + "separator": ":" + } + } + ] + } + ] + }, + "contractComplianceReview": { + "taskType": "contractComplianceReview", + "name": "合规性审查", + "mode": "tabs", + "pdfConfig": { + "layout": "single", + "sources": [ + { "id": "contract", "title": "合同文件", "apiField": "id" } + ] + }, + "tabs": [ + { + "key": "compliance", + "label": "合规性审查", + "pdfConfig": { + "layout": "single", + "sources": [ + { "id": "contract", "title": "合同文件", "apiField": "id" } + ] + }, + "fields": [ + { + "field": "originalText", + "title": "原文", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "contract", + "required": true + }, + { + "field": "reviewBasis", + "title": "法规依据", + "dataType": "json", + "displayType": "markdown", + "jsonConfig": { + "extractFields": ["review_points"], + "separator": ":", + "fieldProcessors": { + "review_points": "arrayJoin" + } + } + } + ] + } + ] + }, + "contractConsistencyReview": { + "taskType": "contractConsistencyReview", + "name": "一致性审查", + "mode": "tabs", + "pdfConfig": { + "layout": "split", + "sources": [ + { "id": "contract", "title": "合同文件", "apiField": "id" }, + { "id": "bid", "title": "招标文件", "apiField": "id" } + ] + }, + "tabs": [ + { + "key": "consistency", + "label": "一致性审查", + "pdfConfig": { + "layout": "split", + "sources": [ + { "id": "contract", "title": "合同文件", "apiField": "id" }, + { "id": "bid", "title": "招标文件", "apiField": "id" } + ] + }, + "fields": [ + { + "field": "originalText", + "title": "合同原文", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "contract", + "required": true + }, + { + "field": "comparedText", + "title": "招标文件对应内容", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "bid", + "required": true + } + ] + } + ] + }, + "contractReview": { + "taskType": "contractReview", + "name": "合同审查", + "mode": "single", + "pdfConfig": { + "layout": "single", + "sources": [ + { "id": "contract", "title": "合同文件", "apiField": "id" } + ] + }, + "fields": [ + { + "field": "originalText", + "title": "原文", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "contract", + "required": true + }, + { + "field": "existingIssues", + "title": "存在问题", + "dataType": "string", + "displayType": "markdown" + }, + { + "field": "modifiedContent", + "title": "修改建议", + "dataType": "string", + "displayType": "markdown", + "showComparison": true, + "comparisonConfig": { + "comparisonField": "modificationDisplay", + "comparisonTitle": "修改情况展示", + "showButton": true + } + }, + { + "field": "reviewBasis", + "title": "审查依据", + "dataType": "json", + "displayType": "markdown", + "jsonConfig": { + "extractFields": ["review_points", "review_content"], + "separator": ":" + } + } + ] + } + } \ No newline at end of file diff --git a/public/configs/documentTaskConfigs.json b/public/configs/documentTaskConfigs.json new file mode 100644 index 0000000..9e6c837 --- /dev/null +++ b/public/configs/documentTaskConfigs.json @@ -0,0 +1,250 @@ +{ + "checkDocumentError": { + "taskType": "checkDocumentError", + "name": "文字错误检查", + "mode": "single", + "pdfConfig": { + "layout": "single", + "sources": [ + { "id": "document", "title": "文档", "apiField": "id" } + ] + }, + "fields": [ + { + "field": "originalText", + "title": "原文", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "document", + "required": true + }, + { + "field": "modifiedContent", + "title": "修改建议", + "dataType": "string", + "displayType": "markdown", + "showComparison": true, + "comparisonConfig": { + "comparisonField": "modificationDisplay", + "comparisonTitle": "修改情况展示", + "showButton": true + } + } + ] + }, + "checkRepeatText": { + "taskType": "checkRepeatText", + "name": "文档相似性检查", + "mode": "single", + "pdfConfig": { + "layout": "single", + "sources": [ + { "id": "document", "title": "文档", "apiField": "id" } + ] + }, + "fields": [ + { + "field": "originalText", + "title": "第一段原文", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "document" + }, + { + "field": "comparedText", + "title": "第二段原文", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "document" + }, + { + "field": "modificationDisplay", + "title": "相似情况", + "dataType": "string", + "displayType": "markdown" + } + ] + }, + "allCheckRepeatText": { + "taskType": "allCheckRepeatText", + "name": "全文档相似性检查", + "mode": "single", + "pdfConfig": { + "layout": "single", + "sources": [ + { "id": "document", "title": "文档", "apiField": "id" } + ] + }, + "fields": [ + { + "field": "originalText", + "title": "第一段原文", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "document" + }, + { + "field": "comparedText", + "title": "第二段原文", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "document" + }, + { + "field": "modificationDisplay", + "title": "相似情况", + "dataType": "string", + "displayType": "markdown" + } + ] + }, + "checkCompanyName": { + "taskType": "checkCompanyName", + "name": "公司名称检查", + "mode": "single", + "pdfConfig": { + "layout": "single", + "sources": [ + { "id": "document", "title": "文档", "apiField": "id" } + ] + }, + "fields": [ + { + "field": "modificationDisplay", + "title": "相关原文", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "document" + } + ] + }, + "checkTitleName": { + "taskType": "checkTitleName", + "name": "文档结构检查", + "mode": "single", + "pdfConfig": { + "layout": "single", + "sources": [ + { "id": "document", "title": "文档", "apiField": "id" } + ] + }, + "fields": [ + { + "field": "modificationDisplay", + "title": "结构检查情况", + "dataType": "string", + "displayType": "markdown" + } + ] + }, + "checkPlaceName": { + "taskType": "checkPlaceName", + "name": "地名检查", + "mode": "single", + "pdfConfig": { + "layout": "single", + "sources": [ + { "id": "document", "title": "文档", "apiField": "id" } + ] + }, + "fields": [ + { + "field": "modificationDisplay", + "title": "相关原文", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "document" + } + ] + }, + "policyBases": { + "taskType": "policyBases", + "name": "建设依据检查", + "mode": "single", + "pdfConfig": { + "layout": "single", + "sources": [ + { "id": "document", "title": "文档", "apiField": "id" } + ] + }, + "fields": [ + { + "field": "originalText", + "title": "系统名称", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "document" + }, + { + "field": "reviewBasis", + "title": "系统描述", + "dataType": "json", + "displayType": "markdown", + "jsonConfig": { + "extractFields": ["review_points", "review_content"], + "separator": ":", + "fieldProcessors": { + "review_points": "extractFirst" + } + } + } + ] + }, + "tenderReview": { + "taskType": "tenderReview", + "name": "投标文件审核", + "mode": "single", + "pdfConfig": { + "layout": "single", + "sources": [ + { "id": "document", "title": "文档", "apiField": "id" } + ] + }, + "fields": [ + { + "field": "issueName", + "title": "合规性分类-具体问题(示例:资质审查-业绩要求)", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "document" + }, + { + "field": "originalText", + "title": "原文", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "document" + }, + { + "field": "reviewBasis", + "title": "相关法律法规政策", + "dataType": "json", + "displayType": "markdown", + "jsonConfig": { + "extractFields": ["review_points"], + "separator": ":", + "fieldProcessors": { + "review_points": "arrayJoinNewLine" + } + } + }, + { + "field": "modifiedContent", + "title": "修改建议", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "document" + }, + { + "field": "reviewBasis", + "title": "风险等级(红色预警:直接废标项,橙色风险:可能引发投诉项)", + "dataType": "json", + "displayType": "markdown", + "jsonConfig": { + "extractFields": ["risk_level"], + "separator": ":" + } + } + ] + } + } \ No newline at end of file diff --git a/public/configs/tenderTaskConfigs.json b/public/configs/tenderTaskConfigs.json new file mode 100644 index 0000000..df46ec2 --- /dev/null +++ b/public/configs/tenderTaskConfigs.json @@ -0,0 +1,186 @@ +{ + "bidConsistencyReview": { + "taskType": "bidConsistencyReview", + "name": "投标文件综合分析", + "mode": "tabs", + "tabs": [ + { + "key": "文档元数据", + "label": "文档元数据", + "fields": [ + { + "field": "originalText", + "title": "文档名称", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "bid", + "required": true + }, + { + "field": "reviewBasis", + "title": "详细元数据信息", + "dataType": "json", + "displayType": "markdown", + "jsonConfig": { + "extractFields": ["review_content", "review_points"], + "separator": ":", + "fieldProcessors": { + "review_points": "arrayJoinNewLine" + } + } + } + ] + }, + { + "key": "重点关注相似内容", + "label": "重点关注相似内容", + "fields": [ + + { + "field": "reviewBasis", + "title": "各文档具体内容", + "dataType": "json", + "displayType": "markdown", + "jsonConfig": { + "extractFields": ["review_points"], + "separator": ":", + "fieldProcessors": { + "review_points": "arrayJoinNewLine" + } + }, + "showComparison": true, + "comparisonConfig": { + "comparisonField": "modificationDisplay", + "comparisonTitle": "修改情况展示", + "showButton": true + } + }, + { + "field": "reviewBasis", + "title": "错别字", + "dataType": "json", + "displayType": "markdown", + "jsonConfig": { + "extractFields": ["error_review_points"], + "separator": ":", + "fieldProcessors": { + "error_review_points": "arrayJoinNewLine" + } + } + } + ] + }, + { + "key": "错别字", + "label": "错别字", + "fields": [ + { + "field": "originalText", + "title": "文档1原文", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "bid", + "required": true + }, + { + "field": "comparedText", + "title": "文档2原文", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "bid", + "required": true + }, + + { + "field": "reviewBasis", + "title": "错误详情", + "dataType": "json", + "displayType": "markdown", + "jsonConfig": { + "extractFields": ["review_content", "review_points"], + "separator": ":", + "fieldProcessors": { + "review_points": "arrayJoin" + } + } + } + ] + }, + { + "key": "一般相似内容", + "label": "一般相似内容", + "fields": [ + { + "field": "modificationDisplay", + "title": "涉及文档", + "dataType": "string", + "displayType": "markdown" + }, + { + "field": "reviewBasis", + "title": "各文档具体内容", + "dataType": "json", + "displayType": "markdown", + "jsonConfig": { + "extractFields": ["review_content", "review_points"], + "separator": ":", + "fieldProcessors": { + "review_points": "arrayJoinNewLine" + } + } + } + ] + } + ] + }, + "tenderComplianceReview": { + "taskType": "tenderComplianceReview", + "name": "招投标合规性审查", + "mode": "single", + "fields": [ + { + "field": "originalText", + "title": "原文", + "dataType": "string", + "displayType": "markdown", + "pdfSource": "bid", + "required": true + }, + { + "field": "reviewBasis", + "title": "审查意见", + "dataType": "json", + "displayType": "markdown", + "jsonConfig": { + "extractFields": ["review_content"], + "separator": ":" + } + }, + { + "field": "reviewBasis", + "title": "审查要点", + "dataType": "json", + "displayType": "markdown", + "jsonConfig": { + "extractFields": ["review_points"], + "separator": ":", + "fieldProcessors": { + "review_points": "arrayJoinNewLine" + } + } + }, + { + "field": "modifiedContent", + "title": "修改建议", + "dataType": "string", + "displayType": "markdown" + }, + { + "field": "modificationDisplay", + "title": "修改情况", + "dataType": "string", + "displayType": "markdown" + } + ] + } + } \ No newline at end of file diff --git a/src/api/contractReview/ContractualClauseConfig/index.ts b/src/api/contractReview/ContractualClauseConfig/index.ts new file mode 100644 index 0000000..8d7fe39 --- /dev/null +++ b/src/api/contractReview/ContractualClauseConfig/index.ts @@ -0,0 +1,57 @@ +import { defHttp } from '@/utils/http/axios'; +import { ID, IDS, commonExport } from '@/api/base'; +import { ContractualClauseConfigVO, ContractualClauseConfigForm, ContractualClauseConfigQuery } from './model'; + +/** + * 查询合同条款配置列表 + * @param params + * @returns + */ +export function ContractualClauseConfigList(params?: ContractualClauseConfigQuery) { + return defHttp.get({ url: '/contractreview/contractualClauseConfig/list', params }); +} + +/** + * 导出合同条款配置列表 + * @param params + * @returns + */ +export function ContractualClauseConfigExport(params?: ContractualClauseConfigQuery) { + return commonExport('/contractreview/contractualClauseConfig/export', params ?? {}); +} + +/** + * 查询合同条款配置详细 + * @param id id + * @returns + */ +export function ContractualClauseConfigInfo(id: ID) { + return defHttp.get({ url: '/contractreview/contractualClauseConfig/' + id }); +} + +/** + * 新增合同条款配置 + * @param data + * @returns + */ +export function ContractualClauseConfigAdd(data: ContractualClauseConfigForm) { + return defHttp.postWithMsg({ url: '/contractreview/contractualClauseConfig', data }); +} + +/** + * 更新合同条款配置 + * @param data + * @returns + */ +export function ContractualClauseConfigUpdate(data: ContractualClauseConfigForm) { + return defHttp.putWithMsg({ url: '/contractreview/contractualClauseConfig', data }); +} + +/** + * 删除合同条款配置 + * @param id id + * @returns + */ +export function ContractualClauseConfigRemove(id: ID | IDS) { + return defHttp.deleteWithMsg({ url: '/contractreview/contractualClauseConfig/' + id }); +} \ No newline at end of file diff --git a/src/api/contractReview/ContractualClauseConfig/model.ts b/src/api/contractReview/ContractualClauseConfig/model.ts new file mode 100644 index 0000000..0ce3344 --- /dev/null +++ b/src/api/contractReview/ContractualClauseConfig/model.ts @@ -0,0 +1,48 @@ +/** + * 合同条款配置查询对象类型 + */ +export interface ContractualClauseConfigQuery { + pageNum?: number; + pageSize?: number; + contractualClauseTypeId?: number; + clauseName?: string; + isRequired?: boolean; + status?: number; + orderByColumn?: string; + isAsc?: string; +} + +/** + * 合同条款配置视图对象类型 + */ +export interface ContractualClauseConfigVO { + id?: number; + contractualClauseTypeId?: number; + clauseName?: string; + clauseDescription?: string; + isRequired?: boolean; + keywords?: string; + sortOrder?: number; + status?: number; + tenantId?: string; + delFlag?: string; + createDept?: number; + createBy?: number; + createTime?: string; + updateBy?: number; + updateTime?: string; +} + +/** + * 合同条款配置表单类型 + */ +export interface ContractualClauseConfigForm { + id?: number; + contractualClauseTypeId?: number; + clauseName?: string; + clauseDescription?: string; + isRequired?: boolean; + keywords?: string; + sortOrder?: number; + status?: number; +} \ No newline at end of file diff --git a/src/api/contractReview/ContractualClauseType/index.ts b/src/api/contractReview/ContractualClauseType/index.ts new file mode 100644 index 0000000..e782ed0 --- /dev/null +++ b/src/api/contractReview/ContractualClauseType/index.ts @@ -0,0 +1,57 @@ +import { defHttp } from '@/utils/http/axios'; +import { ID, IDS, commonExport } from '@/api/base'; +import { ContractualClauseTypeVO, ContractualClauseTypeForm, ContractualClauseTypeQuery } from './model'; + +/** + * 查询合同类型列表 + * @param params + * @returns + */ +export function ContractualClauseTypeList(params?: ContractualClauseTypeQuery) { + return defHttp.get({ url: '/contractreview/contractualClauseType/list', params }); +} + +/** + * 导出合同类型列表 + * @param params + * @returns + */ +export function ContractualClauseTypeExport(params?: ContractualClauseTypeQuery) { + return commonExport('/contractreview/contractualClauseType/export', params ?? {}); +} + +/** + * 查询合同类型详细 + * @param id id + * @returns + */ +export function ContractualClauseTypeInfo(id: ID) { + return defHttp.get({ url: '/contractreview/contractualClauseType/' + id }); +} + +/** + * 新增合同类型 + * @param data + * @returns + */ +export function ContractualClauseTypeAdd(data: ContractualClauseTypeForm) { + return defHttp.postWithMsg({ url: '/contractreview/contractualClauseType', data }); +} + +/** + * 更新合同类型 + * @param data + * @returns + */ +export function ContractualClauseTypeUpdate(data: ContractualClauseTypeForm) { + return defHttp.putWithMsg({ url: '/contractreview/contractualClauseType', data }); +} + +/** + * 删除合同类型 + * @param id id + * @returns + */ +export function ContractualClauseTypeRemove(id: ID | IDS) { + return defHttp.deleteWithMsg({ url: '/contractreview/contractualClauseType/' + id }); +} \ No newline at end of file diff --git a/src/api/contractReview/ContractualClauseType/model.ts b/src/api/contractReview/ContractualClauseType/model.ts new file mode 100644 index 0000000..e4f2fdf --- /dev/null +++ b/src/api/contractReview/ContractualClauseType/model.ts @@ -0,0 +1,43 @@ +/** + * 合同类型查询对象类型 + */ +export interface ContractualClauseTypeQuery { + pageNum?: number; + pageSize?: number; + typeName?: string; + typeCode?: string; + status?: number; + orderByColumn?: string; + isAsc?: string; +} + +/** + * 合同类型视图对象类型 + */ +export interface ContractualClauseTypeVO { + id?: number; + typeName?: string; + typeCode?: string; + description?: string; + sortOrder?: number; + status?: number; + tenantId?: string; + delFlag?: string; + createDept?: number; + createBy?: number; + createTime?: string; + updateBy?: number; + updateTime?: string; +} + +/** + * 合同类型表单类型 + */ +export interface ContractualClauseTypeForm { + id?: number; + typeName?: string; + typeCode?: string; + description?: string; + sortOrder?: number; + status?: number; +} \ No newline at end of file diff --git a/src/api/contractReview/ContractualTasks/index.ts b/src/api/contractReview/ContractualTasks/index.ts index 2924ed8..7fdc9d5 100644 --- a/src/api/contractReview/ContractualTasks/index.ts +++ b/src/api/contractReview/ContractualTasks/index.ts @@ -1,6 +1,8 @@ import { defHttp } from '@/utils/http/axios'; import { ID, IDS, commonExport } from '@/api/base'; import { ContractualTasksVO, ContractualTasksForm, ContractualTasksQuery, StartContractReviewRequest } from './model'; +import { UploadFileParams } from '#/axios'; +import { AxiosProgressEvent } from 'axios'; /** * 查询合同任务列表 @@ -65,7 +67,11 @@ export function AnalyzeContract(ossId: string) { return defHttp.get({ url: '/contractreview/contractualTasks/analyzeContract', params: { ossId } }); } - +/** + * 启动合同审查任务 + * + * @param data 审查请求数据 + */ export function StartReview(data: StartContractReviewRequest) { return defHttp.postWithMsg({ url: '/contractreview/contractualTasks/startReview', @@ -74,5 +80,32 @@ export function StartReview(data: StartContractReviewRequest) { }); } +/** + * 上传合同文档 + * @description: Upload contract document + */ +export function uploadContract( + params: UploadFileParams, + onUploadProgress?: (progressEvent: AxiosProgressEvent) => void, +) { + return defHttp.uploadFile( + { + url: '/contractreview/contractualTasks/upload', + onUploadProgress, + timeout: 1000 * 60 * 10, // 10分钟超时 + }, + params, + ); +} + +/** + * 删除合同文档 + * @param ossId OSS文件ID + * @returns + */ +export function ContractFileRemove(ossId: string) { + return defHttp.deleteWithMsg({ url: '/contractreview/contractualTasks/ossRemoveById/' + ossId }); +} + // 导出类型定义 export type { StartContractReviewRequest } from './model'; diff --git a/src/components/UniversalResultDrawer.vue b/src/components/UniversalResultDrawer.vue index 021f3a4..daebe8a 100644 --- a/src/components/UniversalResultDrawer.vue +++ b/src/components/UniversalResultDrawer.vue @@ -10,9 +10,9 @@ :destroyOnClose="true" >
-
+
-
+
-
+
-
+
getTaskConfig(props.taskType)); + const config = ref(null); + + // 异步加载配置 + const loadConfig = async () => { + try { + config.value = await getTaskConfig(props.taskType); + } catch (error) { + console.error('加载配置失败,使用同步配置:', error); + config.value = getTaskConfigSync(props.taskType); + } + }; // 基础状态管理 const activeCollapseKeys = ref(['0']); @@ -447,7 +458,10 @@ } } - if (!pdfConfig) return null; + // 检查 pdfConfig 是否存在且有效 + if (!pdfConfig || !pdfConfig.layout || !pdfConfig.sources || pdfConfig.sources.length === 0) { + return null; + } // 创建适配ReviewTypeConfig接口的配置对象 return { @@ -459,36 +473,95 @@ } as any; }); - // 可用标签页(直接基于数据生成) + // 可用标签页(按配置顺序展示) const availableTabs = computed(() => { - if (config.value?.mode !== 'tabs' || !props.taskResultDetail || props.taskResultDetail.length === 0) { + if (!config.value || config.value.mode !== 'tabs' || !props.taskResultDetail || props.taskResultDetail.length === 0) { return []; } - // 直接使用所有数据生成标签页 - const dataToProcess = props.taskResultDetail; + // 如果配置中没有定义 tabs,则回退到基于数据生成 + if (!config.value.tabs || config.value.tabs.length === 0) { + console.warn('配置中未定义 tabs,使用数据生成标签页'); + return props.taskResultDetail.map((category, index) => { + let label = `${category.name} (${category.results?.length || 0})`; + + // 特殊处理:如果是实质性审查,加上审查立场 + if (category.name === '实质性审查' && props.taskInfo?.contractPartyRole) { + label = `(${props.taskInfo.contractPartyRole})实质性审查 (${category.results?.length || 0})`; + } + + return { + key: category.name, + label: label, + dataIndex: index, + name: category.name + }; + }); + } + + // 按配置的 tabs 顺序生成标签页 + const availableTabsWithData: any[] = []; - // 根据数据生成标签页 - return dataToProcess.map((category, index) => { - let label = `${category.name} (${category.results?.length || 0})`; + config.value.tabs.forEach((tabConfig, configIndex) => { + // 在数据中查找对应的分类 + const correspondingDataCategory = props.taskResultDetail.find(category => { + // 尝试多种匹配方式 + const matches = [ + category.name === tabConfig.label, + category.name === tabConfig.key, + category.name.includes(tabConfig.label), + tabConfig.label.includes(category.name), + // 额外的匹配:去掉括号和空格后的匹配 + category.name.replace(/[()\(\)\s]/g, '') === tabConfig.label.replace(/[()\(\)\s]/g, ''), + // 处理特殊情况:审查类型的匹配 + (category.name.includes('审查') && tabConfig.label.includes('审查') && + (category.name.includes(tabConfig.label.replace('审查', '')) || + tabConfig.label.includes(category.name.replace('审查', '')))) + ]; + + const isMatch = matches.some(Boolean); + if (isMatch) { + console.log(`匹配成功: 数据"${category.name}" <-> 配置"${tabConfig.label}" (key: ${tabConfig.key})`); + } + return isMatch; + }); - // 特殊处理:如果是实质性审查,加上审查立场 - if (category.name === '实质性审查' && props.taskInfo?.contractPartyRole) { - label = `(${props.taskInfo.contractPartyRole})实质性审查 (${category.results?.length || 0})`; + if (correspondingDataCategory) { + let label = `${correspondingDataCategory.name} (${correspondingDataCategory.results?.length || 0})`; + + // 特殊处理:如果是实质性审查,加上审查立场 + if (correspondingDataCategory.name === '实质性审查' && props.taskInfo?.contractPartyRole) { + label = `(${props.taskInfo.contractPartyRole})实质性审查 (${correspondingDataCategory.results?.length || 0})`; + } + + // 记录在原数据中的索引 + const dataIndex = props.taskResultDetail.findIndex(category => category === correspondingDataCategory); + + availableTabsWithData.push({ + key: correspondingDataCategory.name, // 使用数据的name作为key + label: label, + dataIndex: dataIndex, + name: correspondingDataCategory.name, + configIndex: configIndex, // 记录在配置中的索引,用于排序 + tabConfig: tabConfig // 保存tab配置引用 + }); + } else { + console.log(`未找到匹配的数据分类: 配置"${tabConfig.label}" (key: ${tabConfig.key})`); } - - return { - key: category.name, // 使用数据的name作为key - label: label, - dataIndex: index, // 记录在原数据中的索引 - name: category.name - }; }); + + // 按配置中的顺序排序 + availableTabsWithData.sort((a, b) => a.configIndex - b.configIndex); + + console.log('Generated tabs based on config order:', availableTabsWithData.map(tab => + `${tab.configIndex}: ${tab.label}`)); + + return availableTabsWithData; }); // 过滤后的结果(用于标签模式) const filteredResults = computed(() => { - if (config.value?.mode !== 'tabs' || !props.taskResultDetail || !currentTab.value) { + if (!config.value || config.value.mode !== 'tabs' || !props.taskResultDetail || !currentTab.value) { return []; } @@ -508,7 +581,7 @@ // 单一模式的过滤结果 const filteredTaskResultDetail = computed(() => { - if (config.value?.mode === 'tabs') return []; + if (!config.value || config.value.mode === 'tabs') return []; if (props.taskResultDetail.length <= 0) { return []; @@ -524,25 +597,104 @@ return props.taskResultDetail; }); + // 检查值是否为有效数据(非空) + const hasValidData = (value: any, fieldConfig?: FieldConfig): boolean => { + if (value === null || value === undefined) { + return false; + } + + if (typeof value === 'string') { + return value.trim() !== ''; + } + + if (Array.isArray(value)) { + return value.length > 0; + } + + if (typeof value === 'object') { + // 如果是JSON配置且有extractFields,需要检查指定字段是否有有效数据 + if (fieldConfig && fieldConfig.dataType === 'json' && fieldConfig.jsonConfig?.extractFields) { + const extractFields = fieldConfig.jsonConfig.extractFields; + + // 检查是否至少有一个配置的字段有有效数据 + return extractFields.some((fieldName: string) => { + const fieldValue = value[fieldName]; + if (!fieldValue) return false; + + // 应用字段处理器(如果有) + let processedValue = fieldValue; + if (fieldConfig.jsonConfig?.fieldProcessors && fieldConfig.jsonConfig.fieldProcessors[fieldName]) { + const processor = fieldConfig.jsonConfig.fieldProcessors[fieldName]; + + // 如果处理器是函数,直接调用 + if (typeof processor === 'function') { + processedValue = processor(fieldValue); + } + // 如果处理器是字符串,按名称处理 + else if (typeof processor === 'string') { + if (processor === 'arrayJoinNewLine' && Array.isArray(fieldValue)) { + processedValue = fieldValue.length > 0 ? fieldValue.join('\n') : ''; + } else if (processor === 'arrayJoin' && Array.isArray(fieldValue)) { + processedValue = fieldValue.length > 0 ? fieldValue.join(', ') : ''; + } else if (processor === 'extractFirst' && Array.isArray(fieldValue)) { + processedValue = fieldValue.length > 0 ? fieldValue[0] : ''; + } + } + } else { + // 默认处理:如果是数组,连接所有元素 + if (Array.isArray(fieldValue) && fieldValue.length > 0) { + processedValue = fieldValue.join('\n'); + } + } + + return hasValidData(processedValue); + }); + } + + // 对于普通对象,检查是否有有效的属性值 + return Object.keys(value).length > 0 && Object.values(value).some(v => hasValidData(v)); + } + + return true; + }; + // 获取当前字段配置 const getContentSections = (item: TaskResultItem): FieldConfig[] => { - if (config.value?.mode === 'tabs') { - // 多标签模式:根据当前标签页名称匹配配置中的tab - // 通过数据的name(中文)匹配配置中的label - const currentTabConfig = config.value.tabs?.find(tab => tab.label === currentTab.value); + if (!config.value) return []; + + if (config.value.mode === 'tabs') { + // 多标签模式:优先使用缓存的标签页配置 + let currentTabConfig: any = null; + + // 首先尝试从 availableTabs 中找到当前标签页的配置 + const currentTabData = availableTabs.value.find(tab => tab.key === currentTab.value); + if (currentTabData && currentTabData.tabConfig) { + currentTabConfig = currentTabData.tabConfig; + } else { + // 回退到原有的匹配方式 + currentTabConfig = config.value.tabs?.find(tab => + tab.label === currentTab.value || + tab.key === currentTab.value || + tab.label.includes(currentTab.value) || + currentTab.value.includes(tab.label) + ); + } + return (currentTabConfig?.fields || []).filter(field => { if (field.displayCondition) { return field.displayCondition(item); } - return getItemValue(item, field.field); + const fieldValue = getItemValue(item, field.field); + return hasValidData(fieldValue, field); }); } else { // 单一模式:使用配置的字段 - return (config.value?.fields || []).filter(field => { + return (config.value.fields || []).filter(field => { if (field.displayCondition) { return field.displayCondition(item); } - return getItemValue(item, field.field); + const fieldValue = getItemValue(item, field.field); + return hasValidData(fieldValue, field); }); } }; @@ -619,11 +771,36 @@ // 应用字段处理器 if (jsonConfig.fieldProcessors && jsonConfig.fieldProcessors[fieldName]) { - fieldValue = jsonConfig.fieldProcessors[fieldName](fieldValue); + const processor = jsonConfig.fieldProcessors[fieldName]; + + // 如果处理器是函数,直接调用 + if (typeof processor === 'function') { + fieldValue = processor(fieldValue); + } + // 如果处理器是字符串,按名称处理 + else if (typeof processor === 'string') { + if (processor === 'arrayJoinNewLine' && Array.isArray(fieldValue)) { + fieldValue = fieldValue.join('\n'); + } else if (processor === 'arrayJoin' && Array.isArray(fieldValue)) { + fieldValue = fieldValue.join(', '); + } else if (processor === 'extractFirst' && Array.isArray(fieldValue)) { + fieldValue = fieldValue.length > 0 ? fieldValue[0] : ''; + } else { + // 默认处理 + if (Array.isArray(fieldValue) && fieldValue.length > 0) { + fieldValue = fieldValue.join('\n'); + } + } + } else { + // 默认处理 + if (Array.isArray(fieldValue) && fieldValue.length > 0) { + fieldValue = fieldValue.join('\n'); + } + } } else { - // 默认处理:如果是数组取第一个元素 + // 默认处理:如果是数组,显示所有元素(用换行符连接) if (Array.isArray(fieldValue) && fieldValue.length > 0) { - fieldValue = fieldValue[0]; + fieldValue = fieldValue.join('\n'); } } @@ -632,7 +809,6 @@ } } }); - return renderMarkdown(values.join(separator)); }; @@ -673,7 +849,7 @@ // 根据过滤条件更新折叠状态 const updateActiveItemKeys = () => { const newActiveKeys: string[] = []; - const dataToProcess = config.value?.mode === 'tabs' ? filteredResults.value : filteredTaskResultDetail.value; + const dataToProcess = config.value && config.value.mode === 'tabs' ? filteredResults.value : filteredTaskResultDetail.value; dataToProcess.forEach((category: TaskResultCategory, categoryIndex: number) => { if (category?.results) { @@ -736,7 +912,7 @@ await props.updateResultItemStatus(id, field, value); // 更新本地状态 - const dataToUpdate = config.value?.mode === 'tabs' ? props.taskResultDetail : filteredTaskResultDetail.value; + const dataToUpdate = config.value && config.value.mode === 'tabs' ? props.taskResultDetail : filteredTaskResultDetail.value; dataToUpdate.forEach((category: TaskResultCategory) => { if (category?.results) { category.results.forEach((item: TaskResultItem) => { @@ -974,8 +1150,11 @@ const initData = async () => { loading.value = true; try { + // 首先加载配置 + await loadConfig(); + if (props.taskResultDetail) { - if (config.value?.mode === 'tabs' && availableTabs.value.length > 0) { + if (config.value && config.value.mode === 'tabs' && availableTabs.value.length > 0) { // 使用第一个数据类别的名称作为默认tab currentTab.value = availableTabs.value[0].key; } else { @@ -1336,6 +1515,10 @@ overflow: hidden; } + .split-layout.no-pdf-layout { + justify-content: center; + } + .pdf-preview-wrapper { width: 50%; height: 100%; @@ -1350,6 +1533,10 @@ flex-direction: column; } + .content-container.full-width { + width: 100%; + } + .review-type-tabs { flex-shrink: 0; border-bottom: 1px solid #e8e8e8; diff --git a/src/configs/jsonConfigLoader.ts b/src/configs/jsonConfigLoader.ts new file mode 100644 index 0000000..9256f1d --- /dev/null +++ b/src/configs/jsonConfigLoader.ts @@ -0,0 +1,157 @@ + // JSON配置加载器 - 支持从public目录动态加载配置文件 + +import type { TaskConfig } from './taskConfigTypes'; + +// 缓存已加载的配置 +const configCache = new Map>(); + +/** + * 字段处理器映射 - 将字符串类型的处理器转换为实际函数 + */ +const FIELD_PROCESSORS: Record string> = { + arrayFirst: (value) => Array.isArray(value) && value.length > 0 ? value[0] : value, + arrayJoin: (value) => Array.isArray(value) ? value.join(', ') : value, + arrayJoinNewLine: (value) => Array.isArray(value) ? value.join('\n') : value, + extractFirst: (value) => Array.isArray(value) && value.length > 0 ? value[0] : value, +}; + +/** + * 处理JSON配置中的字段处理器 + */ +function processFieldProcessors(config: any): any { + if (!config) return config; + + if (Array.isArray(config)) { + return config.map(processFieldProcessors); + } + + if (typeof config === 'object') { + const processed: any = {}; + + for (const [key, value] of Object.entries(config)) { + if (key === 'fieldProcessors' && typeof value === 'object') { + // 转换字符串处理器为实际函数 + const processors: Record string> = {}; + for (const [fieldName, processorName] of Object.entries(value as Record)) { + if (typeof processorName === 'string' && FIELD_PROCESSORS[processorName]) { + processors[fieldName] = FIELD_PROCESSORS[processorName]; + } + } + processed[key] = processors; + } else { + processed[key] = processFieldProcessors(value); + } + } + + return processed; + } + + return config; +} + +/** + * 从public目录加载JSON配置文件 + */ +async function loadJsonConfig(fileName: string): Promise | null> { + try { + // 检查缓存 + if (configCache.has(fileName)) { + return configCache.get(fileName)!; + } + + // 从public目录加载配置文件 + const response = await fetch(`/configs/${fileName}`); + + if (!response.ok) { + console.warn(`配置文件 ${fileName} 加载失败:`, response.status, response.statusText); + return null; + } + + const rawConfig = await response.json(); + + // 处理字段处理器 + const processedConfig = processFieldProcessors(rawConfig); + + // 缓存配置 + configCache.set(fileName, processedConfig); + + console.log(`成功加载JSON配置文件: ${fileName}`); + return processedConfig; + + } catch (error) { + console.warn(`加载JSON配置文件 ${fileName} 时出错:`, error); + return null; + } +} + +/** + * 清空配置缓存 - 用于强制重新加载配置 + */ +export function clearConfigCache(): void { + configCache.clear(); + console.log('配置缓存已清空'); +} + +/** + * 重新加载指定配置文件 + */ +export async function reloadConfig(fileName: string): Promise | null> { + configCache.delete(fileName); + return await loadJsonConfig(fileName); +} + +/** + * 加载文档任务配置 + */ +export async function loadDocumentTaskConfigs(): Promise | null> { + return await loadJsonConfig('documentTaskConfigs.json'); +} + +/** + * 加载合同任务配置 + */ +export async function loadContractTaskConfigs(): Promise | null> { + return await loadJsonConfig('contractTaskConfigs.json'); +} + +/** + * 加载招投标任务配置 + */ +export async function loadTenderTaskConfigs(): Promise | null> { + return await loadJsonConfig('tenderTaskConfigs.json'); +} + +/** + * 验证配置格式是否正确 + */ +function validateTaskConfig(config: any): boolean { + if (!config || typeof config !== 'object') return false; + + const requiredFields = ['taskType', 'name', 'mode']; + for (const field of requiredFields) { + if (!config[field]) return false; + } + + if (config.mode === 'tabs' && !config.tabs) return false; + if (config.mode === 'single' && !config.fields) return false; + + return true; +} + +/** + * 检查JSON配置是否可用 + */ +export async function isJsonConfigAvailable(): Promise { + try { + const response = await fetch('/configs/documentTaskConfigs.json', { method: 'HEAD' }); + return response.ok; + } catch { + return false; + } +} + +// 导出配置加载函数 +export { + loadJsonConfig, + validateTaskConfig +}; \ No newline at end of file diff --git a/src/configs/taskConfigs.ts b/src/configs/taskConfigs.ts index 183cfcb..7b412cd 100644 --- a/src/configs/taskConfigs.ts +++ b/src/configs/taskConfigs.ts @@ -4,6 +4,13 @@ import type { TaskConfig, FieldConfig } from './taskConfigTypes'; import { DOCUMENT_TASK_CONFIGS, getDocumentTaskConfig } from './documentTaskConfigs'; import { CONTRACT_TASK_CONFIGS, getContractTaskConfig } from './contractTaskConfigs'; import { TENDER_TASK_CONFIGS, getTenderTaskConfig } from './tenderTaskConfigs'; +import { + loadDocumentTaskConfigs, + loadContractTaskConfigs, + loadTenderTaskConfigs, + isJsonConfigAvailable, + clearConfigCache +} from './jsonConfigLoader'; // 合并所有任务配置 const ALL_TASK_CONFIGS: Record = { @@ -12,8 +19,73 @@ const ALL_TASK_CONFIGS: Record = { ...TENDER_TASK_CONFIGS }; -// 统一的获取任务配置函数 -export function getTaskConfig(taskType: string): TaskConfig | null { +// JSON配置缓存 +let jsonConfigsLoaded = false; +let jsonDocumentConfigs: Record | null = null; +let jsonContractConfigs: Record | null = null; +let jsonTenderConfigs: Record | null = null; + +/** + * 初始化JSON配置加载 + */ +async function initJsonConfigs(): Promise { + if (jsonConfigsLoaded) return; + + try { + // 检查JSON配置是否可用 + const isAvailable = await isJsonConfigAvailable(); + if (!isAvailable) { + console.log('JSON配置文件不可用,使用默认TypeScript配置'); + jsonConfigsLoaded = true; + return; + } + + // 并行加载所有JSON配置文件 + const [docConfigs, contractConfigs, tenderConfigs] = await Promise.all([ + loadDocumentTaskConfigs(), + loadContractTaskConfigs(), + loadTenderTaskConfigs() + ]); + + jsonDocumentConfigs = docConfigs; + jsonContractConfigs = contractConfigs; + jsonTenderConfigs = tenderConfigs; + + jsonConfigsLoaded = true; + + const loadedCount = [docConfigs, contractConfigs, tenderConfigs].filter(Boolean).length; + console.log(`成功加载 ${loadedCount} 个JSON配置文件`); + + } catch (error) { + console.error('加载JSON配置时出错:', error); + jsonConfigsLoaded = true; // 设置为已加载,避免重复尝试 + } +} + +/** + * 从JSON配置中获取任务配置 + */ +function getJsonTaskConfig(taskType: string): TaskConfig | null { + // 依次检查各个JSON配置 + if (jsonDocumentConfigs?.[taskType]) { + return jsonDocumentConfigs[taskType]; + } + + if (jsonContractConfigs?.[taskType]) { + return jsonContractConfigs[taskType]; + } + + if (jsonTenderConfigs?.[taskType]) { + return jsonTenderConfigs[taskType]; + } + + return null; +} + +/** + * 从TypeScript配置中获取任务配置(原有逻辑) + */ +function getTypescriptTaskConfig(taskType: string): TaskConfig | null { // 首先尝试从文档任务配置中获取 let config = getDocumentTaskConfig(taskType); if (config) return config; @@ -29,6 +101,49 @@ export function getTaskConfig(taskType: string): TaskConfig | null { return null; } +// 统一的获取任务配置函数 +export async function getTaskConfig(taskType: string): Promise { + // 确保JSON配置已初始化 + await initJsonConfigs(); + + // 优先从JSON配置获取 + const jsonConfig = getJsonTaskConfig(taskType); + if (jsonConfig) { + console.log(`使用JSON配置: ${taskType}`); + return jsonConfig; + } + + // 回退到TypeScript配置 + const tsConfig = getTypescriptTaskConfig(taskType); + if (tsConfig) { + console.log(`使用TypeScript配置: ${taskType}`); + return tsConfig; + } + + return null; +} + +/** + * 同步版本的获取配置函数(用于向后兼容) + * 注意:这个函数只会返回TypeScript配置,建议迁移到异步版本 + */ +export function getTaskConfigSync(taskType: string): TaskConfig | null { + console.warn('使用同步配置获取,建议迁移到 getTaskConfig 异步版本'); + return getTypescriptTaskConfig(taskType); +} + +/** + * 重新加载JSON配置 + */ +export async function reloadJsonConfigs(): Promise { + clearConfigCache(); + jsonConfigsLoaded = false; + jsonDocumentConfigs = null; + jsonContractConfigs = null; + jsonTenderConfigs = null; + await initJsonConfigs(); +} + // 获取所有可用配置 export function getAvailableConfigs(): TaskConfig[] { return Object.values(ALL_TASK_CONFIGS); diff --git a/src/views/contractReview/ContractClauseConfig/index.vue b/src/views/contractReview/ContractClauseConfig/index.vue new file mode 100644 index 0000000..bf8b9d6 --- /dev/null +++ b/src/views/contractReview/ContractClauseConfig/index.vue @@ -0,0 +1,924 @@ +