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

340 lines
12 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>
<ContentWrap>
<el-row :gutter="20">
<!-- 左侧模板列表 -->
<el-col :span="8">
<ContentWrap title="模板列表" class="!mb-0">
<template #header>
<el-button type="primary" @click="openTemplateForm('create')" v-hasPermi="['prison:evaluation-report:template:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新建模板
</el-button>
</template>
<el-input v-model="templateFilter" placeholder="搜索模板" class="!mb-15px" clearable />
<div class="template-list">
<el-card
v-for="template in filteredTemplateList"
:key="template.id"
class="template-card mb-10px cursor-pointer"
:class="{ 'template-card-active': selectedTemplate?.id === template.id }"
shadow="hover"
@click="selectTemplate(template)"
>
<div class="template-card-header">
<span class="template-name">{{ template.name }}</span>
<el-tag :type="template.status === 1 ? 'success' : 'info'" size="small">
{{ template.status === 1 ? '启用' : '停用' }}
</el-tag>
</div>
<div class="template-info">
<span class="text-gray-500 text-12px">
{{ getDictLabel(DICT_TYPE.PRISON_REPORT_TEMPLATE_TYPE, template.type) }}
</span>
<span class="text-gray-500 text-12px">
{{ getDictLabel(DICT_TYPE.PRISON_EVALUATION_CYCLE, template.evaluationCycle) }}
</span>
</div>
<div class="template-actions">
<el-button type="primary" link size="small" @click.stop="openTemplateForm('update', template.id)">
编辑
</el-button>
<el-button type="success" link size="small" @click.stop="copyTemplate(template)">
复制
</el-button>
<el-button type="danger" link size="small" @click.stop="handleDeleteTemplate(template.id!)" v-hasPermi="['prison:evaluation-report:template:delete']">
删除
</el-button>
</div>
</el-card>
</div>
<el-empty v-if="templateList.length === 0" description="暂无模板" />
</ContentWrap>
</el-col>
<!-- 右侧:模板详情和维度配置 -->
<el-col :span="16">
<ContentWrap v-if="selectedTemplate" class="!mb-0">
<template #header>
<span class="text-16px font-bold">模板详情</span>
</template>
<el-tabs v-model="activeTab">
<!-- 基本信息 Tab -->
<el-tab-pane label="基本信息" name="basic">
<el-descriptions :column="2" border>
<el-descriptions-item label="模板名称">{{ selectedTemplate.name }}</el-descriptions-item>
<el-descriptions-item label="模板编码">{{ selectedTemplate.code }}</el-descriptions-item>
<el-descriptions-item label="模板类型">
{{ getDictLabel(DICT_TYPE.PRISON_REPORT_TEMPLATE_TYPE, selectedTemplate.type) }}
</el-descriptions-item>
<el-descriptions-item label="评估周期">
{{ getDictLabel(DICT_TYPE.PRISON_EVALUATION_CYCLE, selectedTemplate.evaluationCycle) }}
</el-descriptions-item>
<el-descriptions-item label="适用人群">{{ selectedTemplate.applicableCrowd || '-' }}</el-descriptions-item>
<el-descriptions-item label="AI生成">
<el-tag :type="selectedTemplate.aiEnabled === 1 ? 'success' : 'info'" size="small">
{{ selectedTemplate.aiEnabled === 1 ? '启用' : '禁用' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="排序" :span="2">{{ selectedTemplate.sort }}</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">{{ selectedTemplate.description || '-' }}</el-descriptions-item>
<el-descriptions-item label="AI提示词" :span="2">
<el-input type="textarea" :rows="3" :value="selectedTemplate.aiPrompt" readonly />
</el-descriptions-item>
</el-descriptions>
<div class="mt-15px text-right">
<el-button type="primary" @click="openTemplateForm('update', selectedTemplate.id)">
<Icon icon="ep:edit" class="mr-5px" /> 编辑基本信息
</el-button>
</div>
</el-tab-pane>
<!-- 维度配置 Tab -->
<el-tab-pane label="维度配置" name="dimension">
<div class="dimension-header mb-15px">
<el-button type="primary" @click="openDimensionForm(selectedTemplate.id!)" v-hasPermi="['prison:evaluation-report:dimension:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新增维度
</el-button>
</div>
<div class="dimension-list">
<el-card
v-for="(dimension, index) in dimensionList"
:key="dimension.id"
class="dimension-card mb-10px"
shadow="hover"
>
<div class="dimension-header">
<div class="dimension-title">
<span class="dimension-sort">{{ index + 1 }}</span>
<span class="dimension-name">{{ dimension.name }}</span>
<el-tag v-if="dimension.aiEnabled === 1" type="success" size="small">AI生成</el-tag>
</div>
<div class="dimension-actions">
<el-button type="primary" link size="small" @click="openDimensionForm(selectedTemplate.id!, dimension.id)">
编辑
</el-button>
<el-button type="danger" link size="small" @click="handleDeleteDimension(dimension.id!)" v-hasPermi="['prison:evaluation-report:dimension:delete']">
删除
</el-button>
</div>
</div>
<div class="dimension-info">
<el-tag>{{ getDictLabel(DICT_TYPE.PRISON_EVALUATION_DIMENSION_TYPE, dimension.dimensionType) }}</el-tag>
<el-tag>{{ getDictLabel(DICT_TYPE.PRISON_EVALUATION_OUTPUT_FORMAT, dimension.outputFormat) }}</el-tag>
<el-tag>{{ getDictLabel(DICT_TYPE.PRISON_EVALUATION_METHOD, dimension.evaluationMethod) }}</el-tag>
</div>
<div class="dimension-desc text-gray-500 text-12px mt-5px" v-if="dimension.description">
{{ dimension.description }}
</div>
</el-card>
<el-empty v-if="dimensionList.length === 0" description="暂无维度配置,请添加维度" />
</div>
</el-tab-pane>
</el-tabs>
</ContentWrap>
<ContentWrap v-else class="!mb-0">
<el-empty description="请选择左侧模板查看详情" />
</ContentWrap>
</el-col>
</el-row>
</ContentWrap>
<!-- 模板表单弹窗 -->
<TemplateForm ref="templateFormRef" @success="getTemplateList" />
<!-- 维度表单弹窗 -->
<DimensionForm ref="dimensionFormRef" @success="getDimensionsByTemplateId" />
</template>
<script setup lang="ts">
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
import { TemplateApi, TemplateVO, DimensionApi, DimensionVO } from '@/api/prison/evaluation-report'
import TemplateForm from './TemplateForm.vue'
import DimensionForm from './DimensionForm.vue'
defineOptions({ name: 'EvaluationTemplate' })
const { t } = useI18n()
const message = useMessage()
const loading = ref(true)
const templateList = ref<TemplateVO[]>([])
const templateFilter = ref('')
const selectedTemplate = ref<TemplateVO | null>(null)
const activeTab = ref('basic')
const dimensionList = ref<DimensionVO[]>([])
const templateFormRef = ref()
const dimensionFormRef = ref()
/** 获取模板列表 */
const getTemplateList = async () => {
loading.value = true
try {
const data = await TemplateApi.getTemplatePage({ pageNo: 1, pageSize: 100 })
templateList.value = data.list
} finally {
loading.value = false
}
}
/** 过滤后的模板列表 */
const filteredTemplateList = computed(() => {
if (!templateFilter.value) return templateList.value
return templateList.value.filter(item =>
item.name?.includes(templateFilter.value) ||
item.code?.includes(templateFilter.value)
)
})
/** 选择模板 */
const selectTemplate = async (template: TemplateVO) => {
selectedTemplate.value = template
activeTab.value = 'basic'
await getDimensionsByTemplateId(template.id!)
}
/** 根据模板ID获取维度列表 */
const getDimensionsByTemplateId = async (templateId: number) => {
if (!templateId) return
dimensionList.value = await DimensionApi.getDimensionsByTemplateId(templateId)
}
/** 打开模板表单 */
const openTemplateForm = (type: string, id?: number) => {
templateFormRef.value.open(type, id)
}
/** 打开维度表单 */
const openDimensionForm = (templateId: number, id?: number) => {
dimensionFormRef.value.open('create', templateId, id)
}
/** 复制模板 */
const copyTemplate = async (template: TemplateVO) => {
try {
await message.confirm('确定要复制该模板吗?')
const newTemplate: TemplateVO = {
...template,
id: undefined,
name: `${template.name}_副本`,
code: `${template.code}_copy`,
status: 0 // 复制后默认停用
}
delete newTemplate.id
await TemplateApi.createTemplate(newTemplate)
message.success('复制成功')
getTemplateList()
} catch {}
}
/** 删除模板 */
const handleDeleteTemplate = async (id: number) => {
try {
await message.delConfirm()
await TemplateApi.deleteTemplate(id)
message.success(t('common.delSuccess'))
if (selectedTemplate.value?.id === id) {
selectedTemplate.value = null
dimensionList.value = []
}
getTemplateList()
} catch {}
}
/** 删除维度 */
const handleDeleteDimension = async (id: number) => {
try {
await message.delConfirm()
await DimensionApi.deleteDimension(id)
message.success(t('common.delSuccess'))
getDimensionsByTemplateId(selectedTemplate.value?.id)
} catch {}
}
/** 初始化 */
onMounted(() => {
getTemplateList()
})
</script>
<style lang="scss" scoped>
.template-list {
max-height: calc(100vh - 280px);
overflow-y: auto;
}
.template-card {
transition: all 0.3s;
&.template-card-active {
border-color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9);
}
.template-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.template-name {
font-weight: 500;
font-size: 14px;
}
.template-info {
display: flex;
gap: 10px;
margin-bottom: 8px;
}
.template-actions {
border-top: 1px solid var(--el-border-color-lighter);
padding-top: 8px;
}
}
.dimension-list {
max-height: calc(100vh - 350px);
overflow-y: auto;
}
.dimension-card {
.dimension-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.dimension-title {
display: flex;
align-items: center;
gap: 10px;
}
.dimension-sort {
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
background-color: var(--el-color-primary);
color: #fff;
border-radius: 50%;
font-size: 12px;
}
.dimension-name {
font-weight: 500;
}
.dimension-info {
display: flex;
gap: 8px;
}
.dimension-desc {
line-height: 1.5;
}
}
</style>