smart-bid-web/src/views/template/templateInfo/components/AnalysisRule.vue

1380 lines
41 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>
<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>