AI审核支持微调/自定义审核重点

- 新增审核可选择审核重点(字典驱动,合同/简历各一套)并填写附加要求自由文本,
  两者合并为 requirements 随请求提交,后端追加进系统提示词,让模型按需聚焦
- 审核项存字典 oa_ai_review_item_contract / oa_ai_review_item_resume,
  用户可在系统管理→字典管理自行增删审核项(无需改代码),各预置10项
- oa_ai_review 增加 requirements 列(已应用到生产库),落库留痕;详情页展示
- 前后端贯通:analyze / analyzeStream 均新增 requirements 参数

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 14:35:27 +08:00
parent a4f479454f
commit 9e6ae1eca9
8 changed files with 125 additions and 17 deletions

View File

@@ -30,6 +30,23 @@
: '评估候选人,分析与目标岗位的匹配度、优势、短板与面试建议。' }}
支持 PDF / Word(.doc/.docx) 20MB
</div>
<!-- 审核重点 / 附加要求 -->
<div class="req-area">
<div class="req-line">
<span class="req-label">审核重点</span>
<el-checkbox-group v-model="checkedItems" size="mini" :disabled="streaming" class="req-items">
<el-checkbox v-for="it in itemOptions" :key="it.value" :label="it.label" border>{{ it.label }}</el-checkbox>
</el-checkbox-group>
<span class="req-tip">可在系统管理字典管理增删审核项</span>
</div>
<div class="req-line">
<span class="req-label">附加要求</span>
<el-input v-model="extraText" type="textarea" :rows="2" :disabled="streaming"
class="req-extra" maxlength="500" show-word-limit
placeholder="可补充本次审核的特殊关注点,例如:重点核查付款比例是否对我方有利、是否有自动续约陷阱…" />
</div>
</div>
</el-card>
<el-row :gutter="12" class="body">
@@ -96,6 +113,7 @@
<script>
import { getToken } from '@/utils/auth'
import { getDicts } from '@/api/system/dict/data'
const marked = require('marked')
export default {
@@ -107,6 +125,11 @@ export default {
rawFile: null,
fileName: '',
// 审核重点(字典) + 附加要求(自由文本)
itemDicts: { contract: [], resume: [] },
checkedItems: [],
extraText: '',
streaming: false,
done: false,
reasoning: '',
@@ -124,15 +147,37 @@ export default {
computed: {
renderedMd () {
try { return marked(this.content) } catch (e) { return this.content }
},
itemOptions () {
return this.itemDicts[this.reviewType] || []
},
requirements () {
const parts = []
if (this.checkedItems.length) parts.push('重点审核项:' + this.checkedItems.join('、'))
if (this.extraText && this.extraText.trim()) parts.push('其他要求:' + this.extraText.trim())
return parts.join('\n')
}
},
watch: {
// 切换类型时清空已选审核项(两套字典不同)
reviewType () { this.checkedItems = [] }
},
created () {
marked.setOptions({ breaks: true })
this.loadItemDicts()
},
beforeDestroy () {
if (this.previewUrl) URL.revokeObjectURL(this.previewUrl)
},
methods: {
loadItemDicts () {
getDicts('oa_ai_review_item_contract').then(res => {
this.itemDicts = { ...this.itemDicts, contract: (res.data || []).map(d => ({ label: d.dictLabel, value: d.dictValue })) }
})
getDicts('oa_ai_review_item_resume').then(res => {
this.itemDicts = { ...this.itemDicts, resume: (res.data || []).map(d => ({ label: d.dictLabel, value: d.dictValue })) }
})
},
goBack () { this.$router.push('/hint/aiReview') },
goDetail () { if (this.savedId) this.$router.push('/hint/aiReview/detail/' + this.savedId) },
riskTagType (r) { return r === '高' ? 'danger' : (r === '中' ? 'warning' : 'success') },
@@ -167,6 +212,7 @@ export default {
fd.append('file', this.rawFile)
fd.append('reviewType', this.reviewType)
if (this.reviewType === 'resume' && this.position) fd.append('position', this.position)
if (this.requirements) fd.append('requirements', this.requirements)
try {
const resp = await fetch(process.env.VUE_APP_BASE_API + '/oa/aiReview/analyzeStream', {
@@ -264,6 +310,18 @@ export default {
}
.bar-hint { font-size: 12px; color: #909399; margin-top: 8px; }
.req-area { margin-top: 10px; border-top: 1px dashed #ebeef5; padding-top: 8px; }
.req-line { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 8px;
&:last-child { margin-bottom: 0; }
}
.req-label { flex: 0 0 56px; font-size: 12px; color: #606266; padding-top: 5px; font-weight: 600; }
.req-items { flex: 1; display: flex; flex-wrap: wrap; gap: 6px 0;
::v-deep .el-checkbox { margin-right: 8px; margin-left: 0; }
::v-deep .el-checkbox.is-bordered { padding: 4px 10px 4px 8px; height: auto; }
}
.req-tip { flex: 0 0 auto; font-size: 11px; color: #c0c4cc; padding-top: 5px; }
.req-extra { flex: 1; }
.body { margin-top: 0; }
.panel { ::v-deep .el-card__header { padding: 9px 14px; } }
.hd { display: flex; justify-content: space-between; align-items: center;

View File

@@ -29,6 +29,9 @@
<el-descriptions-item label="模型">{{ info.model || '—' }}</el-descriptions-item>
<el-descriptions-item label="审核时间">{{ info.createTime }}</el-descriptions-item>
<el-descriptions-item label="审核人">{{ info.createBy || '—' }}</el-descriptions-item>
<el-descriptions-item v-if="info.requirements" label="审核重点 / 附加要求" :span="3">
<span style="white-space: pre-wrap">{{ info.requirements }}</span>
</el-descriptions-item>
</el-descriptions>
<div v-if="info" class="md-body" v-html="renderedMd" />