|
|
@@ -202,7 +202,7 @@
|
|
|
spellcheck="false"
|
|
|
v-html="docHtml"
|
|
|
@input="onDocInput"
|
|
|
- @click="onDocClick"
|
|
|
+ @mousedown="onDocClick"
|
|
|
ref="docPaperRef"
|
|
|
></div>
|
|
|
|
|
|
@@ -221,6 +221,7 @@
|
|
|
v-if="highlightPopover.visible"
|
|
|
class="element-popover"
|
|
|
:style="{ top: highlightPopover.y + 'px', left: highlightPopover.x + 'px' }"
|
|
|
+ @mousedown.stop
|
|
|
>
|
|
|
<div class="popover-header">
|
|
|
<span class="popover-label">{{ highlightPopover.elementName }}</span>
|
|
|
@@ -240,6 +241,23 @@
|
|
|
<span class="popover-field-label">原始值:</span>
|
|
|
<span class="popover-original">{{ highlightPopover.originalValue }}</span>
|
|
|
</div>
|
|
|
+ <!-- 溯源卡片 -->
|
|
|
+ <div class="popover-rules" v-if="popoverRelatedRules.length > 0">
|
|
|
+ <span class="popover-field-label">来源规则:</span>
|
|
|
+ <div
|
|
|
+ v-for="rule in popoverRelatedRules"
|
|
|
+ :key="rule.id"
|
|
|
+ class="rule-trace-card"
|
|
|
+ >
|
|
|
+ <span class="rule-trace-action" :class="'action-' + rule.actionType">{{ ruleActionLabel(rule.actionType) }}</span>
|
|
|
+ <div class="rule-trace-info">
|
|
|
+ <div class="rule-trace-name">{{ rule.ruleName }}</div>
|
|
|
+ <div v-if="rule.inputs && rule.inputs.length" class="rule-trace-sources">
|
|
|
+ <span v-for="inp in rule.inputs" :key="inp.inputId" class="rule-trace-att">📎 {{ inp.sourceName }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
<div class="popover-footer">
|
|
|
<el-button size="small" @click="highlightPopover.visible = false">关闭</el-button>
|
|
|
@@ -1125,6 +1143,26 @@ function stripValueKeyPrefix(valueElementKey) {
|
|
|
return idx >= 0 ? valueElementKey.substring(idx + 1) : valueElementKey
|
|
|
}
|
|
|
|
|
|
+// 当前弹出框要素关联的规则列表
|
|
|
+const popoverRelatedRules = computed(() => {
|
|
|
+ const key = highlightPopover.elementKey
|
|
|
+ if (!key) return []
|
|
|
+ return rules.value.filter(r => {
|
|
|
+ const rk = stripValueKeyPrefix(r.elementKey)
|
|
|
+ return rk === key
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+function ruleActionLabel(actionType) {
|
|
|
+ const map = {
|
|
|
+ quote: '引用',
|
|
|
+ summary: 'AI总结',
|
|
|
+ table_extract: '表格提取',
|
|
|
+ use_entity_value: '实体值',
|
|
|
+ }
|
|
|
+ return map[actionType] || actionType
|
|
|
+}
|
|
|
+
|
|
|
// 构建要素值映射表,分为长文本、短文本、静态文本三类
|
|
|
function buildElementValueMap() {
|
|
|
const longTexts = [] // paragraph/table 类型的长文本要素
|
|
|
@@ -1741,21 +1779,22 @@ function onDocInput() {
|
|
|
saved.value = false
|
|
|
}
|
|
|
|
|
|
-// 点击文档中的高亮要素
|
|
|
+// 点击文档中的高亮要素(mousedown 比 click 更可靠,在 contenteditable 容器内 e.target 更准确)
|
|
|
function onDocClick(e) {
|
|
|
+ console.log('[onDocClick] target:', e.target.tagName, e.target.className, 'closest:', e.target.closest('.elem-highlight')?.dataset?.elemKey)
|
|
|
const target = e.target.closest('.elem-highlight') || e.target.closest('.elem-highlight-wrap') || e.target.closest('.elem-highlight-table')
|
|
|
if (!target) {
|
|
|
highlightPopover.visible = false
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
const elemKey = target.dataset.elemKey
|
|
|
const valueId = target.dataset.valueId
|
|
|
const elem = elements.value.find(el => el.elementKey === elemKey)
|
|
|
const val = values.value.find(v => String(v.valueId) === String(valueId)) ||
|
|
|
values.value.find(v => stripValueKeyPrefix(v.elementKey) === elemKey)
|
|
|
|
|
|
- if (!elem) return
|
|
|
+ if (!elem || elem.elementType === 'static') return
|
|
|
+ e.preventDefault()
|
|
|
|
|
|
const rect = target.getBoundingClientRect()
|
|
|
const scrollEl = editorRef.value
|
|
|
@@ -3702,6 +3741,7 @@ onMounted(async () => {
|
|
|
overflow-y: auto;
|
|
|
padding: 40px 48px;
|
|
|
background: var(--white);
|
|
|
+ position: relative;
|
|
|
}
|
|
|
|
|
|
.editor-content {
|
|
|
@@ -5678,6 +5718,67 @@ onMounted(async () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ .popover-rules {
|
|
|
+ margin-top: 10px;
|
|
|
+ border-top: 1px dashed var(--border);
|
|
|
+ padding-top: 10px;
|
|
|
+
|
|
|
+ .rule-trace-card {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 6px 8px;
|
|
|
+ background: var(--bg);
|
|
|
+ border-radius: var(--radius-sm);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ margin-bottom: 6px;
|
|
|
+
|
|
|
+ &:last-child { margin-bottom: 0; }
|
|
|
+
|
|
|
+ .rule-trace-action {
|
|
|
+ flex-shrink: 0;
|
|
|
+ font-size: 10px;
|
|
|
+ font-weight: 600;
|
|
|
+ padding: 2px 6px;
|
|
|
+ border-radius: 10px;
|
|
|
+ line-height: 18px;
|
|
|
+
|
|
|
+ &.action-quote { background: #e6f4ff; color: #1677ff; }
|
|
|
+ &.action-summary { background: #f6ffed; color: #52c41a; }
|
|
|
+ &.action-table_extract { background: #fff7e6; color: #fa8c16; }
|
|
|
+ &.action-use_entity_value { background: #f0f0f0; color: #666; }
|
|
|
+ }
|
|
|
+
|
|
|
+ .rule-trace-info {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+
|
|
|
+ .rule-trace-name {
|
|
|
+ font-size: 12px;
|
|
|
+ color: var(--text-1);
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ }
|
|
|
+
|
|
|
+ .rule-trace-sources {
|
|
|
+ margin-top: 3px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 2px;
|
|
|
+
|
|
|
+ .rule-trace-att {
|
|
|
+ font-size: 11px;
|
|
|
+ color: var(--text-3);
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
.popover-footer {
|
|
|
display: flex;
|
|
|
justify-content: flex-end;
|