tangweijie 79cfcf9c6d feat(report): 更新评估报告前端组件和 API
- 优化 DimensionAnalysisPanel 维度分析面板
- 更新 LlmResultPanel LLM 结果展示组件
- 完善 PromptEditor 提示词编辑器功能
- 改进 ReportForm 报告表单交互
- 优化 ReportEditDrawer 报告编辑抽屉
- 调整 prisoner 页面显示
- 更新 evaluation-report 和 report API

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-20 12:13:13 +08:00

291 lines
7.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="prompt-editor">
<div class="prompt-toggle">
<div class="toggle-title">
<el-icon><Document /></el-icon>
<span>提示词与变量</span>
</div>
<el-button link type="primary" @click="showPromptDetails = !showPromptDetails">
{{ showPromptDetails ? '收起' : '展开' }}
</el-button>
</div>
<div v-show="showPromptDetails">
<!-- 系统提示词 -->
<div class="prompt-section">
<div class="section-header">
<div class="header-left">
<el-icon><Document /></el-icon>
<span>系统提示词</span>
<el-tag size="small" type="info">只读</el-tag>
</div>
</div>
<el-input
type="textarea"
v-model="systemPrompt"
:rows="4"
readonly
placeholder="系统提示词由维度配置自动生成"
/>
</div>
<!-- 用户提示词 -->
<div class="prompt-section">
<div class="section-header">
<div class="header-left">
<el-icon><Edit /></el-icon>
<span>自定义提示词</span>
</div>
<div class="header-actions">
<el-button link type="primary" @click="resetToDefault">
<el-icon><Refresh /></el-icon>
恢复默认
</el-button>
</div>
</div>
<el-input
type="textarea"
v-model="customPrompt"
:rows="4"
placeholder="可在此处添加自定义提示词指导AI生成更符合要求的评估内容"
/>
</div>
<!-- 变量预览 -->
<div class="prompt-section">
<div class="section-header">
<div class="header-left">
<el-icon><InfoFilled /></el-icon>
<span>变量预览</span>
</div>
<div class="header-actions">
<el-button link type="primary" @click="showVariableHelp = true">
<el-icon><QuestionFilled /></el-icon>
帮助
</el-button>
</div>
</div>
<div class="variable-preview">
<el-tag
v-for="variable in variables"
:key="variable.key"
class="variable-tag"
@click="insertVariable(variable.key)"
>
<code>{{ getVariableDisplay(variable.key) }}</code>
</el-tag>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="action-buttons">
<el-button
type="primary"
:loading="generating"
@click="handleGenerate"
>
<el-icon><MagicStick /></el-icon>
AI自动生成
</el-button>
<el-button
:disabled="!generatedContent"
@click="handleRegenerate"
>
<el-icon><Refresh /></el-icon>
重新生成
</el-button>
</div>
<!-- 变量说明对话框 -->
<el-dialog
v-model="showVariableHelp"
title="提示词变量说明"
width="500px"
>
<el-table :data="variables" stripe>
<el-table-column prop="key" label="变量" width="120">
<template #default="{ row }">
<code>{{ getVariableDisplay(row.key) }}</code>
</template>
</el-table-column>
<el-table-column prop="desc" label="说明" />
</el-table>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { Document, Edit, Refresh, InfoFilled, QuestionFilled, MagicStick } from '@element-plus/icons-vue'
import { DimensionVO } from '@/api/prison/evaluation-report'
import { computed } from 'vue'
defineOptions({ name: 'PromptEditor' })
const props = defineProps<{
dimension: DimensionVO | undefined
prisoner?: { name?: string; prisonerNo?: string }
generating?: boolean
generatedContent?: string
}>()
const emit = defineEmits<{
(e: 'generate', customPrompt?: string, systemPrompt?: string): void
(e: 'regenerate', customPrompt?: string, systemPrompt?: string): void
}>()
// 生成状态(优先使用 props其次从注入获取
const injectedGenerating = inject<Ref<boolean>>('generating', ref(false))
const injectedGeneratedContent = inject<Ref<string>>('generatedContent', ref(''))
const generating = computed(() => props.generating ?? injectedGenerating.value)
const generatedContent = computed(() => props.generatedContent ?? injectedGeneratedContent.value)
// 系统提示词
const systemPrompt = computed(() => {
if (!props.dimension) return ''
return `你是一位专业的监狱评估专家,负责撰写服刑人员的评估报告。
当前评估维度:${props.dimension.name}
评估要求:
${props.dimension.aiPrompt || `根据服刑人员的基本信息和相关数据,撰写关于「${props.dimension.name}」的客观评估。`}
请直接返回评估内容,格式为段落文本。`
})
// 用户自定义提示词
const customPrompt = ref('')
// 变量列表
const variables = [
{ key: 'prisoner.name', desc: '罪犯姓名' },
{ key: 'prisoner.no', desc: '罪犯编号' },
{ key: 'dimension.name', desc: '维度名称' },
{ key: 'dimension.desc', desc: '维度描述' },
{ key: 'data.summary', desc: '数据汇总' }
]
const showPromptDetails = ref(false)
// 显示变量帮助
const showVariableHelp = ref(false)
// 恢复默认提示词
const resetToDefault = () => {
customPrompt.value = ''
}
// 获取变量显示格式
const getVariableDisplay = (key: string) => {
return `{{${key}}}`
}
// 插入变量
const insertVariable = (key: string) => {
const textarea = document.querySelector('.prompt-section:nth-child(2) textarea') as HTMLTextAreaElement
if (textarea) {
const start = textarea.selectionStart
const end = textarea.selectionEnd
const text = customPrompt.value
const variable = getVariableDisplay(key)
customPrompt.value = text.substring(0, start) + variable + text.substring(end)
textarea.focus()
textarea.setSelectionRange(start + variable.length, start + variable.length)
}
}
// 生成
const handleGenerate = () => {
emit('generate', customPrompt.value || undefined, systemPrompt.value || undefined)
}
// 重新生成
const handleRegenerate = () => {
emit('regenerate', customPrompt.value || undefined, systemPrompt.value || undefined)
}
// 获取自定义提示词(供父组件调用)
const getCustomPrompt = () => {
return customPrompt.value || undefined
}
defineExpose({ getCustomPrompt })
</script>
<style lang="scss" scoped>
.prompt-editor {
padding: 16px;
background: var(--el-bg-color);
border-bottom: 1px solid var(--el-border-color);
}
.prompt-toggle {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 8px;
margin-bottom: 12px;
border-bottom: 1px dashed var(--el-border-color);
.toggle-title {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
.prompt-section {
margin-bottom: 16px;
&:last-of-type {
margin-bottom: 20px;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 8px;
font-weight: 500;
color: var(--el-text-color-primary);
}
.header-left {
display: flex;
align-items: center;
gap: 8px;
}
.header-actions {
display: flex;
align-items: center;
gap: 8px;
}
}
.variable-preview {
display: flex;
flex-wrap: wrap;
gap: 8px;
.variable-tag {
cursor: pointer;
transition: all 0.2s;
&:hover {
transform: scale(1.05);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
}
}
.action-buttons {
display: flex;
gap: 12px;
padding-top: 8px;
}
</style>