23 changed files with 3601 additions and 2323 deletions
@ -1,2 +1,3 @@ |
|||||
export { default as ImagePreview } from './src/Preview.vue'; |
export { default as ImagePreview } from './src/Preview.vue'; |
||||
export { createImgPreview } from './src/functional'; |
export { createImgPreview } from './src/functional'; |
||||
|
export { PdfPreviewComponent, PageSelectModal } from './src/PdfPreview'; |
||||
|
@ -0,0 +1,499 @@ |
|||||
|
<template> |
||||
|
<div class="pdf-preview"> |
||||
|
<div class="pdf-container" ref="pdfContainer"> |
||||
|
<div v-if="loading" class="loading-overlay"> |
||||
|
<Spin size="large" :tip="loadingTip" /> |
||||
|
</div> |
||||
|
<canvas ref="pdfCanvas" :style="{ opacity: loading ? 0.3 : 1 }"></canvas> |
||||
|
</div> |
||||
|
<div class="pdf-controls"> |
||||
|
<Button @click="zoomOut" :disabled="scale <= 0.5 || loading">-</Button> |
||||
|
<Button @click="prevPage" :disabled="currentPage <= 1 || loading" class="ml-2"> |
||||
|
<LeftOutlined /> |
||||
|
</Button> |
||||
|
<span class="page-info">{{ currentPage }} / {{ totalPages }}</span> |
||||
|
<Button @click="nextPage" :disabled="currentPage >= totalPages || loading" class="ml-2"> |
||||
|
<RightOutlined /> |
||||
|
</Button> |
||||
|
<Button @click="zoomIn" :disabled="scale >= 3 || loading" class="ml-2">+</Button> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 页面选择模态框 --> |
||||
|
<PageSelectModal |
||||
|
:visible="showPageSelectModal" |
||||
|
:pages="matchedPages" |
||||
|
:onSelect="handlePageSelect" |
||||
|
:onClose="handlePageModalClose" |
||||
|
/> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import { ref, watch, onBeforeUnmount, nextTick } from 'vue'; |
||||
|
import { Button, Spin } from 'ant-design-vue'; |
||||
|
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue'; |
||||
|
import { message } from 'ant-design-vue'; |
||||
|
import * as pdfjsLib from 'pdfjs-dist'; |
||||
|
import { PDFDocumentProxy } from 'pdfjs-dist/types/src/display/api'; |
||||
|
import PageSelectModal from './PageSelectModal.vue'; |
||||
|
|
||||
|
// 设置PDF.js worker路径 |
||||
|
pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.js'; |
||||
|
|
||||
|
interface PdfPreviewProps { |
||||
|
pdfData?: Blob | null; |
||||
|
taskId?: string; |
||||
|
getPdfStream?: (taskId: string) => Promise<Blob>; |
||||
|
} |
||||
|
|
||||
|
interface PdfLoadedData { |
||||
|
totalPages: number; |
||||
|
} |
||||
|
|
||||
|
interface PdfPreviewInstance { |
||||
|
locateByText: (text: string) => Promise<void>; |
||||
|
locateByTextAndPage: (text: string, pageNumber: number) => Promise<void>; |
||||
|
loadPdf: (data?: Blob | string) => Promise<void>; |
||||
|
renderPage: (num: number, highlightStr?: string) => Promise<void>; |
||||
|
prevPage: () => Promise<void>; |
||||
|
nextPage: () => Promise<void>; |
||||
|
zoomIn: () => void; |
||||
|
zoomOut: () => void; |
||||
|
} |
||||
|
|
||||
|
const props = withDefaults(defineProps<PdfPreviewProps>(), { |
||||
|
pdfData: null, |
||||
|
taskId: '', |
||||
|
getPdfStream: undefined |
||||
|
}); |
||||
|
|
||||
|
const emit = defineEmits<{ |
||||
|
pdfLoaded: [data: PdfLoadedData]; |
||||
|
pdfError: [error: any]; |
||||
|
}>(); |
||||
|
|
||||
|
const pdfContainer = ref<HTMLElement | null>(null); |
||||
|
const pdfCanvas = ref<HTMLCanvasElement | null>(null); |
||||
|
let pdfDoc: PDFDocumentProxy | null = null; |
||||
|
const currentPage = ref(1); |
||||
|
const totalPages = ref(0); |
||||
|
const scale = ref(1); |
||||
|
|
||||
|
const showPageSelectModal = ref(false); |
||||
|
const matchedPages = ref<number[]>([]); |
||||
|
const highlightText = ref<string | null>(null); |
||||
|
|
||||
|
// Loading状态 |
||||
|
const loading = ref(false); |
||||
|
const loadingTip = ref(''); |
||||
|
|
||||
|
let currentRenderTask: any = null; // 记录当前渲染任务 |
||||
|
|
||||
|
// 加载PDF文件 |
||||
|
const loadPdf = async (data?: Blob | string) => { |
||||
|
loading.value = true; |
||||
|
loadingTip.value = '正在加载PDF文件...'; |
||||
|
|
||||
|
try { |
||||
|
let pdfBlob: Blob; |
||||
|
|
||||
|
if (data instanceof Blob) { |
||||
|
pdfBlob = data; |
||||
|
} else if (typeof data === 'string' && props.getPdfStream) { |
||||
|
loadingTip.value = '正在获取PDF数据...'; |
||||
|
pdfBlob = await props.getPdfStream(data); |
||||
|
} else if (props.taskId && props.getPdfStream) { |
||||
|
loadingTip.value = '正在获取PDF数据...'; |
||||
|
pdfBlob = await props.getPdfStream(props.taskId); |
||||
|
} else { |
||||
|
throw new Error('无效的PDF数据源'); |
||||
|
} |
||||
|
|
||||
|
loadingTip.value = '正在解析PDF文件...'; |
||||
|
const arrayBuffer = await pdfBlob.arrayBuffer(); |
||||
|
const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer }); |
||||
|
const doc = await loadingTask.promise; |
||||
|
|
||||
|
if (doc) { |
||||
|
pdfDoc = doc; |
||||
|
totalPages.value = doc.numPages; |
||||
|
currentPage.value = 1; |
||||
|
await nextTick(); |
||||
|
|
||||
|
loadingTip.value = '正在渲染第一页...'; |
||||
|
await renderPage(1); |
||||
|
emit('pdfLoaded', { totalPages: doc.numPages }); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('加载PDF失败:', error); |
||||
|
message.error('加载PDF失败'); |
||||
|
emit('pdfError', error); |
||||
|
} finally { |
||||
|
loading.value = false; |
||||
|
loadingTip.value = ''; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 渲染当前页面 |
||||
|
const renderPage = async (num: number, highlightStr?: string) => { |
||||
|
if (!pdfDoc || !pdfCanvas.value || !pdfContainer.value) return; |
||||
|
|
||||
|
try { |
||||
|
const page = await pdfDoc.getPage(num); |
||||
|
const canvas = pdfCanvas.value; |
||||
|
const ctx = canvas.getContext('2d'); |
||||
|
if (!ctx) return; |
||||
|
|
||||
|
// 计算自适应缩放比例,保证页面完整显示 |
||||
|
const containerWidth = pdfContainer.value.clientWidth || 800; |
||||
|
const baseViewport = page.getViewport({ scale: 1 }); |
||||
|
const scaleRatio = ((containerWidth - 48) / baseViewport.width) * scale.value; |
||||
|
const viewport = page.getViewport({ scale: scaleRatio }); |
||||
|
|
||||
|
canvas.width = viewport.width; |
||||
|
canvas.height = viewport.height; |
||||
|
canvas.style.width = `${viewport.width}px`; |
||||
|
canvas.style.height = `${viewport.height}px`; |
||||
|
|
||||
|
// 关键:如果有上一次渲染任务,先取消 |
||||
|
if (currentRenderTask) { |
||||
|
try { currentRenderTask.cancel(); } catch (e) {} |
||||
|
} |
||||
|
|
||||
|
const renderContext = { |
||||
|
canvasContext: ctx, |
||||
|
viewport: viewport, |
||||
|
}; |
||||
|
|
||||
|
currentRenderTask = page.render(renderContext); |
||||
|
await currentRenderTask.promise; |
||||
|
currentRenderTask = null; |
||||
|
|
||||
|
// 高亮文本实现 |
||||
|
if (highlightStr) { |
||||
|
await highlightTextOnPage(page, ctx, viewport, highlightStr); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
currentRenderTask = null; |
||||
|
// 如果是RenderingCancelledException,不提示错误 |
||||
|
if (error && error.name === 'RenderingCancelledException') { |
||||
|
return; |
||||
|
} |
||||
|
console.error('渲染PDF页面失败:', error); |
||||
|
message.error('渲染PDF页面失败'); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 上一页 |
||||
|
const prevPage = async () => { |
||||
|
if (currentPage.value > 1 && !loading.value) { |
||||
|
currentPage.value--; |
||||
|
await renderPage(currentPage.value); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 下一页 |
||||
|
const nextPage = async () => { |
||||
|
if (currentPage.value < totalPages.value && !loading.value) { |
||||
|
currentPage.value++; |
||||
|
await renderPage(currentPage.value); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 放大 |
||||
|
const zoomIn = () => { |
||||
|
if (scale.value < 3 && !loading.value) { |
||||
|
scale.value += 0.1; |
||||
|
renderPage(currentPage.value); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 缩小 |
||||
|
const zoomOut = () => { |
||||
|
if (scale.value > 0.5 && !loading.value) { |
||||
|
scale.value -= 0.1; |
||||
|
renderPage(currentPage.value); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 根据文本定位 |
||||
|
const locateByText = async (text: string) => { |
||||
|
if (!pdfDoc || !text || loading.value) return; |
||||
|
|
||||
|
loading.value = true; |
||||
|
loadingTip.value = '正在搜索文本...'; |
||||
|
|
||||
|
try { |
||||
|
// 去除Markdown格式中的**标记 |
||||
|
const cleanText = text.replace(/\*\*/g, ''); |
||||
|
|
||||
|
const numPages = pdfDoc.numPages; |
||||
|
const foundPages: number[] = []; |
||||
|
|
||||
|
// 搜索所有页面 |
||||
|
for (let i = 1; i <= numPages; i++) { |
||||
|
loadingTip.value = `正在搜索第${i}/${numPages}页...`; |
||||
|
const page = await pdfDoc.getPage(i); |
||||
|
const content = await page.getTextContent(); |
||||
|
const pageText = content.items.map((item: any) => item.str).join(''); |
||||
|
|
||||
|
// 标准化文本,移除所有非文本字符 |
||||
|
const normalizeText = (text: string) => { |
||||
|
return text |
||||
|
.replace(/[\r\n\t\f\v]/g, '') // 移除所有换行和不可见字符 |
||||
|
.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, '') // 只保留中文、英文和数字 |
||||
|
.toLowerCase(); // 转换为小写以进行不区分大小写的比较 |
||||
|
}; |
||||
|
|
||||
|
const normalizedPageText = normalizeText(pageText); |
||||
|
const normalizedSearchText = normalizeText(cleanText); |
||||
|
|
||||
|
if (normalizedPageText.includes(normalizedSearchText)) { |
||||
|
foundPages.push(i); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (foundPages.length === 0) { |
||||
|
message.warning('未在PDF中找到该文本'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 保存要高亮的文本(去除**标记后的) |
||||
|
highlightText.value = cleanText; |
||||
|
|
||||
|
// 如果只找到一个页面,直接跳转并高亮 |
||||
|
if (foundPages.length === 1) { |
||||
|
loadingTip.value = `正在跳转到第${foundPages[0]}页...`; |
||||
|
currentPage.value = foundPages[0]; |
||||
|
await renderPage(foundPages[0], cleanText); |
||||
|
message.success(`已定位到第${foundPages[0]}页`); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 如果找到多个页面,弹出选择对话框 |
||||
|
matchedPages.value = foundPages; |
||||
|
showPageSelectModal.value = true; |
||||
|
} catch (error) { |
||||
|
console.error('文本定位失败:', error); |
||||
|
message.error('文本定位失败'); |
||||
|
} finally { |
||||
|
loading.value = false; |
||||
|
loadingTip.value = ''; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 直接定位到指定页面并高亮文本(无需弹窗选择) |
||||
|
const locateByTextAndPage = async (text: string, pageNumber: number) => { |
||||
|
if (!pdfDoc || !text || loading.value || !pageNumber) return; |
||||
|
|
||||
|
loading.value = true; |
||||
|
loadingTip.value = `正在定位到第${pageNumber}页...`; |
||||
|
|
||||
|
try { |
||||
|
// 去除Markdown格式中的**标记 |
||||
|
const cleanText = text.replace(/\*\*/g, ''); |
||||
|
|
||||
|
// 检查页码是否有效 |
||||
|
if (pageNumber < 1 || pageNumber > totalPages.value) { |
||||
|
message.warning(`无效的页码: ${pageNumber}`); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 直接跳转到指定页面并高亮 |
||||
|
currentPage.value = pageNumber; |
||||
|
await renderPage(pageNumber, cleanText); |
||||
|
message.success(`已定位到第${pageNumber}页`); |
||||
|
} catch (error) { |
||||
|
console.error('页面定位失败:', error); |
||||
|
message.error('页面定位失败'); |
||||
|
} finally { |
||||
|
loading.value = false; |
||||
|
loadingTip.value = ''; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 页面选择处理 |
||||
|
const handlePageSelect = async (page: number) => { |
||||
|
loading.value = true; |
||||
|
loadingTip.value = `正在跳转到第${page}页...`; |
||||
|
|
||||
|
try { |
||||
|
currentPage.value = page; |
||||
|
// 修复类型错误:确保highlightText.value不为null时才传递 |
||||
|
await renderPage(page, highlightText.value || undefined); |
||||
|
showPageSelectModal.value = false; |
||||
|
message.success(`已定位到第${page}页`); |
||||
|
} catch (error) { |
||||
|
console.error('页面跳转失败:', error); |
||||
|
message.error('页面跳转失败'); |
||||
|
} finally { |
||||
|
loading.value = false; |
||||
|
loadingTip.value = ''; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const handlePageModalClose = () => { |
||||
|
showPageSelectModal.value = false; |
||||
|
}; |
||||
|
|
||||
|
// 高亮整段文本 |
||||
|
async function highlightTextOnPage(page: any, ctx: any, viewport: any, targetText: string) { |
||||
|
if (!targetText) return; |
||||
|
|
||||
|
// 确保去除**标记 |
||||
|
const cleanTargetText = targetText.replace(/\*\*/g, ''); |
||||
|
|
||||
|
const textContent = await page.getTextContent(); |
||||
|
const items = textContent.items as any[]; |
||||
|
|
||||
|
// 连接所有文本项,但不移除空格,以保持更好的匹配精度 |
||||
|
const pageTextWithSpaces = items.map((i: any) => i.str).join(''); |
||||
|
const pageText = pageTextWithSpaces.replace(/\s/g, ''); |
||||
|
const target = cleanTargetText.replace(/\s/g, ''); |
||||
|
|
||||
|
// 尝试在页面文本中查找目标文本 |
||||
|
const startIdx = pageText.indexOf(target); |
||||
|
if (startIdx === -1) return; |
||||
|
|
||||
|
// 反查属于哪些item |
||||
|
let charCount = 0; |
||||
|
let highlightItems: any[] = []; |
||||
|
let highlightStarted = false; |
||||
|
let highlightLength = 0; |
||||
|
|
||||
|
for (let item of items) { |
||||
|
const itemTextNoSpace = item.str.replace(/\s/g, ''); |
||||
|
if (!highlightStarted && charCount + itemTextNoSpace.length > startIdx) { |
||||
|
highlightStarted = true; |
||||
|
} |
||||
|
|
||||
|
if (highlightStarted && highlightLength < target.length) { |
||||
|
highlightItems.push(item); |
||||
|
highlightLength += itemTextNoSpace.length; |
||||
|
if (highlightLength >= target.length) break; |
||||
|
} |
||||
|
|
||||
|
charCount += itemTextNoSpace.length; |
||||
|
} |
||||
|
|
||||
|
// 绘制高亮 |
||||
|
ctx.save(); |
||||
|
ctx.globalAlpha = 0.4; |
||||
|
ctx.fillStyle = '#ffd54f'; |
||||
|
|
||||
|
for (let item of highlightItems) { |
||||
|
const transform = item.transform; |
||||
|
const x = transform[4]; // e |
||||
|
const y = transform[5]; // f |
||||
|
|
||||
|
// 通过viewport变换 |
||||
|
const pt = viewport.convertToViewportPoint(x, y); |
||||
|
|
||||
|
// pdf.js的y是基线,需调整 |
||||
|
const height = item.height || item.fontSize || 10; |
||||
|
const width = item.width || (item.str.length * (item.fontSize || 10) * 0.6); |
||||
|
|
||||
|
ctx.fillRect(pt[0], pt[1] - height, width, height + 2); |
||||
|
} |
||||
|
|
||||
|
ctx.restore(); |
||||
|
} |
||||
|
|
||||
|
// 监听props变化 |
||||
|
watch(() => props.pdfData, (newData) => { |
||||
|
if (newData) { |
||||
|
loadPdf(newData); |
||||
|
} |
||||
|
}, { immediate: true }); |
||||
|
|
||||
|
watch(() => props.taskId, (newTaskId) => { |
||||
|
if (newTaskId && props.getPdfStream) { |
||||
|
loadPdf(newTaskId); |
||||
|
} |
||||
|
}, { immediate: true }); |
||||
|
|
||||
|
// 组件卸载前清理资源 |
||||
|
onBeforeUnmount(() => { |
||||
|
if (pdfDoc) { |
||||
|
pdfDoc.destroy(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 暴露方法给父组件 |
||||
|
defineExpose<PdfPreviewInstance>({ |
||||
|
locateByText, |
||||
|
locateByTextAndPage, |
||||
|
loadPdf, |
||||
|
renderPage, |
||||
|
prevPage, |
||||
|
nextPage, |
||||
|
zoomIn, |
||||
|
zoomOut |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
.pdf-preview { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
background: #f5f5f5; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.pdf-container { |
||||
|
flex: 1; |
||||
|
overflow: auto; |
||||
|
display: block; |
||||
|
padding: 24px 0; |
||||
|
background: #f5f5f5; |
||||
|
position: relative; |
||||
|
|
||||
|
canvas { |
||||
|
background: #fff; |
||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); |
||||
|
border-radius: 6px; |
||||
|
display: block; |
||||
|
transition: opacity 0.3s; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.loading-overlay { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
background: rgba(255, 255, 255, 0.8); |
||||
|
z-index: 10; |
||||
|
|
||||
|
:deep(.ant-spin) { |
||||
|
.ant-spin-text { |
||||
|
color: #666; |
||||
|
margin-top: 8px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.pdf-controls { |
||||
|
padding: 8px; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
background: white; |
||||
|
border-top: 1px solid #e8e8e8; |
||||
|
|
||||
|
.page-info { |
||||
|
margin: 0 16px; |
||||
|
color: rgba(0, 0, 0, 0.65); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.ml-2 { |
||||
|
margin-left: 8px; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,247 @@ |
|||||
|
<template> |
||||
|
<div class="review-pdf-container"> |
||||
|
<!-- 单PDF布局 --> |
||||
|
<div v-if="pdfLayout === 'single'" class="single-pdf-layout"> |
||||
|
<PdfPreviewComponent |
||||
|
ref="singlePdfRef" |
||||
|
:taskId="pdfSources[0]?.taskId" |
||||
|
:getPdfStream="getPdfStreamBySource(pdfSources[0]?.id)" |
||||
|
@pdfLoaded="handlePdfLoaded" |
||||
|
@pdfError="handlePdfError" |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 双PDF布局 --> |
||||
|
<div v-else-if="pdfLayout === 'split'" class="split-pdf-layout"> |
||||
|
<!-- 调试信息 --> |
||||
|
<div v-if="pdfSources.length === 0" class="debug-info"> |
||||
|
<p>⚠️ 没有PDF源配置</p> |
||||
|
<p>配置: {{ config }}</p> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 强制显示两个PDF面板用于测试 --> |
||||
|
<div v-if="pdfSources.length < 2" class="debug-info"> |
||||
|
<p>⚠️ PDF源数量不足: {{ pdfSources.length }}/2</p> |
||||
|
<p>当前源: {{ pdfSources.map(s => s.id).join(', ') }}</p> |
||||
|
</div> |
||||
|
|
||||
|
<div class="pdf-panel" v-for="(source, index) in pdfSources" :key="source.id"> |
||||
|
<div class="pdf-panel-header"> |
||||
|
<span class="pdf-title">{{ source.title }} ({{ source.id }})</span> |
||||
|
<!-- 临时调试信息 --> |
||||
|
<span class="debug-text">TaskId: {{ source.taskId }}</span> |
||||
|
</div> |
||||
|
<div class="pdf-panel-content"> |
||||
|
<PdfPreviewComponent |
||||
|
:ref="el => setPdfRef(source.id, el)" |
||||
|
:taskId="source.taskId" |
||||
|
:getPdfStream="getPdfStreamBySource(source.id)" |
||||
|
@pdfLoaded="(data) => handlePdfLoaded(data, source.id)" |
||||
|
@pdfError="(error) => handlePdfError(error, source.id)" |
||||
|
/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 调试信息 --> |
||||
|
<div v-else class="debug-info"> |
||||
|
<p>⚠️ 未知的PDF布局类型: {{ pdfLayout }}</p> |
||||
|
<p>配置: {{ config }}</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import { ref, computed, onMounted } from 'vue'; |
||||
|
import { PdfPreviewComponent } from '@/components/Preview'; |
||||
|
import { getBidPdfStream } from '@/api/contractReview/ContractualTaskResults'; |
||||
|
|
||||
|
interface Props { |
||||
|
config: any; |
||||
|
taskInfo: any; |
||||
|
getPdfStream: (taskId: string) => Promise<Blob>; |
||||
|
} |
||||
|
|
||||
|
const props = defineProps<Props>(); |
||||
|
const emit = defineEmits(['pdfLoaded', 'pdfError']); |
||||
|
|
||||
|
// PDF布局模式 |
||||
|
const pdfLayout = computed(() => props.config.pdfLayout); |
||||
|
|
||||
|
// PDF数据源 |
||||
|
const pdfSources = computed(() => { |
||||
|
const sources = props.config.pdfSources.map(source => ({ |
||||
|
...source, |
||||
|
taskId: props.taskInfo[source.apiField] || props.taskInfo.id |
||||
|
})); |
||||
|
console.log('PDF Layout:', pdfLayout.value); |
||||
|
console.log('PDF Sources:', sources); |
||||
|
console.log('Task Info:', props.taskInfo); |
||||
|
return sources; |
||||
|
}); |
||||
|
|
||||
|
// PDF组件引用 |
||||
|
const singlePdfRef = ref<InstanceType<typeof PdfPreviewComponent> | null>(null); |
||||
|
const pdfRefs = ref<Record<string, InstanceType<typeof PdfPreviewComponent> | null>>({}); |
||||
|
|
||||
|
// 设置PDF组件引用 |
||||
|
const setPdfRef = (sourceId: string, el: any) => { |
||||
|
if (el) { |
||||
|
pdfRefs.value[sourceId] = el; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 根据PDF源获取对应的getPdfStream函数 |
||||
|
const getPdfStreamBySource = (sourceId: string) => { |
||||
|
console.log('Getting PDF stream for source:', sourceId); |
||||
|
if (sourceId === 'bid') { |
||||
|
// 招投标文件使用专门的API |
||||
|
return (taskId: string) => { |
||||
|
console.log('Using getBidPdfStream for taskId:', taskId); |
||||
|
return getBidPdfStream(taskId); |
||||
|
}; |
||||
|
} else { |
||||
|
// 合同文件使用默认的API |
||||
|
return (taskId: string) => { |
||||
|
console.log('Using default getPdfStream for taskId:', taskId); |
||||
|
return props.getPdfStream(taskId); |
||||
|
}; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// PDF加载成功处理 |
||||
|
const handlePdfLoaded = (data: any, sourceId?: string) => { |
||||
|
console.log(`PDF loaded for ${sourceId || 'single'}:`, data); |
||||
|
emit('pdfLoaded', { data, sourceId }); |
||||
|
}; |
||||
|
|
||||
|
// PDF加载失败处理 |
||||
|
const handlePdfError = (error: any, sourceId?: string) => { |
||||
|
console.error(`PDF load error for ${sourceId || 'single'}:`, error); |
||||
|
emit('pdfError', { error, sourceId }); |
||||
|
}; |
||||
|
|
||||
|
// 文本定位方法 |
||||
|
const locateByText = async (text: string, pdfSource?: string) => { |
||||
|
if (pdfLayout.value === 'single') { |
||||
|
// 单PDF布局 |
||||
|
if (singlePdfRef.value) { |
||||
|
await singlePdfRef.value.locateByText(text); |
||||
|
} |
||||
|
} else { |
||||
|
// 双PDF布局 |
||||
|
if (pdfSource && pdfRefs.value[pdfSource]) { |
||||
|
await pdfRefs.value[pdfSource]?.locateByText(text); |
||||
|
} else { |
||||
|
// 如果没有指定PDF源,默认在第一个PDF中定位 |
||||
|
const firstPdfRef = Object.values(pdfRefs.value)[0]; |
||||
|
if (firstPdfRef) { |
||||
|
await firstPdfRef.locateByText(text); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 直接定位到指定页面并高亮文本(无需弹窗选择) |
||||
|
const locateByTextAndPage = async (text: string, page: number, pdfSource?: string) => { |
||||
|
if (pdfLayout.value === 'single') { |
||||
|
// 单PDF布局 |
||||
|
if (singlePdfRef.value) { |
||||
|
await singlePdfRef.value.locateByTextAndPage(text, page); |
||||
|
} |
||||
|
} else { |
||||
|
// 双PDF布局 |
||||
|
if (pdfSource && pdfRefs.value[pdfSource]) { |
||||
|
await pdfRefs.value[pdfSource]?.locateByTextAndPage(text, page); |
||||
|
} else { |
||||
|
// 如果没有指定PDF源,默认在第一个PDF中定位 |
||||
|
const firstPdfRef = Object.values(pdfRefs.value)[0]; |
||||
|
if (firstPdfRef) { |
||||
|
await firstPdfRef.locateByTextAndPage(text, page); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 组件挂载时输出配置信息 |
||||
|
onMounted(() => { |
||||
|
console.log('=== ReviewPdfContainer Debug Info ==='); |
||||
|
console.log('Config:', props.config); |
||||
|
console.log('PDF Layout:', pdfLayout.value); |
||||
|
console.log('PDF Sources Count:', props.config.pdfSources.length); |
||||
|
console.log('PDF Sources:', props.config.pdfSources); |
||||
|
console.log('Task Info:', props.taskInfo); |
||||
|
console.log('Computed PDF Sources:', pdfSources.value); |
||||
|
console.log('====================================='); |
||||
|
}); |
||||
|
|
||||
|
// 暴露方法给父组件 |
||||
|
defineExpose({ |
||||
|
locateByText, |
||||
|
locateByTextAndPage, |
||||
|
pdfRefs, |
||||
|
singlePdfRef |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
.review-pdf-container { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.single-pdf-layout { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.split-pdf-layout { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
} |
||||
|
|
||||
|
.pdf-panel { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
border-right: 1px solid #e8e8e8; |
||||
|
|
||||
|
&:last-child { |
||||
|
border-right: none; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.pdf-panel-header { |
||||
|
padding: 8px 16px; |
||||
|
background: #fafafa; |
||||
|
border-bottom: 1px solid #e8e8e8; |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
|
||||
|
.pdf-title { |
||||
|
font-weight: 500; |
||||
|
color: #333; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
|
||||
|
.pdf-panel-content { |
||||
|
flex: 1; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.debug-info { |
||||
|
padding: 16px; |
||||
|
background: #f0f0f0; |
||||
|
border-top: 1px solid #e8e8e8; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.debug-text { |
||||
|
font-size: 12px; |
||||
|
color: #999; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,2 @@ |
|||||
|
export { default as PdfPreviewComponent } from './PdfPreviewComponent.vue'; |
||||
|
export { default as PageSelectModal } from './PageSelectModal.vue'; |
File diff suppressed because it is too large
@ -0,0 +1,166 @@ |
|||||
|
// 合同审核任务配置文件
|
||||
|
|
||||
|
import type { TaskConfig } from './taskConfigTypes'; |
||||
|
|
||||
|
// 合同审核任务配置
|
||||
|
export const CONTRACT_TASK_CONFIGS: Record<string, TaskConfig> = { |
||||
|
// 合同审核(多标签模式)
|
||||
|
"contractReview": { |
||||
|
taskType: "contractReview", |
||||
|
name: "合同审核", |
||||
|
mode: "tabs", |
||||
|
pdfConfig: { |
||||
|
layout: "split", |
||||
|
sources: [ |
||||
|
{ id: "contract", title: "合同文件", apiField: "id" }, |
||||
|
{ id: "bid", 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: ':' |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
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: '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'], |
||||
|
separator: ':', |
||||
|
fieldProcessors: { |
||||
|
'review_points': (value) => Array.isArray(value) ? value.join('\n') : value |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
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 |
||||
|
}, |
||||
|
// {
|
||||
|
// field: 'modifiedContent',
|
||||
|
// title: '修改建议',
|
||||
|
// dataType: 'string',
|
||||
|
// displayType: 'markdown',
|
||||
|
// showComparison: true,
|
||||
|
// comparisonConfig: {
|
||||
|
// comparisonField: 'modificationDisplay',
|
||||
|
// comparisonTitle: '修改情况展示',
|
||||
|
// showButton: true
|
||||
|
// }
|
||||
|
// }
|
||||
|
] |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 获取合同任务配置
|
||||
|
export function getContractTaskConfig(taskType: string): TaskConfig | null { |
||||
|
return CONTRACT_TASK_CONFIGS[taskType] || null; |
||||
|
} |
||||
|
|
||||
|
// 获取所有合同任务配置
|
||||
|
export function getAllContractTaskConfigs(): TaskConfig[] { |
||||
|
return Object.values(CONTRACT_TASK_CONFIGS); |
||||
|
} |
@ -0,0 +1,229 @@ |
|||||
|
// 文档审核任务配置文件
|
||||
|
|
||||
|
import type { TaskConfig } from './taskConfigTypes'; |
||||
|
|
||||
|
// 文档审核任务配置
|
||||
|
export const DOCUMENT_TASK_CONFIGS: Record<string, TaskConfig> = { |
||||
|
// 文档错误检查
|
||||
|
"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 |
||||
|
} |
||||
|
}, |
||||
|
// {
|
||||
|
// field: 'modificationDisplay',
|
||||
|
// title: '修改情况',
|
||||
|
// dataType: 'string',
|
||||
|
// displayType: 'markdown'
|
||||
|
// }
|
||||
|
] |
||||
|
}, |
||||
|
|
||||
|
// 重复文本检查
|
||||
|
"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', |
||||
|
pdfSource: 'document' |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
|
||||
|
// 地名检查
|
||||
|
"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': (value) => Array.isArray(value) ? value[0] : value |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 获取文档任务配置
|
||||
|
export function getDocumentTaskConfig(taskType: string): TaskConfig | null { |
||||
|
return DOCUMENT_TASK_CONFIGS[taskType] || null; |
||||
|
} |
||||
|
|
||||
|
// 获取所有文档任务配置
|
||||
|
export function getAllDocumentTaskConfigs(): TaskConfig[] { |
||||
|
return Object.values(DOCUMENT_TASK_CONFIGS); |
||||
|
} |
@ -0,0 +1,65 @@ |
|||||
|
// 任务配置类型定义
|
||||
|
|
||||
|
// 基础接口定义
|
||||
|
export interface FieldConfig { |
||||
|
field: string; // 数据字段名
|
||||
|
title: string; // 显示标题
|
||||
|
dataType: 'string' | 'json' | 'number' | 'array'; // 数据类型
|
||||
|
displayType: 'markdown' | 'text' | 'html'; // 前端展示类型
|
||||
|
required?: boolean; |
||||
|
pdfSource?: 'contract' | 'bid' | 'document' | 'auto'; // PDF源
|
||||
|
displayCondition?: (item: any) => boolean; |
||||
|
showComparison?: boolean; // 显示对比按钮
|
||||
|
|
||||
|
// 对比配置
|
||||
|
comparisonConfig?: { |
||||
|
comparisonField: string; // 对比数据字段名,默认为 modificationDisplay
|
||||
|
comparisonTitle?: string; // 对比内容的标题,默认为 '修改情况展示'
|
||||
|
showButton?: boolean; // 是否显示对比按钮,默认为 true
|
||||
|
}; |
||||
|
|
||||
|
// JSON数据类型的配置
|
||||
|
jsonConfig?: { |
||||
|
extractFields: string[]; |
||||
|
separator?: string; |
||||
|
fieldLabels?: Record<string, string>; |
||||
|
fieldProcessors?: Record<string, (value: any) => string>; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export interface PdfSourceConfig { |
||||
|
id: string; |
||||
|
title: string; |
||||
|
apiField: string; |
||||
|
} |
||||
|
|
||||
|
export interface PdfConfig { |
||||
|
layout: 'single' | 'split'; |
||||
|
sources: PdfSourceConfig[]; |
||||
|
} |
||||
|
|
||||
|
export interface TabConfig { |
||||
|
key: string; |
||||
|
label: string; |
||||
|
fields: FieldConfig[]; |
||||
|
displayCondition?: (data: any) => boolean; |
||||
|
pdfConfig?: PdfConfig; // 每个tab可以有自己的PDF配置
|
||||
|
} |
||||
|
|
||||
|
export interface TaskConfig { |
||||
|
taskType: string; // 任务类型标识
|
||||
|
name: string; // 任务显示名称
|
||||
|
mode: 'single' | 'tabs'; // 展示模式:单一内容或多标签
|
||||
|
|
||||
|
// PDF配置
|
||||
|
pdfConfig?: PdfConfig; |
||||
|
|
||||
|
// 单一模式配置
|
||||
|
fields?: FieldConfig[]; |
||||
|
|
||||
|
// 多标签模式配置
|
||||
|
tabs?: TabConfig[]; |
||||
|
|
||||
|
// 全局显示条件
|
||||
|
displayCondition?: (taskInfo: any, data: any) => boolean; |
||||
|
} |
@ -0,0 +1,68 @@ |
|||||
|
// 统一任务配置入口文件
|
||||
|
|
||||
|
import type { TaskConfig, FieldConfig } from './taskConfigTypes'; |
||||
|
import { DOCUMENT_TASK_CONFIGS, getDocumentTaskConfig } from './documentTaskConfigs'; |
||||
|
import { CONTRACT_TASK_CONFIGS, getContractTaskConfig } from './contractTaskConfigs'; |
||||
|
|
||||
|
// 合并所有任务配置
|
||||
|
const ALL_TASK_CONFIGS: Record<string, TaskConfig> = { |
||||
|
...DOCUMENT_TASK_CONFIGS, |
||||
|
...CONTRACT_TASK_CONFIGS |
||||
|
}; |
||||
|
|
||||
|
// 统一的获取任务配置函数
|
||||
|
export function getTaskConfig(taskType: string): TaskConfig | null { |
||||
|
// 首先尝试从文档任务配置中获取
|
||||
|
let config = getDocumentTaskConfig(taskType); |
||||
|
if (config) return config; |
||||
|
|
||||
|
// 然后尝试从合同任务配置中获取
|
||||
|
config = getContractTaskConfig(taskType); |
||||
|
if (config) return config; |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
// 获取所有可用配置
|
||||
|
export function getAvailableConfigs(): TaskConfig[] { |
||||
|
return Object.values(ALL_TASK_CONFIGS); |
||||
|
} |
||||
|
|
||||
|
// 检查字段是否应该显示对比按钮
|
||||
|
export function shouldShowComparison(fieldConfig: FieldConfig, item: any): boolean { |
||||
|
if (!fieldConfig.showComparison) return false; |
||||
|
|
||||
|
// 获取对比字段名,默认为 modificationDisplay
|
||||
|
const comparisonField = fieldConfig.comparisonConfig?.comparisonField || 'modificationDisplay'; |
||||
|
|
||||
|
// 检查对比字段是否有内容
|
||||
|
return !!(item[comparisonField] && item[comparisonField].trim()); |
||||
|
} |
||||
|
|
||||
|
// 获取对比字段名
|
||||
|
export function getComparisonField(fieldConfig: FieldConfig): string { |
||||
|
return fieldConfig.comparisonConfig?.comparisonField || 'modificationDisplay'; |
||||
|
} |
||||
|
|
||||
|
// 获取对比标题
|
||||
|
export function getComparisonTitle(fieldConfig: FieldConfig): string { |
||||
|
return fieldConfig.comparisonConfig?.comparisonTitle || '修改情况展示'; |
||||
|
} |
||||
|
|
||||
|
// 检查是否应该显示对比按钮
|
||||
|
export function shouldShowComparisonButton(fieldConfig: FieldConfig): boolean { |
||||
|
return fieldConfig.comparisonConfig?.showButton !== false; // 默认为true
|
||||
|
} |
||||
|
|
||||
|
// 获取字段的PDF源
|
||||
|
export function getFieldPdfSource(fieldConfig: FieldConfig): string | null { |
||||
|
return fieldConfig.pdfSource || null; |
||||
|
} |
||||
|
|
||||
|
// 检查字段是否支持PDF定位
|
||||
|
export function supportsPdfLocation(fieldConfig: FieldConfig): boolean { |
||||
|
return !!(fieldConfig.pdfSource && fieldConfig.pdfSource !== 'auto'); |
||||
|
} |
||||
|
|
||||
|
// 重新导出类型
|
||||
|
export type { TaskConfig, FieldConfig, TabConfig, PdfSourceConfig } from './taskConfigTypes'; |
@ -0,0 +1,405 @@ |
|||||
|
// 通用任务配置文件
|
||||
|
|
||||
|
// 基础接口定义
|
||||
|
export interface FieldConfig { |
||||
|
field: string; // 数据字段名
|
||||
|
title: string; // 显示标题
|
||||
|
dataType: 'string' | 'json' | 'number' | 'array'; // 数据类型
|
||||
|
displayType: 'markdown' | 'text' | 'html'; // 前端展示类型
|
||||
|
required?: boolean; |
||||
|
pdfSource?: 'contract' | 'bid' | 'document' | 'auto'; // PDF源
|
||||
|
displayCondition?: (item: any) => boolean; |
||||
|
showComparison?: boolean; // 显示对比按钮
|
||||
|
|
||||
|
// JSON数据类型的配置
|
||||
|
jsonConfig?: { |
||||
|
extractFields: string[]; |
||||
|
separator?: string; |
||||
|
fieldLabels?: Record<string, string>; |
||||
|
fieldProcessors?: Record<string, (value: any) => string>; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export interface PdfSourceConfig { |
||||
|
id: string; |
||||
|
title: string; |
||||
|
apiField: string; |
||||
|
} |
||||
|
|
||||
|
export interface TabConfig { |
||||
|
key: string; |
||||
|
label: string; |
||||
|
fields: FieldConfig[]; |
||||
|
displayCondition?: (data: any) => boolean; |
||||
|
} |
||||
|
|
||||
|
export interface TaskConfig { |
||||
|
taskType: string; // 任务类型标识
|
||||
|
name: string; // 任务显示名称
|
||||
|
mode: 'single' | 'tabs'; // 展示模式:单一内容或多标签
|
||||
|
|
||||
|
// PDF配置
|
||||
|
pdfConfig?: { |
||||
|
layout: 'single' | 'split'; |
||||
|
sources: PdfSourceConfig[]; |
||||
|
}; |
||||
|
|
||||
|
// 单一模式配置
|
||||
|
fields?: FieldConfig[]; |
||||
|
|
||||
|
// 多标签模式配置
|
||||
|
tabs?: TabConfig[]; |
||||
|
|
||||
|
// 全局显示条件
|
||||
|
displayCondition?: (taskInfo: any, data: any) => boolean; |
||||
|
} |
||||
|
|
||||
|
// 配置定义
|
||||
|
export const UNIVERSAL_TASK_CONFIGS: Record<string, TaskConfig> = { |
||||
|
// ==================== 文档审核任务配置 ====================
|
||||
|
|
||||
|
// 文档错误检查
|
||||
|
"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 |
||||
|
}, |
||||
|
{ |
||||
|
field: 'modificationDisplay', |
||||
|
title: '修改情况', |
||||
|
dataType: 'string', |
||||
|
displayType: 'markdown' |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
|
||||
|
// 重复文本检查
|
||||
|
"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', |
||||
|
pdfSource: 'document' |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
|
||||
|
// 地名检查
|
||||
|
"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' |
||||
|
}, |
||||
|
{ |
||||
|
field: 'reviewBasis', |
||||
|
title: '系统描述', |
||||
|
dataType: 'json', |
||||
|
displayType: 'markdown', |
||||
|
jsonConfig: { |
||||
|
extractFields: ['reviewPoints', 'review_content'], |
||||
|
separator: ':', |
||||
|
fieldProcessors: { |
||||
|
'reviewPoints': (value) => Array.isArray(value) ? value[0] : value |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
|
||||
|
// ==================== 合同审核任务配置 ====================
|
||||
|
|
||||
|
// 合同审核(多标签模式)
|
||||
|
"contractReview": { |
||||
|
taskType: "contractReview", |
||||
|
name: "合同审核", |
||||
|
mode: "tabs", |
||||
|
pdfConfig: { |
||||
|
layout: "split", |
||||
|
sources: [ |
||||
|
{ id: "contract", title: "合同文件", apiField: "id" }, |
||||
|
{ id: "bid", title: "招标文件", apiField: "id" } |
||||
|
] |
||||
|
}, |
||||
|
tabs: [ |
||||
|
{ |
||||
|
key: "substantive", |
||||
|
label: "实质性审查", |
||||
|
fields: [ |
||||
|
{ |
||||
|
field: 'originalText', |
||||
|
title: '原文', |
||||
|
dataType: 'string', |
||||
|
displayType: 'markdown', |
||||
|
pdfSource: 'contract', |
||||
|
required: true |
||||
|
}, |
||||
|
{ |
||||
|
field: 'modifiedContent', |
||||
|
title: '修改建议', |
||||
|
dataType: 'string', |
||||
|
displayType: 'markdown', |
||||
|
showComparison: true |
||||
|
}, |
||||
|
{ |
||||
|
field: 'reviewBasis', |
||||
|
title: '审查依据', |
||||
|
dataType: 'json', |
||||
|
displayType: 'markdown', |
||||
|
jsonConfig: { |
||||
|
extractFields: ['reviewContent', 'reviewPoints'], |
||||
|
separator: ':' |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
key: "compliance", |
||||
|
label: "合规性审查", |
||||
|
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 |
||||
|
}, |
||||
|
{ |
||||
|
field: 'reviewBasis', |
||||
|
title: '法规依据', |
||||
|
dataType: 'json', |
||||
|
displayType: 'markdown', |
||||
|
jsonConfig: { |
||||
|
extractFields: ['reviewContent', 'reviewPoints'], |
||||
|
separator: ':' |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
key: "consistency", |
||||
|
label: "一致性审查", |
||||
|
fields: [ |
||||
|
{ |
||||
|
field: 'originalText', |
||||
|
title: '合同原文', |
||||
|
dataType: 'string', |
||||
|
displayType: 'markdown', |
||||
|
pdfSource: 'contract', |
||||
|
required: true |
||||
|
}, |
||||
|
{ |
||||
|
field: 'comparedText', |
||||
|
title: '招标文件对应内容', |
||||
|
dataType: 'string', |
||||
|
displayType: 'markdown', |
||||
|
pdfSource: 'bid', |
||||
|
required: true |
||||
|
}, |
||||
|
{ |
||||
|
field: 'modifiedContent', |
||||
|
title: '修改建议', |
||||
|
dataType: 'string', |
||||
|
displayType: 'markdown', |
||||
|
showComparison: true |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 工具函数
|
||||
|
export function getTaskConfig(taskType: string): TaskConfig | null { |
||||
|
return UNIVERSAL_TASK_CONFIGS[taskType] || null; |
||||
|
} |
||||
|
|
||||
|
export function getAvailableConfigs(): TaskConfig[] { |
||||
|
return Object.values(UNIVERSAL_TASK_CONFIGS); |
||||
|
} |
||||
|
|
||||
|
// 检查字段是否应该显示对比按钮
|
||||
|
export function shouldShowComparison(fieldConfig: FieldConfig, item: any): boolean { |
||||
|
return !!(fieldConfig.showComparison && item.modificationDisplay && item.modificationDisplay.trim()); |
||||
|
} |
||||
|
|
||||
|
// 获取字段的PDF源
|
||||
|
export function getFieldPdfSource(fieldConfig: FieldConfig): string | null { |
||||
|
return fieldConfig.pdfSource || null; |
||||
|
} |
||||
|
|
||||
|
// 检查字段是否支持PDF定位
|
||||
|
export function supportsPdfLocation(fieldConfig: FieldConfig): boolean { |
||||
|
return !!(fieldConfig.pdfSource && fieldConfig.pdfSource !== 'auto'); |
||||
|
} |
File diff suppressed because it is too large
@ -0,0 +1,190 @@ |
|||||
|
// 审查字段配置接口
|
||||
|
export interface ReviewFieldConfig { |
||||
|
field: string; |
||||
|
title: string; |
||||
|
type: 'markdown' | 'text' | 'reviewPointsList'; |
||||
|
required?: boolean; |
||||
|
pdfSource?: 'contract' | 'bid' | 'auto'; // 文本定位时使用哪个PDF
|
||||
|
displayCondition?: (item: any) => boolean; |
||||
|
showComparison?: boolean; // 是否显示对比按钮
|
||||
|
nestedFields?: string[]; // 嵌套字段,用于reviewPointsList类型
|
||||
|
} |
||||
|
|
||||
|
// PDF数据源配置接口
|
||||
|
export interface PdfSourceConfig { |
||||
|
id: string; |
||||
|
title: string; |
||||
|
apiField: string; // 对应后端字段名
|
||||
|
} |
||||
|
|
||||
|
// 审查类型配置接口
|
||||
|
export interface ReviewTypeConfig { |
||||
|
type: string; |
||||
|
name: string; |
||||
|
pdfLayout: 'single' | 'split'; |
||||
|
pdfSources: PdfSourceConfig[]; |
||||
|
fields: ReviewFieldConfig[]; |
||||
|
} |
||||
|
|
||||
|
// 审查类型配置定义
|
||||
|
export const REVIEW_TYPE_CONFIGS: Record<string, ReviewTypeConfig> = { |
||||
|
// 实质性审查配置
|
||||
|
substantive: { |
||||
|
type: 'substantive', |
||||
|
name: '实质性审查', |
||||
|
pdfLayout: 'single', |
||||
|
pdfSources: [ |
||||
|
{ id: 'contract', title: '合同文件', apiField: 'id' } |
||||
|
], |
||||
|
fields: [ |
||||
|
{ |
||||
|
field: 'originalText', |
||||
|
title: '原文', |
||||
|
type: 'markdown', |
||||
|
pdfSource: 'contract', |
||||
|
required: true |
||||
|
}, |
||||
|
{ |
||||
|
field: 'modifiedContent', |
||||
|
title: '修改建议', |
||||
|
type: 'markdown', |
||||
|
showComparison: true // 显示对比按钮
|
||||
|
}, |
||||
|
{ |
||||
|
field: 'reviewBasis', |
||||
|
title: '审查依据', |
||||
|
type: 'reviewPointsList', |
||||
|
nestedFields: ['reviewContent', 'reviewPoints'] |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
|
||||
|
// 合规性审查配置
|
||||
|
compliance: { |
||||
|
type: 'compliance', |
||||
|
name: '合规性审查', |
||||
|
pdfLayout: 'single', |
||||
|
pdfSources: [ |
||||
|
{ id: 'contract', title: '合同文件', apiField: 'id' } |
||||
|
], |
||||
|
fields: [ |
||||
|
{ |
||||
|
field: 'originalText', |
||||
|
title: '原文', |
||||
|
type: 'markdown', |
||||
|
pdfSource: 'contract', |
||||
|
required: true |
||||
|
}, |
||||
|
{ |
||||
|
field: 'existingIssues', |
||||
|
title: '合规性问题', |
||||
|
type: 'markdown' |
||||
|
}, |
||||
|
{ |
||||
|
field: 'modifiedContent', |
||||
|
title: '修改建议', |
||||
|
type: 'markdown', |
||||
|
showComparison: true |
||||
|
}, |
||||
|
{ |
||||
|
field: 'reviewBasis', |
||||
|
title: '法规依据', |
||||
|
type: 'reviewPointsList', |
||||
|
nestedFields: ['reviewContent', 'reviewPoints'] |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
|
||||
|
// 一致性审查配置
|
||||
|
consistency: { |
||||
|
type: 'consistency', |
||||
|
name: '一致性审查', |
||||
|
pdfLayout: 'split', |
||||
|
pdfSources: [ |
||||
|
{ id: 'contract', title: '合同文件', apiField: 'id' }, |
||||
|
{ id: 'bid', title: '招标文件', apiField: 'id' } |
||||
|
], |
||||
|
fields: [ |
||||
|
{ |
||||
|
field: 'originalText', |
||||
|
title: '合同原文', |
||||
|
type: 'markdown', |
||||
|
pdfSource: 'contract', |
||||
|
required: true |
||||
|
}, |
||||
|
{ |
||||
|
field: 'comparedText', |
||||
|
title: '招标文件对应内容', |
||||
|
type: 'markdown', |
||||
|
pdfSource: 'bid', |
||||
|
required: true |
||||
|
}, |
||||
|
{ |
||||
|
field: 'modifiedContent', |
||||
|
title: '修改建议', |
||||
|
type: 'markdown', |
||||
|
showComparison: true |
||||
|
}, |
||||
|
// {
|
||||
|
// field: 'existingIssues',
|
||||
|
// title: '不一致问题',
|
||||
|
// type: 'markdown'
|
||||
|
// },
|
||||
|
// {
|
||||
|
// field: 'reviewBasis',
|
||||
|
// title: '审查依据',
|
||||
|
// type: 'reviewPointsList',
|
||||
|
// nestedFields: ['reviewContent', 'reviewPoints']
|
||||
|
// }
|
||||
|
] |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 根据审查类型获取配置的工具函数
|
||||
|
export function getReviewTypeConfig(reviewTypes: string[]): ReviewTypeConfig { |
||||
|
// 优先级:一致性 > 合规性 > 实质性
|
||||
|
if (reviewTypes.includes('consistency')) { |
||||
|
return REVIEW_TYPE_CONFIGS.consistency; |
||||
|
} else if (reviewTypes.includes('compliance')) { |
||||
|
return REVIEW_TYPE_CONFIGS.compliance; |
||||
|
} else { |
||||
|
return REVIEW_TYPE_CONFIGS.substantive; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 根据中文名称获取配置
|
||||
|
export function getReviewTypeConfigByName(chineseName: string): ReviewTypeConfig { |
||||
|
switch (chineseName) { |
||||
|
case '一致性审查': |
||||
|
return REVIEW_TYPE_CONFIGS.consistency; |
||||
|
case '合规性审查': |
||||
|
return REVIEW_TYPE_CONFIGS.compliance; |
||||
|
case '实质性审查': |
||||
|
return REVIEW_TYPE_CONFIGS.substantive; |
||||
|
default: |
||||
|
return REVIEW_TYPE_CONFIGS.substantive; // 默认返回实质性审查
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 获取所有可用的审查类型配置(根据中文名称列表)
|
||||
|
export function getAvailableReviewConfigs(chineseNames: string[]): ReviewTypeConfig[] { |
||||
|
return chineseNames |
||||
|
.filter(name => name !== '全部') // 过滤掉"全部"
|
||||
|
.map(name => getReviewTypeConfigByName(name)); |
||||
|
} |
||||
|
|
||||
|
// 检查字段是否应该显示对比按钮
|
||||
|
export function shouldShowComparison(fieldConfig: ReviewFieldConfig, item: any): boolean { |
||||
|
return !!(fieldConfig.showComparison && item.modificationDisplay && item.modificationDisplay.trim()); |
||||
|
} |
||||
|
|
||||
|
// 获取字段的PDF源
|
||||
|
export function getFieldPdfSource(fieldConfig: ReviewFieldConfig): string | null { |
||||
|
// 只有配置了pdfSource的字段才支持定位
|
||||
|
return fieldConfig.pdfSource || null; |
||||
|
} |
||||
|
|
||||
|
// 检查字段是否支持PDF定位
|
||||
|
export function supportsPdfLocation(fieldConfig: ReviewFieldConfig): boolean { |
||||
|
return !!(fieldConfig.pdfSource && fieldConfig.pdfSource !== 'auto'); |
||||
|
} |
File diff suppressed because it is too large
Loading…
Reference in new issue