19 changed files with 1435 additions and 314 deletions
@ -0,0 +1,192 @@ |
|||
import { defHttp } from '@/utils/http/axios'; |
|||
import { ID, IDS, commonExport } from '@/api/base'; |
|||
import { TenderTaskResultsVO, TenderTaskResultsForm, TenderTaskResultsQuery, TenderTaskResultDetailVO } from './model'; |
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
interface DownloadOptions { |
|||
filename?: string; |
|||
mimeType?: string; |
|||
} |
|||
|
|||
/** |
|||
* 通用文件下载函数 |
|||
* @param url 下载地址 |
|||
* @param onError 错误处理回调 |
|||
* @returns Promise<boolean> 下载是否成功 |
|||
*/ |
|||
export async function useDownload( |
|||
url: string, |
|||
onError?: (error: any) => void |
|||
): Promise<boolean> { |
|||
try { |
|||
const response = await defHttp.get( |
|||
{ |
|||
url, |
|||
responseType: 'blob', |
|||
timeout: 60000, // 设置较长的超时时间
|
|||
}, |
|||
{ |
|||
isReturnNativeResponse: true, |
|||
// 自定义错误处理
|
|||
errorMessageMode: 'none', |
|||
} |
|||
); |
|||
|
|||
// 检查响应类型
|
|||
const contentType = response.headers['content-type']; |
|||
if (contentType && contentType.includes('application/json')) { |
|||
// 如果返回的是JSON(通常是错误信息),转换并抛出
|
|||
const reader = new FileReader(); |
|||
reader.onload = () => { |
|||
const error = JSON.parse(reader.result as string); |
|||
message.error(error.message || '下载失败'); |
|||
onError?.(error); |
|||
}; |
|||
reader.readAsText(response.data); |
|||
return false; |
|||
} |
|||
|
|||
// 获取文件名
|
|||
const contentDisposition = response.headers['content-disposition']; |
|||
let fileName = ''; |
|||
|
|||
if (contentDisposition) { |
|||
const matches = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/); |
|||
if (matches && matches[1]) { |
|||
fileName = decodeURIComponent(matches[1].replace(/['"]/g, '')); |
|||
} |
|||
} |
|||
|
|||
// 创建Blob对象
|
|||
const blob = new Blob([response.data], { |
|||
type: contentType || 'application/octet-stream' |
|||
}); |
|||
|
|||
if (window.navigator && (window.navigator as any).msSaveOrOpenBlob) { |
|||
// 针对IE的处理
|
|||
(window.navigator as any).msSaveOrOpenBlob(blob, fileName); |
|||
} else { |
|||
// 现代浏览器的处理
|
|||
const url = window.URL.createObjectURL(blob); |
|||
const link = document.createElement('a'); |
|||
link.href = url; |
|||
link.download = fileName; |
|||
link.style.display = 'none'; |
|||
document.body.appendChild(link); |
|||
link.click(); |
|||
document.body.removeChild(link); |
|||
window.URL.revokeObjectURL(url); |
|||
} |
|||
|
|||
return true; |
|||
} catch (error: any) { |
|||
console.error('下载失败:', error); |
|||
message.error(error.message || '下载失败,请稍后重试'); |
|||
onError?.(error); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 查询招投标任务结果列表 |
|||
* @param params |
|||
* @returns |
|||
*/ |
|||
export function TenderTaskResultsList(params?: TenderTaskResultsQuery) { |
|||
return defHttp.get<TenderTaskResultsVO[]>({ url: '/tenderTask/tenderTaskResults/list', params }); |
|||
} |
|||
|
|||
/** |
|||
* 导出招投标任务结果列表 |
|||
* @param params |
|||
* @returns |
|||
*/ |
|||
export function TenderTaskResultsExport(params?: TenderTaskResultsQuery) { |
|||
return commonExport('/tenderTask/tenderTaskResults/export', params ?? {}); |
|||
} |
|||
|
|||
/** |
|||
* 查询招投标任务结果详细 |
|||
* @param id id |
|||
* @returns |
|||
*/ |
|||
export function TenderTaskResultsInfo(id: ID) { |
|||
return defHttp.get<TenderTaskResultsVO>({ url: '/tenderTask/tenderTaskResults/' + id }); |
|||
} |
|||
|
|||
/** |
|||
* 根据任务id查询详细分类的招投标任务结果 |
|||
* @param taskId 任务ID |
|||
* @returns 详细的招投标任务结果列表 |
|||
*/ |
|||
export function getDetailResultsByTaskId(taskId: ID) { |
|||
return defHttp.get<TenderTaskResultDetailVO[]>({ url: '/tenderTask/tenderTaskResults/taskDetail/' + taskId }); |
|||
} |
|||
|
|||
/** |
|||
* 新增招投标任务结果 |
|||
* @param data |
|||
* @returns |
|||
*/ |
|||
export function TenderTaskResultsAdd(data: TenderTaskResultsForm) { |
|||
return defHttp.postWithMsg<void>({ url: '/tenderTask/tenderTaskResults', data }); |
|||
} |
|||
|
|||
/** |
|||
* 更新招投标任务结果 |
|||
* @param data |
|||
* @returns |
|||
*/ |
|||
export function TenderTaskResultsUpdate(data: TenderTaskResultsForm) { |
|||
return defHttp.putWithMsg<void>({ url: '/tenderTask/tenderTaskResults', data }); |
|||
} |
|||
|
|||
/** |
|||
* 删除招投标任务结果 |
|||
* @param id id |
|||
* @returns |
|||
*/ |
|||
export function TenderTaskResultsRemove(id: ID | IDS) { |
|||
return defHttp.deleteWithMsg<void>({ url: '/tenderTask/tenderTaskResults/' + id }); |
|||
} |
|||
|
|||
/** |
|||
* 更新招投标任务结果项的状态(已读/采纳) |
|||
* @param id 结果项ID |
|||
* @param field 字段名(isRead/isAdopted) |
|||
* @param value 值(0/1) |
|||
* @returns |
|||
*/ |
|||
export function updateResultItemStatus(id: ID, field: 'isRead' | 'isAdopted', value: '0' | '1') { |
|||
return defHttp.putWithMsg<void>({ |
|||
url: `/tenderTask/tenderTaskResults/updateResultItemStatus/${id}/${field}/${value}` |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 下载招投标任务结果 |
|||
* @param id id |
|||
* @returns |
|||
*/ |
|||
export function TenderTaskResultDownload(id: ID | IDS) { |
|||
return useDownload(`/tenderTask/tenderTaskResults/downloadResult/${id}`); |
|||
} |
|||
|
|||
/** |
|||
* 获取PDF文件流 |
|||
* @param taskId 任务ID |
|||
* @returns Promise<Blob> |
|||
*/ |
|||
export function getPdfStream(taskId: ID): Promise<Blob> { |
|||
return defHttp.get( |
|||
{ |
|||
url: `/tenderTask/tenderTaskResults/getPdfStream/${taskId}`, |
|||
responseType: 'blob', |
|||
timeout:600000 |
|||
}, |
|||
{ |
|||
isReturnNativeResponse: true, |
|||
errorMessageMode: 'none', |
|||
} |
|||
).then(response => response.data); |
|||
} |
@ -0,0 +1,99 @@ |
|||
import { BaseEntity, PageQuery } from '@/api/base'; |
|||
|
|||
export interface TenderTaskResultsVO { |
|||
/** |
|||
* 任务结果 |
|||
*/ |
|||
result?: string; |
|||
} |
|||
|
|||
export interface TenderTaskResultDetailVO { |
|||
/** |
|||
* 分类名称 |
|||
*/ |
|||
name: string; |
|||
/** |
|||
* 结果列表 |
|||
*/ |
|||
results: { |
|||
/** |
|||
* 唯一标识ID |
|||
*/ |
|||
id?: string; |
|||
/** |
|||
* 序号 |
|||
*/ |
|||
serialNumber?: number; |
|||
/** |
|||
* 问题点名称 |
|||
*/ |
|||
issueName?: string; |
|||
/** |
|||
* 原文 |
|||
*/ |
|||
originalText?: string; |
|||
/** |
|||
* 比对原文 |
|||
*/ |
|||
comparedText?: string; |
|||
/** |
|||
* 修改后的内容 |
|||
*/ |
|||
modifiedContent?: string; |
|||
/** |
|||
* 展示修改情况 |
|||
*/ |
|||
modificationDisplay?: string; |
|||
/** |
|||
* 存在的问题 |
|||
*/ |
|||
existingIssues?: string; |
|||
/** |
|||
* 审查依据 |
|||
*/ |
|||
reviewBasis?: { |
|||
/** |
|||
* 审查内容 |
|||
*/ |
|||
reviewContent?: string; |
|||
/** |
|||
* 审查点 |
|||
*/ |
|||
reviewPoints?: string[]; |
|||
}; |
|||
/** |
|||
* 是否已读 |
|||
*/ |
|||
isRead?: string; |
|||
/** |
|||
* 是否采纳 |
|||
*/ |
|||
isAdopted?: string; |
|||
}[]; |
|||
} |
|||
|
|||
export interface TenderTaskResultsForm extends BaseEntity { |
|||
} |
|||
|
|||
export interface TenderTaskResultsQuery extends PageQuery { |
|||
|
|||
/** |
|||
* id |
|||
*/ |
|||
id?: string | number; |
|||
|
|||
/** |
|||
* 任务id |
|||
*/ |
|||
tenderTaskId?: string | number; |
|||
|
|||
/** |
|||
* 任务结果 |
|||
*/ |
|||
result?: string; |
|||
|
|||
/** |
|||
* 日期范围参数 |
|||
*/ |
|||
params?: any; |
|||
} |
@ -0,0 +1,156 @@ |
|||
// 招投标审核任务配置文件
|
|||
|
|||
import type { TaskConfig } from './taskConfigTypes'; |
|||
|
|||
// 招投标审核任务配置
|
|||
export const TENDER_TASK_CONFIGS: Record<string, TaskConfig> = { |
|||
// 招投标文件审核(多标签模式)
|
|||
"sjjbidAnalysis": { |
|||
taskType: "sjjbidAnalysis", |
|||
name: "招投标文件审核", |
|||
mode: "tabs", |
|||
pdfConfig: { |
|||
layout: "split", |
|||
sources: [ |
|||
{ id: "bid", title: "投标文件", apiField: "id" }, |
|||
{ id: "tender", title: "招标文件", apiField: "id" } |
|||
] |
|||
}, |
|||
tabs: [ |
|||
{ |
|||
key: "两两相似", |
|||
label: "两两相似", |
|||
pdfConfig: { |
|||
layout: "split", |
|||
sources: [ |
|||
{ id: "bid", title: "投标文件", apiField: "id" }, |
|||
{ id: "tender", title: "招标文件", apiField: "id" } |
|||
] |
|||
}, |
|||
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: 'modificationDisplay', |
|||
title: '相似情况分析', |
|||
dataType: 'string', |
|||
displayType: 'markdown' |
|||
}, |
|||
{ |
|||
field: 'reviewBasis', |
|||
title: '分析详情', |
|||
dataType: 'json', |
|||
displayType: 'markdown', |
|||
jsonConfig: { |
|||
extractFields: ['review_content', 'review_points'], |
|||
separator: ':' |
|||
} |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
key: "错别字", |
|||
label: "错别字", |
|||
pdfConfig: { |
|||
layout: "split", |
|||
sources: [ |
|||
{ id: "bid", title: "投标文件", apiField: "id" }, |
|||
{ id: "tender", title: "招标文件", apiField: "id" } |
|||
] |
|||
}, |
|||
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: 'modificationDisplay', |
|||
title: '错误分析与修改建议', |
|||
dataType: 'string', |
|||
displayType: 'markdown' |
|||
}, |
|||
{ |
|||
field: 'reviewBasis', |
|||
title: '错误详情', |
|||
dataType: 'json', |
|||
displayType: 'markdown', |
|||
jsonConfig: { |
|||
extractFields: ['review_content', 'review_points'], |
|||
separator: ':', |
|||
fieldProcessors: { |
|||
'review_points': (value) => Array.isArray(value) ? value.join('\n') : value |
|||
} |
|||
} |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
key: "多家相同内容", |
|||
label: "多家相同内容", |
|||
pdfConfig: { |
|||
layout: "single", |
|||
sources: [ |
|||
{ id: "bid", title: "投标文件", apiField: "id" } |
|||
] |
|||
}, |
|||
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': (value) => Array.isArray(value) ? value.join('\n\n') : value |
|||
} |
|||
} |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||
}; |
|||
|
|||
// 获取招投标任务配置
|
|||
export function getTenderTaskConfig(taskType: string): TaskConfig | null { |
|||
return TENDER_TASK_CONFIGS[taskType] || null; |
|||
} |
|||
|
|||
// 获取所有招投标任务配置
|
|||
export function getAllTenderTaskConfigs(): TaskConfig[] { |
|||
return Object.values(TENDER_TASK_CONFIGS); |
|||
} |
@ -0,0 +1,55 @@ |
|||
<template> |
|||
<BasicDrawer v-bind="$attrs" title="结果详情" :width="drawerWidth" @register="registerDrawer"> |
|||
<!-- 纯预览 --> |
|||
<MdPreview |
|||
:modelValue="value" |
|||
:theme="theme" |
|||
previewTheme="github" |
|||
codeTheme="github" |
|||
:showCodeRowNumber="true" |
|||
:previewOnly="true" |
|||
/> |
|||
<template #footer> </template> |
|||
</BasicDrawer> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import text from './tender-caozuomiaoshu.md?raw'; |
|||
import { MdPreview } from 'md-editor-v3'; |
|||
import 'md-editor-v3/lib/style.css'; |
|||
import { ref, onMounted } from 'vue'; |
|||
import { BasicDrawer, useDrawer, useDrawerInner } from '@/components/Drawer'; |
|||
import { useMaxWidthOrDefault } from '@/hooks/web/useSize'; |
|||
|
|||
const value = ref<string>(''); |
|||
const type = ref<string>('markdown'); |
|||
const MarkdownViewerkeynum = ref<number>(0); |
|||
const iframeKey = ref<number>(10); |
|||
const drawerWidth = useMaxWidthOrDefault(1000); |
|||
const textvalue = ref<string>(text); |
|||
const [registerDrawer] = useDrawerInner(init); |
|||
// 主题设置:'light' | 'dark' |
|||
const theme = ref<'light' | 'dark'>('light'); |
|||
|
|||
onMounted(() => {}); |
|||
|
|||
function init(data) { |
|||
console.log('TenderDocsDrawer', data, document.documentElement.clientHeight); |
|||
if (data) { |
|||
value.value = data.value; |
|||
type.value = data.type; |
|||
MarkdownViewerkeynum.value += 1; |
|||
iframeKey.value += 1; |
|||
if (type.value == 'def') { |
|||
console.log(textvalue.value); |
|||
value.value = textvalue.value; |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
iframe { |
|||
width: 100%; |
|||
} |
|||
</style> |
@ -0,0 +1,56 @@ |
|||
<template> |
|||
<UniversalResultDrawer |
|||
:visible="visible" |
|||
:title="title" |
|||
:width="width" |
|||
:taskType="taskInfo.taskName || 'checkTenderError'" |
|||
:taskResultDetail="taskResultDetail as any" |
|||
:taskInfo="taskInfo" |
|||
:getPdfStream="handleGetPdfStream" |
|||
:updateResultItemStatus="handleStatusChange" |
|||
@update:visible="$emit('update:visible', $event)" |
|||
@close="$emit('close')" |
|||
/> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { type PropType } from 'vue'; |
|||
import UniversalResultDrawer from '@/components/UniversalResultDrawer.vue'; |
|||
import { TenderTaskResultDetailVO } from '@/api/tenderReview/TenderTaskResults/model'; |
|||
import { updateResultItemStatus, getPdfStream } from '@/api/tenderReview/TenderTaskResults'; |
|||
|
|||
const props = defineProps({ |
|||
visible: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
title: { |
|||
type: String, |
|||
default: '招投标审核结果' |
|||
}, |
|||
width: { |
|||
type: [String, Number], |
|||
default: '95%' |
|||
}, |
|||
taskResultDetail: { |
|||
type: Array as PropType<TenderTaskResultDetailVO[]>, |
|||
default: () => [] |
|||
}, |
|||
taskInfo: { |
|||
type: Object, |
|||
default: () => ({}) |
|||
} |
|||
}); |
|||
|
|||
const emit = defineEmits(['update:visible', 'close']); |
|||
|
|||
// PDF相关处理函数 |
|||
const handleGetPdfStream = async (taskId: string) => { |
|||
return await getPdfStream(taskId); |
|||
}; |
|||
|
|||
// 状态变更处理 |
|||
const handleStatusChange = async (id: string, field: string, value: string) => { |
|||
return await updateResultItemStatus(id, field as 'isRead' | 'isAdopted', value as '0' | '1'); |
|||
}; |
|||
</script> |
@ -0,0 +1,55 @@ |
|||
# 招投标文件审核操作说明 |
|||
|
|||
## 🎯 功能概述 |
|||
|
|||
招投标文件审核系统帮助您快速对招标文件和投标文件进行智能分析和审核,提高工作效率。 |
|||
|
|||
## 📋 操作步骤 |
|||
|
|||
### 1. 新增审核任务 |
|||
|
|||
1. **点击"新增"按钮** |
|||
2. **选择任务类型**: |
|||
- 招标文件文本审核 |
|||
- 招标文件图片审核 |
|||
3. **上传文件**: |
|||
- 招标文件:支持.docx、.pdf格式 |
|||
- 投标文件集:支持.zip格式 |
|||
4. **提交任务** |
|||
|
|||
### 2. 查看任务进度 |
|||
|
|||
- 在任务列表中可以实时查看任务进度 |
|||
- 任务状态: |
|||
- 🟡 **待处理**:任务已提交,等待处理 |
|||
- 🔵 **处理中**:任务正在执行 |
|||
- 🟢 **已完成**:任务处理完成 |
|||
- 🔴 **已终止**:任务被手动终止 |
|||
|
|||
### 3. 下载结果 |
|||
|
|||
- 任务完成后,点击"下载"按钮获取审核结果 |
|||
- 支持批量下载多个任务结果 |
|||
|
|||
### 4. 查看详细结果 |
|||
|
|||
- 点击"详情"按钮查看详细的审核报告 |
|||
- 包含问题点分析、修改建议等 |
|||
|
|||
## ⚠️ 注意事项 |
|||
|
|||
1. **文件大小限制**:单个文件不超过1GB |
|||
2. **文件格式**:请确保上传正确的文件格式 |
|||
3. **安全提醒**:严禁上传涉及国家秘密的文件 |
|||
4. **任务管理**:可随时终止正在处理的任务 |
|||
|
|||
## 🔧 常见问题 |
|||
|
|||
**Q: 任务一直处于"处理中"状态怎么办?** |
|||
A: 请检查网络连接,如果问题持续存在,可以终止任务后重新提交。 |
|||
|
|||
**Q: 支持哪些文件格式?** |
|||
A: 招标文件支持.docx、.pdf格式;投标文件需要压缩为.zip格式上传。 |
|||
|
|||
**Q: 可以同时处理多个任务吗?** |
|||
A: 是的,系统支持并发处理多个任务。 |
Loading…
Reference in new issue