1380 lines
41 KiB
Vue
1380 lines
41 KiB
Vue
<template>
|
||
<el-card class="analysis-rule-container">
|
||
<!-- 返回按钮和保存按钮 -->
|
||
<div class="back-container">
|
||
<el-button
|
||
type="primary"
|
||
@click="handleSaveRules"
|
||
:disabled="!selectedLabelItemId ||
|
||
(currentTab === 'ruleConfig' && (ruleSets.length === 0 || contentSources.length === 0)) ||
|
||
(currentTab === 'promptConfig' && !promptWord.trim())"
|
||
class="save-btn"
|
||
>
|
||
保存
|
||
</el-button>
|
||
<el-button
|
||
type="default"
|
||
size="small"
|
||
@click="handleBack"
|
||
class="back-btn">
|
||
返回
|
||
</el-button>
|
||
</div>
|
||
|
||
<el-row :gutter="20">
|
||
<!-- 左侧树状图区域 -->
|
||
<el-col :span="4">
|
||
<div class="tree-container" style="height: calc(100vh - 205px)">
|
||
<el-input
|
||
placeholder="搜索标签项"
|
||
v-model="treeSearchKey"
|
||
class="tree-search"
|
||
clearable
|
||
size="small"
|
||
/>
|
||
|
||
<el-tree
|
||
ref="labelItemTree"
|
||
:data="treeData"
|
||
:props="treeProps"
|
||
node-key="analysisLabelItemId"
|
||
:filter-node-method="filterTreeNode"
|
||
:highlight-current="true"
|
||
default-expand-all
|
||
@node-click="handleTreeNodeClick"
|
||
@current-change="handleTreeNodeChange"
|
||
>
|
||
<template #default="{ node, data }">
|
||
<span class="tree-node-content">
|
||
<span
|
||
:style="{
|
||
color: data.analysisLevel === 3 ? '#1F72EA' : '#333',
|
||
fontWeight: data.analysisLevel === 3 ? '500' : 'normal'
|
||
}"
|
||
>
|
||
{{ node.label }}
|
||
</span>
|
||
</span>
|
||
</template>
|
||
</el-tree>
|
||
</div>
|
||
</el-col>
|
||
|
||
<!-- 右侧解析规则配置区域 -->
|
||
<el-col :span="13">
|
||
<div class="rule-config-container">
|
||
<!-- 标签切换 -->
|
||
<el-tabs v-model="currentTab" @tab-click="handleTabClick">
|
||
<el-tab-pane label="规则配置" name="ruleConfig"></el-tab-pane>
|
||
<el-tab-pane label="提示词配置" name="promptConfig"></el-tab-pane>
|
||
<el-tab-pane label="规则验证" name="ruleValidation"></el-tab-pane>
|
||
</el-tabs>
|
||
|
||
<!-- 规则配置内容 -->
|
||
<div v-if="currentTab === 'ruleConfig'">
|
||
<el-form ref="ruleForm" :model="ruleForm" label-width="100px" class="rule-sets">
|
||
<!-- 添加规则按钮 -->
|
||
<div class="add-rule-container">
|
||
<el-button
|
||
:disabled="!selectedLabelItemId ||
|
||
(currentTab === 'ruleConfig' && (ruleSets.length === 0 || contentSources.length === 0)) ||
|
||
(currentTab === 'promptConfig' && !promptWord.trim())"
|
||
type="primary"
|
||
icon="el-icon-plus"
|
||
@click="handleAddRuleSet"
|
||
>
|
||
添加规则
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- 规则集列表 -->
|
||
<div v-for="(ruleSet, index) in ruleSets" :key="index" class="rule-set-item">
|
||
<div class="rule-set-header">
|
||
<span class="rule-set-index">规则 {{ index + 1 }}</span>
|
||
<div class="rule-set-actions">
|
||
<el-button
|
||
icon="el-icon-copy"
|
||
type="primary"
|
||
size="mini"
|
||
@click="handleCopyRuleSet(index)"
|
||
>复制</el-button>
|
||
<el-button
|
||
size="mini"
|
||
type="danger"
|
||
@click="handleDeleteRuleSet(index)"
|
||
>删除</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="rule-set-content">
|
||
<!-- 内容来源单选 -->
|
||
<el-form-item label="内容来源">
|
||
<el-radio-group
|
||
v-model="ruleSet.selectedCompositionId"
|
||
class="content-source-radios"
|
||
>
|
||
<el-radio
|
||
v-for="source in contentSources"
|
||
:key="source.compositionId"
|
||
:label="source.compositionId"
|
||
>
|
||
{{ source.compositionName }}
|
||
</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
|
||
<!-- 定位查询规则 -->
|
||
<div class="position-rules">
|
||
<el-form-item label="定位规则查询" class="position-rule-label">
|
||
</el-form-item>
|
||
<div v-for="(positionRule, posIndex) in ruleSet.positionRules" :key="posIndex" class="position-rule-item">
|
||
<el-form-item>
|
||
<!-- 规则类型选择下拉框 -->
|
||
<el-select
|
||
v-model="positionRule.ruleSetType"
|
||
@change="handleRuleTypeChange(positionRule, posIndex, index)"
|
||
placeholder="请选择规则类型"
|
||
style="width: 280px;"
|
||
>
|
||
<el-option
|
||
v-for="option in ruleSetTypeOptions"
|
||
:key="option.value"
|
||
:label="option.label"
|
||
:value="option.value"
|
||
></el-option>
|
||
</el-select>
|
||
|
||
<!-- 规则类型对应的字段 -->
|
||
<div class="rule-fields" v-if="positionRule.ruleSetType === 'headSpecifyLength'">
|
||
<el-input
|
||
v-model="positionRule.specifiedWordCount"
|
||
placeholder="指定字数"
|
||
type="number"
|
||
style="width: 150px; margin-left: 10px;"
|
||
></el-input>
|
||
</div>
|
||
|
||
<div class="rule-fields" v-if="positionRule.ruleSetType === 'docToSpecifyText'">
|
||
<el-input
|
||
v-model="positionRule.specifiedText"
|
||
placeholder="指定文字"
|
||
style="width: 200px; margin-left: 10px;"
|
||
></el-input>
|
||
</div>
|
||
|
||
<div class="rule-fields" v-if="positionRule.ruleSetType === 'tailSpecifyLength'">
|
||
<el-input
|
||
v-model="positionRule.specifiedWordCount"
|
||
placeholder="指定字数"
|
||
type="number"
|
||
style="width: 150px; margin-left: 10px;"
|
||
></el-input>
|
||
</div>
|
||
|
||
<div class="rule-fields" v-if="positionRule.ruleSetType === 'specifyTextToEnd'">
|
||
<el-input
|
||
v-model="positionRule.specifiedText"
|
||
placeholder="指定文字"
|
||
style="width: 200px; margin-left: 10px;"
|
||
></el-input>
|
||
</div>
|
||
|
||
<div class="rule-fields" v-if="positionRule.ruleSetType === 'keywordAround'">
|
||
<el-input
|
||
v-model="positionRule.beforeCharactersNum"
|
||
placeholder="前面字数"
|
||
type="number"
|
||
style="width: 120px; margin-left: 10px;"
|
||
></el-input>
|
||
<el-input
|
||
v-model="positionRule.specifiedKeywords"
|
||
placeholder="指定关键词"
|
||
style="width: 180px; margin-left: 10px;"
|
||
></el-input>
|
||
<el-input
|
||
v-model="positionRule.afterCharactersNum"
|
||
placeholder="后面字数"
|
||
type="number"
|
||
style="width: 120px; margin-left: 10px;"
|
||
></el-input>
|
||
</div>
|
||
|
||
<div class="rule-fields" v-if="positionRule.ruleSetType === 'specifyTextToLength'">
|
||
<el-input
|
||
v-model="positionRule.specifiedText"
|
||
placeholder="指定文字"
|
||
style="width: 200px; margin-left: 10px;"
|
||
></el-input>
|
||
<el-input
|
||
v-model="positionRule.specifiedWordCountLength"
|
||
placeholder="指定字数长度"
|
||
type="number"
|
||
style="width: 150px; margin-left: 10px;"
|
||
></el-input>
|
||
</div>
|
||
|
||
<div class="rule-fields" v-if="positionRule.ruleSetType === 'specifyTextEndToLength'">
|
||
<el-input
|
||
v-model="positionRule.beforeSpecifiedCharactersNum"
|
||
placeholder="指定文字前方字数"
|
||
type="number"
|
||
style="width: 180px; margin-left: 10px;"
|
||
></el-input>
|
||
<el-input
|
||
v-model="positionRule.specifiedText"
|
||
placeholder="指定文字"
|
||
style="width: 200px; margin-left: 10px;"
|
||
></el-input>
|
||
</div>
|
||
|
||
<div class="rule-fields" v-if="positionRule.ruleSetType === 'headTailRange'">
|
||
<el-input
|
||
v-model="positionRule.firstText"
|
||
placeholder="首部文字"
|
||
style="width: 150px; margin-left: 10px;"
|
||
></el-input>
|
||
<el-input
|
||
v-model="positionRule.shouldIncludeCharactersNum"
|
||
placeholder="需要包含的字数"
|
||
type="number"
|
||
style="width: 150px; margin-left: 10px;"
|
||
></el-input>
|
||
<el-input
|
||
v-model="positionRule.endText"
|
||
placeholder="尾部文字"
|
||
style="width: 150px; margin-left: 10px;"
|
||
></el-input>
|
||
</div>
|
||
|
||
<el-button
|
||
icon="el-icon-minus"
|
||
size="mini"
|
||
type="danger"
|
||
@click="handleRemovePositionRule(index, posIndex)"
|
||
style="margin-left: 10px;"
|
||
></el-button>
|
||
<el-button
|
||
icon="el-icon-plus"
|
||
size="mini"
|
||
type="primary"
|
||
@click="handleAddPositionRule(index)"
|
||
></el-button>
|
||
</el-form-item>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-form>
|
||
</div>
|
||
|
||
<!-- 提示词配置内容 -->
|
||
<div v-if="currentTab === 'promptConfig'">
|
||
<div class="button-group">
|
||
<el-button
|
||
type="primary"
|
||
@click="handleLabelIdClick"
|
||
class="info-button"
|
||
>
|
||
标的编号
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
@click="handleLabelNameClick"
|
||
class="info-button"
|
||
>
|
||
标的名称
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
@click="handleSegmentIdClick"
|
||
class="info-button"
|
||
>
|
||
标段编号
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
@click="handleSegmentNameClick"
|
||
class="info-button"
|
||
>
|
||
标段名称
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
@click="handleExtractedContentClick"
|
||
class="info-button"
|
||
>
|
||
规则提取内容
|
||
</el-button>
|
||
</div>
|
||
<!-- 仅保留一个大文本框,用户输入所有内容(包括任务说明、字段说明等) -->
|
||
<el-input
|
||
v-model="promptWord"
|
||
type="textarea"
|
||
rows="15"
|
||
placeholder="请输入提示词内容"
|
||
style="height: 100%;"
|
||
></el-input>
|
||
</div>
|
||
|
||
<!-- 规则验证内容(占位) -->
|
||
<div v-if="currentTab === 'ruleValidation'">
|
||
<div class="validation-placeholder">
|
||
规则验证功能待实现
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-col>
|
||
</el-row>
|
||
</el-card>
|
||
</template>
|
||
|
||
<script>
|
||
import { columnsRuleList } from '../config'
|
||
import {
|
||
listAnalysisLabelItem,
|
||
} from '@/api/template/analysisLabelItem/analysisLabelItem'
|
||
import {
|
||
listAnalysisRuleSet,
|
||
delAnalysisRuleSet,
|
||
addAnalysisRuleSet,
|
||
updateAnalysisRuleSet
|
||
} from '@/api/template/analysisRuleSet/analysisRuleSet'
|
||
import { getTemplateInfoDetail } from '@/api/template/templateInfo/templateInfo'
|
||
import { encryptWithSM4, decryptWithSM4 } from '@/utils/sm'
|
||
|
||
export default {
|
||
name: 'AnalysisRuleSet',
|
||
components: {
|
||
|
||
},
|
||
data() {
|
||
return {
|
||
promptWord: '',
|
||
deletedSubRuleIds: [], // 记录被删除的子规则ID
|
||
deletedPositionIds: [], // 记录被删除的定位规则ID
|
||
ruleForm: {},
|
||
columnsRuleList,
|
||
|
||
// 标签页状态
|
||
currentTab: 'ruleConfig', // 默认显示规则配置
|
||
|
||
// 树状图配置
|
||
treeData: [],
|
||
treeProps: {
|
||
label: 'analysisName',
|
||
children: 'children'
|
||
},
|
||
treeSearchKey: '',
|
||
labelItemMap: new Map(),
|
||
|
||
// 表格传递参数
|
||
tableSendParams: {
|
||
templateId: '',
|
||
analysisLabelItemId: ''
|
||
},
|
||
|
||
// 路由参数
|
||
templateId: '',
|
||
analysisLabelId: '',
|
||
templateName: '',
|
||
contentSources: [],
|
||
|
||
// 选中的标签项信息
|
||
selectedLabelItemId: '',
|
||
selectedLabelLevel: 0,
|
||
analysisCode: '',
|
||
analysisLevel: '',
|
||
analysisName: '',
|
||
analysisSort: '',
|
||
parentId: '',
|
||
|
||
// 规则集数据
|
||
ruleSets: [],
|
||
ruleSetTypeOptions: [],
|
||
|
||
// 提示词配置数据
|
||
promptForm: {
|
||
taskDescription: '', // 任务说明(用户填写)
|
||
fieldDescription: '', // 字段说明(用户填写)
|
||
inputContent: '', // 输入内容(用户填写)
|
||
outputFormat: '' // 输出格式(用户填写)
|
||
},
|
||
// 提示词表格数据
|
||
promptTableData: [
|
||
{
|
||
labelId: '',
|
||
labelName: '',
|
||
segmentId: '',
|
||
segmentName: '',
|
||
extractedContent: ''
|
||
}
|
||
]
|
||
}
|
||
},
|
||
|
||
created() {
|
||
this.loadRuleSetTypeDict()
|
||
this.templateId = decryptWithSM4(this.$route.query.templateId) || ''
|
||
this.analysisLabelId = decryptWithSM4(this.$route.query.analysisLabelId) || ''
|
||
this.tableSendParams.templateId = this.templateId
|
||
this.loadTemplateInfo()
|
||
this.loadTreeData()
|
||
},
|
||
|
||
methods: {
|
||
// 加载规则类型字典表数据
|
||
loadRuleSetTypeDict() {
|
||
this.getDicts('rule_set_type').then(response => {
|
||
this.ruleSetTypeOptions = response.data.map(item => ({
|
||
label: item.dictLabel,
|
||
value: item.dictValue
|
||
}))
|
||
}).catch(error => {
|
||
console.error('加载查询规则类型字典失败:', error)
|
||
})
|
||
},
|
||
|
||
// 返回上一页
|
||
handleBack() {
|
||
const obj = { path: "/templateInfo/index" };
|
||
this.$tab.closeOpenPage(obj);
|
||
},
|
||
|
||
// 加载模板信息
|
||
loadTemplateInfo() {
|
||
if (this.templateId) {
|
||
getTemplateInfoDetail({templateId: this.templateId})
|
||
.then(res => {
|
||
if (res.code === 200) {
|
||
this.templateName = res.data.templateName || '';
|
||
const compositionList = res.data.compositionList || [];
|
||
|
||
console.log('detail',res.data)
|
||
|
||
|
||
// 去重处理
|
||
const uniqueSources = [];
|
||
const seenIds = new Set();
|
||
compositionList.forEach(source => {
|
||
if (source.compositionId && !seenIds.has(source.compositionId)) {
|
||
seenIds.add(source.compositionId);
|
||
uniqueSources.push({
|
||
compositionId: source.compositionId,
|
||
compositionName: source.fileName
|
||
});
|
||
}
|
||
});
|
||
|
||
this.contentSources = uniqueSources;
|
||
|
||
if (this.selectedLabelItemId) {
|
||
this.loadRuleSets();
|
||
}
|
||
}
|
||
})
|
||
.catch(err => {
|
||
console.error('加载模板信息失败:', err);
|
||
});
|
||
}
|
||
},
|
||
|
||
// 加载树形结构数据
|
||
loadTreeData() {
|
||
const allItems = []; // 用于存储所有页的数据
|
||
const pageSize = 1000; // 每页数量,根据后端接口定义调整
|
||
let currentPage = 1; // 当前页码
|
||
|
||
// 定义一个递归函数来获取每一页的数据
|
||
const fetchPage = () => {
|
||
listAnalysisLabelItem({
|
||
analysisId: this.analysisLabelId,
|
||
pageNum: currentPage,
|
||
pageSize: pageSize
|
||
}).then(res => {
|
||
if (res.code === 200) {
|
||
const { rows, total } = res;
|
||
|
||
// 将当前页的数据追加到总数组
|
||
if (rows && rows.length > 0) {
|
||
allItems.push(...rows);
|
||
}
|
||
|
||
// 计算总页数
|
||
const totalPages = Math.ceil(total / pageSize);
|
||
|
||
// 如果当前页不是最后一页,则继续获取下一页
|
||
if (currentPage < totalPages) {
|
||
currentPage++;
|
||
fetchPage(); // 递归调用,获取下一页
|
||
} else {
|
||
// 所有页都获取完毕,构建树形结构
|
||
console.log('所有数据获取完毕,共', allItems.length, '条');
|
||
this.treeData = this.buildTree(allItems);
|
||
this.buildLabelItemMap(allItems); // 构建映射表
|
||
}
|
||
} else {
|
||
// 出错处理
|
||
this.$modal.msgError('加载标签项数据失败');
|
||
}
|
||
});
|
||
};
|
||
|
||
// 开始执行首次获取
|
||
fetchPage();
|
||
},
|
||
|
||
// 构建树形结构
|
||
buildTree(list) {
|
||
const tree = []
|
||
const map = {}
|
||
list.forEach(item => {
|
||
map[item.analysisLabelItemId] = { ...item, children: [] }
|
||
})
|
||
list.forEach(item => {
|
||
if (item.parentId && map[item.parentId]) {
|
||
map[item.parentId].children.push(map[item.analysisLabelItemId])
|
||
} else {
|
||
tree.push(map[item.analysisLabelItemId])
|
||
}
|
||
})
|
||
return tree
|
||
},
|
||
|
||
// 构建标签项ID与名称映射表
|
||
buildLabelItemMap(list) {
|
||
this.labelItemMap.clear()
|
||
list.forEach(item => {
|
||
this.labelItemMap.set(String(item.analysisLabelItemId), {
|
||
name: item.analysisName,
|
||
level: item.analysisLevel
|
||
})
|
||
})
|
||
},
|
||
|
||
// 树形节点过滤
|
||
filterTreeNode(value, data) {
|
||
if (!value) return true
|
||
return data.analysisName.includes(value) ||
|
||
(data.analysisCode && data.analysisCode.includes(value))
|
||
},
|
||
|
||
// 树形节点点击事件
|
||
handleTreeNodeClick(data) {
|
||
if (data.analysisLevel === 3) {
|
||
this.promptWord = data.promptWord
|
||
this.analysisLevel = data.analysisLevel
|
||
this.analysisName = data.analysisName
|
||
this.analysisCode = data.analysisCode
|
||
this.analysisSort = data.analysisSort
|
||
this.parentId = data.parentId
|
||
this.selectedLabelItemId = data.analysisLabelItemId
|
||
this.selectedLabelLevel = data.analysisLevel
|
||
this.tableSendParams.analysisLabelItemId = data.analysisLabelItemId
|
||
this.loadRuleSets()
|
||
} else {
|
||
this.$modal.msgInfo('请选择三级标签项进行操作')
|
||
}
|
||
},
|
||
|
||
// 树形节点切换事件
|
||
handleTreeNodeChange(currentNode) {
|
||
if (currentNode && currentNode.analysisLevel === 3) {
|
||
this.promptWord = data.promptWord
|
||
this.selectedLabelItemId = currentNode.analysisLabelItemId
|
||
this.selectedLabelLevel = currentNode.analysisLevel
|
||
this.tableSendParams.analysisLabelItemId = currentNode.analysisLabelItemId
|
||
this.loadRuleSets()
|
||
} else if (currentNode) {
|
||
this.$modal.msgInfo('请选择三级标签项进行操作')
|
||
this.$refs.labelItemTree.setCurrentKey(null)
|
||
this.selectedLabelItemId = ''
|
||
this.selectedLabelLevel = 0
|
||
this.tableSendParams.analysisLabelItemId = ''
|
||
this.promptWord = ''
|
||
this.ruleSets = []
|
||
} else {
|
||
this.selectedLabelItemId = ''
|
||
this.selectedLabelLevel = 0
|
||
this.tableSendParams.analysisLabelItemId = ''
|
||
this.promptWord = ''
|
||
this.ruleSets = []
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 加载规则集
|
||
*/
|
||
loadRuleSets() {
|
||
if (this.selectedLabelItemId && this.templateId) {
|
||
listAnalysisRuleSet({
|
||
templateId: this.templateId,
|
||
analysisLabelItemId: this.selectedLabelItemId
|
||
}).then(res => {
|
||
console.log('规则集数据:', res)
|
||
if (res.code === 200) {
|
||
this.ruleSets = [];
|
||
this.promptWord = res.rows.length > 0 ? (res.rows[0].promptWord || '') : '';
|
||
|
||
|
||
res.rows.forEach(ruleSet => {
|
||
(ruleSet.subRules || []).forEach(subRule => {
|
||
let sourceObj = { compositionId: '', compositionName: '' };
|
||
if (subRule.compositionId) {
|
||
const foundSource = this.contentSources.find(
|
||
s => s.compositionId === subRule.compositionId
|
||
);
|
||
sourceObj = foundSource ? foundSource : {
|
||
compositionId: subRule.compositionId,
|
||
compositionName: subRule.compositionName || '未知来源'
|
||
};
|
||
} else if (this.contentSources.length > 0) {
|
||
sourceObj = this.contentSources[0];
|
||
}
|
||
|
||
const subRulePositions = (ruleSet.positions || []).filter(
|
||
pos => pos.subRuleId === subRule.subRuleId
|
||
);
|
||
|
||
const ruleSetItem = {
|
||
ruleSetId: ruleSet.ruleSetId,
|
||
subRuleId: subRule.subRuleId,
|
||
ruleResourceId: subRule.ruleResourceId,
|
||
contentSource: sourceObj,
|
||
selectedCompositionId: subRule.compositionId,
|
||
positionRules: subRulePositions.length > 0
|
||
? subRulePositions.map(pos => ({
|
||
...pos,
|
||
subRuleIndex: this.ruleSets.length
|
||
}))
|
||
: [this.createEmptyPositionRule()]
|
||
};
|
||
|
||
this.ruleSets.push(ruleSetItem);
|
||
});
|
||
});
|
||
|
||
if (this.ruleSets.length === 0 && this.contentSources.length > 0) {
|
||
this.handleAddRuleSet();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 添加规则集
|
||
*/
|
||
handleAddRuleSet() {
|
||
// 初始化内容来源
|
||
const defaultSource = this.contentSources.length > 0
|
||
? {
|
||
compositionId: this.contentSources[0].compositionId,
|
||
compositionName: this.contentSources[0].compositionName
|
||
}
|
||
: { compositionId: '', compositionName: '' };
|
||
|
||
// 新增规则集
|
||
this.ruleSets.push({
|
||
contentSource: defaultSource,
|
||
selectedCompositionId: defaultSource.compositionId,
|
||
positionRules: [this.createEmptyPositionRule()]
|
||
});
|
||
},
|
||
|
||
/**
|
||
* 复制规则集
|
||
*/
|
||
handleCopyRuleSet(index) {
|
||
const copiedRuleSet = JSON.parse(JSON.stringify(this.ruleSets[index]));
|
||
// 删除ID信息,由后端重新生成
|
||
delete copiedRuleSet.subRuleId;
|
||
delete copiedRuleSet.ruleSetId;
|
||
delete copiedRuleSet.ruleResourceId;
|
||
|
||
copiedRuleSet.selectedCompositionId = copiedRuleSet.contentSource.compositionId;
|
||
|
||
copiedRuleSet.positionRules.forEach(posRule => {
|
||
delete posRule.queryRuleId;
|
||
posRule.subRuleIndex = this.ruleSets.length;
|
||
});
|
||
|
||
this.ruleSets.splice(index + 1, 0, copiedRuleSet);
|
||
},
|
||
|
||
/**
|
||
* 删除规则集
|
||
*/
|
||
handleDeleteRuleSet(index) {
|
||
const deletedRuleSet = this.ruleSets[index];
|
||
// 如果是已有子规则(有subRuleId),添加到删除列表
|
||
if (deletedRuleSet.subRuleId) {
|
||
this.deletedSubRuleIds.push(deletedRuleSet.subRuleId);
|
||
}
|
||
// 收集该子规则下所有定位规则的ID
|
||
if (deletedRuleSet.positionRules && deletedRuleSet.positionRules.length) {
|
||
deletedRuleSet.positionRules.forEach(pos => {
|
||
if (pos.queryRuleId) { // 仅收集已有定位规则
|
||
this.deletedPositionIds.push(pos.queryRuleId);
|
||
}
|
||
});
|
||
}
|
||
this.ruleSets.splice(index, 1);
|
||
},
|
||
|
||
/**
|
||
* 添加定位规则
|
||
*/
|
||
handleAddPositionRule(ruleSetIndex) {
|
||
this.ruleSets[ruleSetIndex].positionRules.push(this.createEmptyPositionRule());
|
||
},
|
||
|
||
/**
|
||
* 移除定位规则
|
||
*/
|
||
handleRemovePositionRule(ruleSetIndex, posIndex) {
|
||
const positionRule = this.ruleSets[ruleSetIndex].positionRules[posIndex];
|
||
// 如果是已有定位规则(有queryRuleId),添加到删除列表
|
||
if (positionRule.queryRuleId) {
|
||
this.deletedPositionIds.push(positionRule.queryRuleId);
|
||
}
|
||
if (this.ruleSets[ruleSetIndex].positionRules.length > 1) {
|
||
this.ruleSets[ruleSetIndex].positionRules.splice(posIndex, 1);
|
||
} else {
|
||
this.$message.warning('至少保留一个定位规则');
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 规则类型改变事件
|
||
*/
|
||
handleRuleTypeChange(positionRule, posIndex, ruleSetIndex) {
|
||
const allFields = [
|
||
'specifiedWordCount', 'specifiedText', 'specifiedKeywords',
|
||
'beforeCharactersNum', 'afterCharactersNum', 'specifiedWordCountLength',
|
||
'beforeSpecifiedCharactersNum', 'firstText', 'shouldIncludeCharactersNum', 'endText'
|
||
];
|
||
|
||
allFields.forEach(field => {
|
||
if (positionRule.hasOwnProperty(field)) {
|
||
this.$set(positionRule, field, '');
|
||
}
|
||
});
|
||
|
||
const ruleType = positionRule.ruleSetType;
|
||
switch (ruleType) {
|
||
case 'headSpecifyLength':
|
||
case 'tailSpecifyLength':
|
||
this.$set(positionRule, 'specifiedWordCount', '');
|
||
break;
|
||
case 'docToSpecifyText':
|
||
case 'specifyTextToEnd':
|
||
this.$set(positionRule, 'specifiedText', '');
|
||
break;
|
||
case 'keywordAround':
|
||
this.$set(positionRule, 'beforeCharactersNum', '');
|
||
this.$set(positionRule, 'specifiedKeywords', '');
|
||
this.$set(positionRule, 'afterCharactersNum', '');
|
||
break;
|
||
case 'specifyTextToLength':
|
||
this.$set(positionRule, 'specifiedText', '');
|
||
this.$set(positionRule, 'specifiedWordCountLength', '');
|
||
break;
|
||
case 'specifyTextEndToLength':
|
||
this.$set(positionRule, 'beforeSpecifiedCharactersNum', '');
|
||
this.$set(positionRule, 'specifiedText', '');
|
||
break;
|
||
case 'headTailRange':
|
||
this.$set(positionRule, 'firstText', '');
|
||
this.$set(positionRule, 'shouldIncludeCharactersNum', '');
|
||
this.$set(positionRule, 'endText', '');
|
||
break;
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 创建空的定位规则
|
||
*/
|
||
createEmptyPositionRule() {
|
||
return {
|
||
subRuleIndex: 0,
|
||
ruleSetType: '',
|
||
};
|
||
},
|
||
|
||
/**
|
||
* 标签切换事件处理
|
||
*/
|
||
handleTabClick(tab) {
|
||
this.currentTab = tab.name;
|
||
// 可以在这里添加切换标签时的额外逻辑
|
||
},
|
||
|
||
/**
|
||
* 整合提示词内容为一个字符串
|
||
*/
|
||
getPromptWord() {
|
||
// 将各个提示词部分整合为一个字符串
|
||
let promptParts = [];
|
||
|
||
if (this.promptForm.taskDescription) {
|
||
promptParts.push(`# 任务说明\n${this.promptForm.taskDescription}`);
|
||
}
|
||
|
||
if (this.promptForm.fieldDescription) {
|
||
promptParts.push(`# 字段说明\n${this.promptForm.fieldDescription}`);
|
||
}
|
||
|
||
if (this.promptForm.inputContent) {
|
||
promptParts.push(`# 输入内容\n${this.promptForm.inputContent}`);
|
||
}
|
||
|
||
if (this.promptForm.outputFormat) {
|
||
promptParts.push(`# 输出格式\n${this.promptForm.outputFormat}`);
|
||
}
|
||
|
||
return promptParts.join('\n\n');
|
||
},
|
||
|
||
/**
|
||
* 验证提示词配置
|
||
*/
|
||
validatePrompt() {
|
||
// 直接验证当前使用的 promptWord 变量
|
||
if (!this.promptWord.trim()) {
|
||
this.$message.warning('请填写提示词内容');
|
||
return false;
|
||
}
|
||
return true;
|
||
},
|
||
|
||
/**
|
||
* 保存规则(包括提示词)
|
||
*/
|
||
handleSaveRules() {
|
||
// 根据当前标签页进行不同验证
|
||
if (this.currentTab === 'ruleConfig' && !this.validateRules()) {
|
||
return;
|
||
}
|
||
|
||
if (this.currentTab === 'promptConfig' && !this.validatePrompt()) {
|
||
return;
|
||
}
|
||
|
||
// 准备提交的数据
|
||
const submitData = {
|
||
deletedSubRuleIds: this.deletedSubRuleIds,
|
||
deletedPositionIds: this.deletedPositionIds,
|
||
ruleSetId: this.ruleSets.length > 0 ? this.ruleSets[0].ruleSetId : null,
|
||
analysisLevel: this.analysisLevel,
|
||
analysisName: this.analysisName,
|
||
analysisCode: this.analysisCode,
|
||
analysisSort: this.analysisSort,
|
||
parentId: this.parentId,
|
||
templateId: this.templateId,
|
||
analysisLabelItemId: this.selectedLabelItemId,
|
||
|
||
// 子规则信息
|
||
subRules: this.ruleSets.map((ruleSet) => ({
|
||
subRuleId: ruleSet.subRuleId,
|
||
compositionId: ruleSet.selectedCompositionId,
|
||
ruleResourceId: ruleSet.ruleResourceId
|
||
})),
|
||
|
||
positions: [],
|
||
|
||
// 提示词内容,整合为promptWord字段
|
||
promptWord: this.promptWord.trim()
|
||
};
|
||
|
||
this.deletedSubRuleIds = [];
|
||
this.deletedPositionIds = [];
|
||
|
||
// 处理定位规则
|
||
this.ruleSets.forEach((ruleSet, ruleSetIndex) => {
|
||
ruleSet.positionRules.forEach(posRule => {
|
||
const positionData = {
|
||
queryRuleId: posRule.queryRuleId,
|
||
ruleSetType: posRule.ruleSetType,
|
||
specifiedWordCount: posRule.specifiedWordCount,
|
||
specifiedText: posRule.specifiedText,
|
||
specifiedKeywords: posRule.specifiedKeywords,
|
||
beforeCharactersNum: posRule.beforeCharactersNum,
|
||
afterCharactersNum: posRule.afterCharactersNum,
|
||
specifiedWordCountLength: posRule.specifiedWordCountLength,
|
||
beforeSpecifiedCharactersNum: posRule.beforeSpecifiedCharactersNum,
|
||
firstText: posRule.firstText,
|
||
subRuleIndex: ruleSetIndex,
|
||
shouldIncludeCharactersNum: posRule.shouldIncludeCharactersNum,
|
||
endText: posRule.endText
|
||
};
|
||
|
||
if (ruleSet.subRuleId) {
|
||
positionData.subRuleId = ruleSet.subRuleId;
|
||
}
|
||
|
||
submitData.positions.push(positionData);
|
||
});
|
||
});
|
||
console.log('提交数据:', submitData)
|
||
|
||
// 调用接口:区分新增和更新
|
||
const isUpdate = this.ruleSets.some(rule => rule.ruleSetId);
|
||
const apiMethod = isUpdate ? updateAnalysisRuleSet : addAnalysisRuleSet;
|
||
|
||
apiMethod(submitData)
|
||
.then(res => {
|
||
if (res.code === 200) {
|
||
this.$message.success(isUpdate ? '规则更新成功' : '规则保存成功');
|
||
this.loadRuleSets(); // 重新加载以获取最新ID
|
||
} else {
|
||
this.$message.error(res.msg || (isUpdate ? '规则更新失败' : '规则保存失败'));
|
||
}
|
||
})
|
||
.catch(err => {
|
||
console.error('保存规则失败:', err);
|
||
this.$message.error('操作失败,请重试');
|
||
});
|
||
},
|
||
|
||
/**
|
||
* 验证规则
|
||
*/
|
||
validateRules() {
|
||
for (let i = 0; i < this.ruleSets.length; i++) {
|
||
const ruleSet = this.ruleSets[i];
|
||
|
||
// 验证内容来源
|
||
if (!ruleSet.selectedCompositionId) {
|
||
this.$message.warning(`规则 ${i + 1}:请选择内容来源`);
|
||
return false;
|
||
}
|
||
|
||
// 验证每个定位规则
|
||
for (let j = 0; j < ruleSet.positionRules.length; j++) {
|
||
const posRule = ruleSet.positionRules[j];
|
||
|
||
if (!posRule.ruleSetType) {
|
||
this.$message.warning(`规则 ${i + 1},定位规则 ${j + 1}:请选择规则类型`);
|
||
return false;
|
||
}
|
||
|
||
if (posRule.ruleSetType === 'headSpecifyLength' && !posRule.specifiedWordCount) {
|
||
this.$message.warning(`规则 ${i + 1},定位规则 ${j + 1}:请输入指定字数`);
|
||
return false;
|
||
}
|
||
|
||
if (posRule.ruleSetType === 'docToSpecifyText' && !posRule.specifiedText) {
|
||
this.$message.warning(`规则 ${i + 1},定位规则 ${j + 1}:请输入指定文字`);
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
return true;
|
||
},
|
||
|
||
/**
|
||
* 获取字典标签
|
||
*/
|
||
getDictLabel(dictType, dictValue) {
|
||
const dictItem = this.dictCache[dictType]?.find(item => item.dictValue === dictValue)
|
||
return dictItem ? dictItem.dictLabel : dictValue
|
||
}
|
||
},
|
||
|
||
watch: {
|
||
// 监听树搜索关键词变化
|
||
treeSearchKey(val) {
|
||
this.$refs.labelItemTree.filter(val)
|
||
},
|
||
|
||
// 监听路由参数变化
|
||
'$route.query.analysisLabelId'(val) {
|
||
this.analysisLabelId = decryptWithSM4(val) || ''
|
||
this.loadTreeData()
|
||
},
|
||
'$route.query.templateId'(val) {
|
||
this.templateId = decryptWithSM4(val) || ''
|
||
this.tableSendParams.templateId = this.templateId
|
||
this.loadTemplateInfo()
|
||
if (this.selectedLabelItemId) {
|
||
this.loadRuleSets()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
/* 原有样式保持不变 */
|
||
.back-container {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
padding: 0 20px;
|
||
|
||
.back-btn {
|
||
width: 98px;
|
||
height: 36px;
|
||
background: #FFFFFF;
|
||
box-shadow: 0px 4px 8px 0px rgba(76, 76, 76, 0.2);
|
||
border-radius: 4px;
|
||
border: none;
|
||
color: #666;
|
||
font-size: 14px;
|
||
transition: all 0.3s ease;
|
||
|
||
&:hover {
|
||
background: #f5f5f5;
|
||
color: #409EFF;
|
||
box-shadow: 0px 6px 12px 0px rgba(76, 76, 76, 0.3);
|
||
}
|
||
}
|
||
|
||
.save-btn {
|
||
width: 98px;
|
||
height: 36px;
|
||
box-shadow: 0px 4px 8px 0px rgba(76, 76, 76, 0.2);
|
||
border-radius: 4px;
|
||
border: none;
|
||
font-size: 14px;
|
||
transition: all 0.3s ease;
|
||
|
||
&:hover {
|
||
box-shadow: 0px 6px 12px 0px rgba(76, 76, 76, 0.3);
|
||
}
|
||
}
|
||
}
|
||
|
||
.analysis-rule-container {
|
||
height: calc(100vh - 84px);
|
||
overflow: hidden;
|
||
background: linear-gradient(180deg, #F1F6FF 20%, #E5EFFF 100%);
|
||
padding: 16px;
|
||
}
|
||
|
||
.tree-container {
|
||
height: 100%;
|
||
background: #fff;
|
||
border: 1px solid #ebeef5;
|
||
border-radius: 4px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||
padding: 16px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
box-sizing: border-box;
|
||
|
||
.tree-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
h3 {
|
||
margin: 0;
|
||
font-size: 16px;
|
||
font-weight: 600; // 增强标题权重,与表格标题一致
|
||
color: #333;
|
||
}
|
||
}
|
||
|
||
.tree-search {
|
||
margin-bottom: 16px;
|
||
::v-deep .el-input__inner {
|
||
border-color: #dcdfe6;
|
||
border-radius: 4px;
|
||
&:focus {
|
||
border-color: #1f72ea; // 聚焦时边框色与表格交互元素一致
|
||
box-shadow: 0 0 0 2px rgba(31, 114, 234, 0.2);
|
||
}
|
||
}
|
||
}
|
||
|
||
.el-tree {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
&::-webkit-scrollbar {
|
||
width: 6px;
|
||
height: 6px;
|
||
}
|
||
&::-webkit-scrollbar-thumb {
|
||
background-color: #ddd;
|
||
border-radius: 3px;
|
||
}
|
||
&::-webkit-scrollbar-track {
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
::v-deep .el-tree-node__content {
|
||
height: 40px;
|
||
&:hover {
|
||
background-color: #f5f7fa;
|
||
}
|
||
}
|
||
|
||
::v-deep .el-tree-node.is-current > .el-tree-node__content {
|
||
background-color: #e6f7ff;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.tree-node-content {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
width: 100%;
|
||
height: 100%; // 确保内容充满节点行高
|
||
|
||
> span:first-child {font-size: 15px !important;}
|
||
|
||
.tree-node-actions {
|
||
opacity: 0;
|
||
transition: opacity 0.3s;
|
||
|
||
i {
|
||
margin-left: 8px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
// 9. 图标交互效果与表格操作按钮对齐
|
||
&:hover {
|
||
filter: brightness(1.2); // hover 时轻微提亮
|
||
}
|
||
}
|
||
|
||
.el-icon-edit {
|
||
color: #EAA819;
|
||
}
|
||
|
||
.el-icon-delete {
|
||
color: #DB3E29;
|
||
}
|
||
}
|
||
|
||
&:hover .tree-node-actions {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 规则配置区域样式
|
||
.rule-config-container {
|
||
background: #fff;
|
||
border-radius: 4px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||
padding: 20px;
|
||
height: calc(100vh - 205px);
|
||
overflow-y: auto;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.rule-config-container {
|
||
/* 使用 ::v-deep 穿透 scoped 样式 */
|
||
::v-deep .el-textarea {
|
||
/* 确保 textarea 容器占满可用宽度 */
|
||
height: 100%;
|
||
width: 100%;
|
||
|
||
&__inner {
|
||
/* 1. 基础文本美化 */
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||
font-size: 15px; /* slightly larger font for better readability */
|
||
line-height: 1.7; /* 增加行高,提升可读性 */
|
||
letter-spacing: 0.5px; /* 增加字间距,让文字不拥挤 */
|
||
color: #333; /* 文本颜色 */
|
||
|
||
/* 2. 容器样式美化 */
|
||
background-color: #ffffff;
|
||
border: 1px solid #e0e6ed; /* 更柔和的边框色 */
|
||
border-radius: 8px; /* 圆润的 corners */
|
||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); /* subtle outer shadow */
|
||
|
||
/* 3. 内部 Padding */
|
||
padding: 16px; /* 上下左右都有 padding */
|
||
|
||
/* 4. 交互与过渡效果 */
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); /* 平滑过渡 */
|
||
|
||
/* Hover 状态 */
|
||
&:hover {
|
||
border-color: #c9d2dc;
|
||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
|
||
}
|
||
|
||
/* Focus 状态 (这是最重要的交互反馈) */
|
||
&:focus {
|
||
outline: none;
|
||
border-color: #409EFF; /* 高亮边框色,与主题色一致 */
|
||
box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.15); /* 聚焦发光效果 */
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 5. 美化 Placeholder */
|
||
::v-deep .el-textarea__inner::placeholder {
|
||
color: #b4bcc2; /* 更淡的占位符颜色 */
|
||
font-style: italic; /* 斜体,增加设计感 */
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
|
||
.template-info {
|
||
margin-bottom: 20px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 1px solid #eee;
|
||
|
||
h3 {
|
||
margin: 0;
|
||
color: #1F72EA;
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
|
||
// 提示词表单样式
|
||
.prompt-form {
|
||
::v-deep .el-form-item {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
::v-deep .el-textarea__inner {
|
||
min-height: 100%;
|
||
}
|
||
}
|
||
|
||
// 规则验证占位样式
|
||
.validation-placeholder {
|
||
text-align: center;
|
||
padding: 50px 0;
|
||
color: #999;
|
||
}
|
||
|
||
// 其他原有样式保持不变
|
||
.rule-sets {
|
||
::v-deep .el-form-item {
|
||
}
|
||
|
||
::v-deep .el-form-item__label {
|
||
text-align: left;
|
||
|
||
}
|
||
|
||
::v-deep .el-form-item__content {
|
||
}
|
||
}
|
||
|
||
.add-rule-container {
|
||
margin-bottom: 20px;
|
||
text-align: right;
|
||
}
|
||
|
||
.rule-set-item {
|
||
background: #f9f9f9;
|
||
border-radius: 6px;
|
||
padding: 15px;
|
||
margin-bottom: 20px;
|
||
position: relative;
|
||
}
|
||
|
||
.rule-set-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 1px dashed #e0e0e0;
|
||
|
||
.rule-set-index {
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
}
|
||
|
||
.rule-set-actions {
|
||
display: flex;
|
||
gap: 5px;
|
||
}
|
||
|
||
.rule-set-content {
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.content-source-radios {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 15px;
|
||
margin-top: 5px;
|
||
}
|
||
|
||
.position-rules {
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.position-rule-item {
|
||
margin-bottom: 15px;
|
||
background: #f9f9f9;
|
||
}
|
||
|
||
.position-rule-item ::v-deep .el-form-item__content {
|
||
margin-left: 0 !important;
|
||
}
|
||
|
||
.rule-fields {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.add-position-rule {
|
||
color: #409EFF;
|
||
margin-top: 10px;
|
||
display: inline-block;
|
||
}
|
||
|
||
.save-rule-container {
|
||
margin-top: 30px;
|
||
text-align: right;
|
||
}
|
||
|
||
.add-btn {
|
||
width: 121px;
|
||
height: 36px;
|
||
background: #1F72EA;
|
||
box-shadow: 0px 4px 8px 0px rgba(51, 135, 255, 0.5);
|
||
border-radius: 4px;
|
||
border: none;
|
||
color: #fff;
|
||
font-size: 14px;
|
||
transition: all 0.3s;
|
||
margin-right: 10px;
|
||
|
||
&:hover {
|
||
background: #4A8BFF;
|
||
box-shadow: 0px 6px 12px 0px rgba(51, 135, 255, 0.6);
|
||
}
|
||
|
||
&:disabled {
|
||
background: #a0cfff;
|
||
box-shadow: none;
|
||
cursor: not-allowed;
|
||
}
|
||
}
|
||
|
||
.action-btn {
|
||
margin-right: 8px;
|
||
|
||
&:last-child {
|
||
margin-right: 0;
|
||
}
|
||
}
|
||
|
||
.button-group {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
padding: 10px 0;
|
||
}
|
||
|
||
.info-button {
|
||
min-width: 120px;
|
||
}
|
||
</style>
|