Browse Source

优化原文定位增加右键定位功能

ai_dev
zhouhaibin 3 days ago
parent
commit
3e859fd9a9
  1. 98
      public/configs/tenderTaskConfigs.json
  2. 226
      src/components/UniversalResultDrawer.vue

98
public/configs/tenderTaskConfigs.json

@ -138,7 +138,7 @@
"tenderComplianceReview": {
"taskType": "tenderComplianceReview",
"name": "招投标文件合规性审核",
"mode": "single",
"mode": "tabs",
"pdfConfig": {
"layout": "single",
"sources": [
@ -149,54 +149,66 @@
}
]
},
"fields": [
{
"field": "issueName",
"title": "合规性分类-具体问题(示例:资质审查-业绩要求)",
"dataType": "string",
"displayType": "markdown",
"pdfSource": "document"
},
{
"field": "originalText",
"title": "原文",
"dataType": "string",
"displayType": "markdown",
"pdfSource": "document"
},
"tabs": [
{
"field": "reviewBasis",
"title": "相关法律法规政策",
"dataType": "json",
"displayType": "markdown",
"jsonConfig": {
"extractFields": [
"review_points"
],
"separator": ":",
"fieldProcessors": {
"review_points": "arrayJoinNewLine"
"key": "存在",
"label": "存在",
"fields": [
{
"field": "originalText",
"title": "原文",
"dataType": "string",
"displayType": "markdown",
"pdfSource": "bid",
"required": true
},
{
"field": "issueName",
"title": "原因",
"dataType": "string",
"displayType": "markdown"
}
}
]
},
{
"field": "modifiedContent",
"title": "修改建议",
"dataType": "string",
"displayType": "markdown",
"pdfSource": "document"
"key": "不存在",
"label": "不存在",
"fields": [
{
"field": "originalText",
"title": "原文",
"dataType": "string",
"displayType": "markdown",
"pdfSource": "bid",
"required": true
},
{
"field": "issueName",
"title": "原因",
"dataType": "string",
"displayType": "markdown"
}
]
},
{
"field": "reviewBasis",
"title": "风险等级(红色预警:直接废标项,橙色风险:可能引发投诉项)",
"dataType": "json",
"displayType": "markdown",
"jsonConfig": {
"extractFields": [
"risk_level"
],
"separator": ":"
}
"key": "无法判断",
"label": "无法判断",
"fields": [
{
"field": "originalText",
"title": "原文",
"dataType": "string",
"displayType": "markdown",
"pdfSource": "bid",
"required": true
},
{
"field": "issueName",
"title": "原因",
"dataType": "string",
"displayType": "markdown"
}
]
}
]
},

226
src/components/UniversalResultDrawer.vue

@ -108,6 +108,7 @@
class="section-content markdown-content"
v-html="renderContent(section, getItemValue(item, section.field))"
@click="locateByText(getItemValue(item, section.field), item, section, categoryIndex, idx)"
@contextmenu="handleContextMenu($event, getItemValue(item, section.field), item, section, categoryIndex, idx)"
></div>
<!-- 页面按钮 - 只在有pdfSource且存在多页时显示 -->
@ -151,6 +152,7 @@
class="section-content markdown-content comparison-content"
v-html="renderMarkdown(getComparisonContent(item, section))"
@click="locateByText(getComparisonContent(item, section), item, section, categoryIndex, idx)"
@contextmenu="handleContextMenu($event, getComparisonContent(item, section), item, section, categoryIndex, idx)"
></div>
</div>
</template>
@ -241,6 +243,7 @@
v-html="renderContent(section, getItemValue(item, section.field))"
v-if="getItemValue(item, section.field)"
@click="locateByText(getItemValue(item, section.field), item, section, index, idx)"
@contextmenu="handleContextMenu($event, getItemValue(item, section.field), item, section, index, idx)"
></div>
<!-- 页面按钮 - 只在有pdfSource且存在多页时显示 -->
@ -285,6 +288,7 @@
class="section-content markdown-content comparison-content"
v-html="renderMarkdown(getComparisonContent(item, section))"
@click="locateByText(getComparisonContent(item, section), item, section, index, idx)"
@contextmenu="handleContextMenu($event, getComparisonContent(item, section), item, section, index, idx)"
></div>
</div>
</template>
@ -320,10 +324,39 @@
</div>
</template>
</Drawer>
<!-- 右键菜单 -->
<div
v-if="contextMenu.visible"
class="context-menu"
:style="{
left: contextMenu.x + 'px',
top: contextMenu.y + 'px',
display: contextMenu.visible ? 'block' : 'none'
}"
@click.stop
>
<div
class="context-menu-item"
@click="locateSelectedText"
v-if="contextMenu.canLocate && contextMenu.selectedText"
>
<span>📍 定位原文</span>
</div>
<div
class="context-menu-item disabled"
v-else
>
<span>📍 定位原文{{ !contextMenu.selectedText ? '(未选中文本)' : '(不支持)' }}</span>
</div>
<div class="context-menu-item" @click="copySelectedText" v-if="contextMenu.selectedText">
<span>📋 复制选中文本</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, watch, nextTick, type PropType } from 'vue';
import { ref, computed, watch, nextTick, onUnmounted, type PropType } from 'vue';
import { Drawer, Button, Card, Switch, Tabs, TabPane } from 'ant-design-vue';
import { DownOutlined, UpOutlined, CopyOutlined, EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
@ -440,6 +473,19 @@
const currentSelectItemIndex = ref<number | null>(null);
const fieldPageButtons = ref<Record<string, number[]>>({});
//
const contextMenu = ref({
visible: false,
x: 0,
y: 0,
selectedText: '',
canLocate: false,
currentItem: null as TaskResultItem | null,
currentFieldConfig: null as FieldConfig | null,
currentCategoryIndex: null as number | null,
currentItemIndex: null as number | null
});
const pdfContainerRef = ref<InstanceType<typeof ReviewPdfContainer> | null>(null);
// PDFtabs
@ -509,14 +555,11 @@
const matches = [
category.name === tabConfig.label,
category.name === tabConfig.key,
category.name.includes(tabConfig.label),
tabConfig.label.includes(category.name),
// 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);
@ -1202,6 +1245,32 @@
}
});
//
const handleGlobalClick = (event: Event) => {
//
const target = event.target as HTMLElement;
if (!target.closest('.context-menu')) {
hideContextMenu();
}
};
//
watch(() => contextMenu.value.visible, (visible) => {
if (visible) {
document.addEventListener('click', handleGlobalClick);
document.addEventListener('contextmenu', handleGlobalClick);
} else {
document.removeEventListener('click', handleGlobalClick);
document.removeEventListener('contextmenu', handleGlobalClick);
}
});
//
onUnmounted(() => {
document.removeEventListener('click', handleGlobalClick);
document.removeEventListener('contextmenu', handleGlobalClick);
});
watch([expandReadItems, expandAdoptedItems], () => {
updateActiveItemKeys();
});
@ -1231,6 +1300,93 @@
const comparisonField = getComparisonField(fieldConfig);
return item[comparisonField] || '';
};
//
const locateSelectedText = async () => {
if (!contextMenu.value.selectedText || !contextMenu.value.canLocate) {
return;
}
const { selectedText, currentItem, currentFieldConfig, currentCategoryIndex, currentItemIndex } = contextMenu.value;
//
contextMenu.value.visible = false;
//
await locateByText(
selectedText,
currentItem || undefined,
currentFieldConfig || undefined,
currentCategoryIndex || undefined,
currentItemIndex || undefined
);
};
//
const handleContextMenu = (event: MouseEvent, fieldValue: any, item: TaskResultItem, fieldConfig: FieldConfig, categoryIndex: number, itemIndex: number) => {
console.log('右键菜单触发', { event, fieldValue, item, fieldConfig, categoryIndex, itemIndex });
event.preventDefault();
event.stopPropagation();
//
const selection = window.getSelection();
const selectedText = selection?.toString().trim();
console.log('选中的文本:', selectedText);
// 使
if (!selectedText) {
console.log('没有选中文本,显示禁用菜单');
contextMenu.value = {
visible: true,
x: event.clientX,
y: event.clientY,
selectedText: '',
canLocate: false,
currentItem: item,
currentFieldConfig: fieldConfig,
currentCategoryIndex: categoryIndex,
currentItemIndex: itemIndex
};
return;
}
// PDF
const canLocate = supportsPdfLocation(fieldConfig);
//
contextMenu.value = {
visible: true,
x: event.clientX,
y: event.clientY,
selectedText: selectedText,
canLocate: canLocate,
currentItem: item,
currentFieldConfig: fieldConfig,
currentCategoryIndex: categoryIndex,
currentItemIndex: itemIndex
};
};
//
const hideContextMenu = () => {
contextMenu.value.visible = false;
};
//
const copySelectedText = () => {
if (contextMenu.value.selectedText) {
navigator.clipboard.writeText(contextMenu.value.selectedText)
.then(() => {
message.success('选中文本已复制到剪贴板');
})
.catch((err) => {
console.error('复制失败:', err);
message.error('复制失败');
});
}
contextMenu.value.visible = false;
};
</script>
<style lang="less" scoped>
@ -1393,6 +1549,18 @@
overflow: auto;
font-size: 18px;
font-weight: 500;
user-select: text; /* 允许文本选择 */
/* 选中文本的样式 */
::selection {
background-color: #bae7ff;
color: #1890ff;
}
::-moz-selection {
background-color: #bae7ff;
color: #1890ff;
}
}
.comparison-section {
@ -1567,4 +1735,48 @@
display: flex;
flex-direction: column;
}
/* 右键菜单样式 */
.context-menu {
position: fixed;
background: #ffffff;
border: 1px solid #d9d9d9;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 4px 0;
min-width: 140px;
z-index: 9999;
user-select: none;
}
.context-menu-item {
padding: 8px 16px;
cursor: pointer;
font-size: 14px;
color: #262626;
display: flex;
align-items: center;
transition: all 0.2s;
&:hover {
background-color: #f5f5f5;
color: #1890ff;
}
&.disabled {
color: #bfbfbf;
cursor: not-allowed;
&:hover {
background-color: transparent;
color: #bfbfbf;
}
}
span {
display: flex;
align-items: center;
gap: 8px;
}
}
</style>
Loading…
Cancel
Save