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

1380 lines
41 KiB
Vue
Raw Normal View History

2025-11-26 18:25:40 +08:00
<template>
<el-card class="analysis-rule-container">
2025-11-28 14:27:39 +08:00
<!-- 返回按钮和保存按钮 -->
2025-11-26 18:25:40 +08:00
<div class="back-container">
<el-button
type="primary"
@click="handleSaveRules"
2025-11-28 14:27:39 +08:00
:disabled="!selectedLabelItemId ||
(currentTab === 'ruleConfig' && (ruleSets.length === 0 || contentSources.length === 0)) ||
(currentTab === 'promptConfig' && !promptWord.trim())"
2025-11-26 18:25:40 +08:00
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>
<!-- 右侧解析规则配置区域 -->
2025-11-28 14:27:39 +08:00
<el-col :span="13">
2025-11-26 18:25:40 +08:00
<div class="rule-config-container">
2025-11-28 14:27:39 +08:00
<!-- 标签切换 -->
<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
2025-12-05 09:12:28 +08:00
:disabled="!selectedLabelItemId ||
(currentTab === 'ruleConfig' && (ruleSets.length === 0 || contentSources.length === 0)) ||
(currentTab === 'promptConfig' && !promptWord.trim())"
2025-11-28 14:27:39 +08:00
type="primary"
icon="el-icon-plus"
@click="handleAddRuleSet"
>
添加规则
</el-button>
</div>
2025-11-26 18:25:40 +08:00
2025-11-28 14:27:39 +08:00
<!-- 规则集列表 -->
<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>
2025-11-26 18:25:40 +08:00
</div>
2025-11-28 14:27:39 +08:00
<div class="rule-set-content">
<!-- 内容来源单选 -->
<el-form-item label="内容来源">
<el-radio-group
v-model="ruleSet.selectedCompositionId"
class="content-source-radios"
2025-11-26 18:25:40 +08:00
>
2025-11-28 14:27:39 +08:00
<el-radio
v-for="source in contentSources"
:key="source.compositionId"
:label="source.compositionId"
2025-11-26 18:25:40 +08:00
>
2025-11-28 14:27:39 +08:00
{{ source.compositionName }}
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 定位查询规则 -->
<div class="position-rules">
<el-form-item label="定位规则查询" class="position-rule-label">
2025-11-26 18:25:40 +08:00
</el-form-item>
2025-11-28 14:27:39 +08:00
<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>
2025-11-26 18:25:40 +08:00
</div>
</div>
</div>
2025-11-28 14:27:39 +08:00
</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="请输入提示词内容"
2025-12-05 09:12:28 +08:00
style="height: 100%;"
2025-11-28 14:27:39 +08:00
></el-input>
</div>
<!-- 规则验证内容占位 -->
<div v-if="currentTab === 'ruleValidation'">
<div class="validation-placeholder">
规则验证功能待实现
2025-11-26 18:25:40 +08:00
</div>
2025-11-28 14:27:39 +08:00
</div>
2025-11-26 18:25:40 +08:00
</div>
</el-col>
</el-row>
</el-card>
</template>
<script>
import { columnsRuleList } from '../config'
import {
listAnalysisLabelItem,
} from '@/api/template/analysisLabelItem/analysisLabelItem'
import {
listAnalysisRuleSet,
delAnalysisRuleSet,
2025-11-28 14:27:39 +08:00
addAnalysisRuleSet,
updateAnalysisRuleSet
2025-11-26 18:25:40 +08:00
} 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 {
2025-11-28 14:27:39 +08:00
promptWord: '',
deletedSubRuleIds: [], // 记录被删除的子规则ID
deletedPositionIds: [], // 记录被删除的定位规则ID
2025-11-26 18:25:40 +08:00
ruleForm: {},
columnsRuleList,
2025-11-28 14:27:39 +08:00
// 标签页状态
currentTab: 'ruleConfig', // 默认显示规则配置
2025-11-26 18:25:40 +08:00
// 树状图配置
treeData: [],
treeProps: {
label: 'analysisName',
children: 'children'
},
treeSearchKey: '',
2025-11-28 14:27:39 +08:00
labelItemMap: new Map(),
2025-11-26 18:25:40 +08:00
// 表格传递参数
tableSendParams: {
2025-11-28 14:27:39 +08:00
templateId: '',
analysisLabelItemId: ''
2025-11-26 18:25:40 +08:00
},
// 路由参数
templateId: '',
analysisLabelId: '',
2025-11-28 14:27:39 +08:00
templateName: '',
contentSources: [],
2025-11-26 18:25:40 +08:00
// 选中的标签项信息
selectedLabelItemId: '',
selectedLabelLevel: 0,
analysisCode: '',
analysisLevel: '',
analysisName: '',
analysisSort: '',
parentId: '',
// 规则集数据
ruleSets: [],
2025-11-28 14:27:39 +08:00
ruleSetTypeOptions: [],
// 提示词配置数据
promptForm: {
taskDescription: '', // 任务说明(用户填写)
fieldDescription: '', // 字段说明(用户填写)
inputContent: '', // 输入内容(用户填写)
outputFormat: '' // 输出格式(用户填写)
},
// 提示词表格数据
promptTableData: [
{
labelId: '',
labelName: '',
segmentId: '',
segmentName: '',
extractedContent: ''
}
]
2025-11-26 18:25:40 +08:00
}
},
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: {
2025-11-28 14:27:39 +08:00
// 加载规则类型字典表数据
2025-11-26 18:25:40 +08:00
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)
})
},
2025-11-28 14:27:39 +08:00
// 返回上一页
2025-11-26 18:25:40 +08:00
handleBack() {
const obj = { path: "/templateInfo/index" };
this.$tab.closeOpenPage(obj);
},
2025-11-28 14:27:39 +08:00
// 加载模板信息
2025-11-26 18:25:40 +08:00
loadTemplateInfo() {
if (this.templateId) {
getTemplateInfoDetail({templateId: this.templateId})
.then(res => {
if (res.code === 200) {
this.templateName = res.data.templateName || '';
const compositionList = res.data.compositionList || [];
2025-12-05 09:12:28 +08:00
console.log('detail',res.data)
2025-11-26 18:25:40 +08:00
// 去重处理
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);
});
}
},
2025-11-28 14:27:39 +08:00
// 加载树形结构数据
2025-11-26 18:25:40 +08:00
loadTreeData() {
2025-11-29 17:54:26 +08:00
const allItems = []; // 用于存储所有页的数据
2025-12-05 09:12:28 +08:00
const pageSize = 1000; // 每页数量,根据后端接口定义调整
2025-11-29 17:54:26 +08:00
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();
2025-11-26 18:25:40 +08:00
},
2025-11-28 14:27:39 +08:00
// 构建树形结构
2025-11-26 18:25:40 +08:00
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
},
2025-11-28 14:27:39 +08:00
// 构建标签项ID与名称映射表
2025-11-26 18:25:40 +08:00
buildLabelItemMap(list) {
this.labelItemMap.clear()
list.forEach(item => {
this.labelItemMap.set(String(item.analysisLabelItemId), {
name: item.analysisName,
level: item.analysisLevel
})
})
},
2025-11-28 14:27:39 +08:00
// 树形节点过滤
2025-11-26 18:25:40 +08:00
filterTreeNode(value, data) {
if (!value) return true
return data.analysisName.includes(value) ||
(data.analysisCode && data.analysisCode.includes(value))
},
2025-11-28 14:27:39 +08:00
// 树形节点点击事件
2025-11-26 18:25:40 +08:00
handleTreeNodeClick(data) {
if (data.analysisLevel === 3) {
2025-11-28 14:27:39 +08:00
this.promptWord = data.promptWord
2025-11-26 18:25:40 +08:00
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
2025-11-28 14:27:39 +08:00
this.loadRuleSets()
2025-11-26 18:25:40 +08:00
} else {
this.$modal.msgInfo('请选择三级标签项进行操作')
}
},
2025-11-28 14:27:39 +08:00
// 树形节点切换事件
2025-11-26 18:25:40 +08:00
handleTreeNodeChange(currentNode) {
if (currentNode && currentNode.analysisLevel === 3) {
2025-11-28 14:27:39 +08:00
this.promptWord = data.promptWord
2025-11-26 18:25:40 +08:00
this.selectedLabelItemId = currentNode.analysisLabelItemId
this.selectedLabelLevel = currentNode.analysisLevel
this.tableSendParams.analysisLabelItemId = currentNode.analysisLabelItemId
2025-11-28 14:27:39 +08:00
this.loadRuleSets()
2025-11-26 18:25:40 +08:00
} else if (currentNode) {
this.$modal.msgInfo('请选择三级标签项进行操作')
this.$refs.labelItemTree.setCurrentKey(null)
this.selectedLabelItemId = ''
this.selectedLabelLevel = 0
this.tableSendParams.analysisLabelItemId = ''
2025-11-28 14:27:39 +08:00
this.promptWord = ''
this.ruleSets = []
2025-11-26 18:25:40 +08:00
} else {
this.selectedLabelItemId = ''
this.selectedLabelLevel = 0
this.tableSendParams.analysisLabelItemId = ''
2025-11-28 14:27:39 +08:00
this.promptWord = ''
this.ruleSets = []
2025-11-26 18:25:40 +08:00
}
},
/**
* 加载规则集
*/
loadRuleSets() {
if (this.selectedLabelItemId && this.templateId) {
listAnalysisRuleSet({
templateId: this.templateId,
analysisLabelItemId: this.selectedLabelItemId
}).then(res => {
2025-11-28 14:27:39 +08:00
console.log('规则集数据:', res)
2025-11-26 18:25:40 +08:00
if (res.code === 200) {
this.ruleSets = [];
2025-11-28 14:27:39 +08:00
this.promptWord = res.rows.length > 0 ? (res.rows[0].promptWord || '') : '';
2025-11-26 18:25:40 +08:00
2025-11-28 14:27:39 +08:00
res.rows.forEach(ruleSet => {
(ruleSet.subRules || []).forEach(subRule => {
2025-11-26 18:25:40 +08:00
let sourceObj = { compositionId: '', compositionName: '' };
2025-11-28 14:27:39 +08:00
if (subRule.compositionId) {
2025-11-26 18:25:40 +08:00
const foundSource = this.contentSources.find(
2025-11-28 14:27:39 +08:00
s => s.compositionId === subRule.compositionId
2025-11-26 18:25:40 +08:00
);
2025-11-28 14:27:39 +08:00
sourceObj = foundSource ? foundSource : {
compositionId: subRule.compositionId,
compositionName: subRule.compositionName || '未知来源'
};
2025-11-26 18:25:40 +08:00
} else if (this.contentSources.length > 0) {
sourceObj = this.contentSources[0];
}
2025-11-28 14:27:39 +08:00
const subRulePositions = (ruleSet.positions || []).filter(
pos => pos.subRuleId === subRule.subRuleId
);
const ruleSetItem = {
ruleSetId: ruleSet.ruleSetId,
subRuleId: subRule.subRuleId,
ruleResourceId: subRule.ruleResourceId,
2025-11-26 18:25:40 +08:00
contentSource: sourceObj,
2025-11-28 14:27:39 +08:00
selectedCompositionId: subRule.compositionId,
positionRules: subRulePositions.length > 0
? subRulePositions.map(pos => ({
...pos,
subRuleIndex: this.ruleSets.length
}))
2025-11-26 18:25:40 +08:00
: [this.createEmptyPositionRule()]
2025-11-28 14:27:39 +08:00
};
this.ruleSets.push(ruleSetItem);
2025-11-26 18:25:40 +08:00
});
});
if (this.ruleSets.length === 0 && this.contentSources.length > 0) {
this.handleAddRuleSet();
}
}
});
}
},
/**
* 添加规则集
*/
handleAddRuleSet() {
2025-11-28 14:27:39 +08:00
// 初始化内容来源
2025-11-26 18:25:40 +08:00
const defaultSource = this.contentSources.length > 0
? {
compositionId: this.contentSources[0].compositionId,
compositionName: this.contentSources[0].compositionName
}
: { compositionId: '', compositionName: '' };
2025-11-28 14:27:39 +08:00
// 新增规则集
2025-11-26 18:25:40 +08:00
this.ruleSets.push({
contentSource: defaultSource,
2025-11-28 14:27:39 +08:00
selectedCompositionId: defaultSource.compositionId,
2025-11-26 18:25:40 +08:00
positionRules: [this.createEmptyPositionRule()]
});
},
/**
* 复制规则集
*/
handleCopyRuleSet(index) {
const copiedRuleSet = JSON.parse(JSON.stringify(this.ruleSets[index]));
2025-11-28 14:27:39 +08:00
// 删除ID信息由后端重新生成
delete copiedRuleSet.subRuleId;
2025-11-26 18:25:40 +08:00
delete copiedRuleSet.ruleSetId;
2025-11-28 14:27:39 +08:00
delete copiedRuleSet.ruleResourceId;
copiedRuleSet.selectedCompositionId = copiedRuleSet.contentSource.compositionId;
copiedRuleSet.positionRules.forEach(posRule => {
delete posRule.queryRuleId;
posRule.subRuleIndex = this.ruleSets.length;
});
2025-11-26 18:25:40 +08:00
this.ruleSets.splice(index + 1, 0, copiedRuleSet);
},
/**
* 删除规则集
*/
handleDeleteRuleSet(index) {
2025-11-28 14:27:39 +08:00
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);
}
});
}
2025-11-26 18:25:40 +08:00
this.ruleSets.splice(index, 1);
},
/**
* 添加定位规则
*/
handleAddPositionRule(ruleSetIndex) {
this.ruleSets[ruleSetIndex].positionRules.push(this.createEmptyPositionRule());
},
/**
* 移除定位规则
*/
handleRemovePositionRule(ruleSetIndex, posIndex) {
2025-11-28 14:27:39 +08:00
const positionRule = this.ruleSets[ruleSetIndex].positionRules[posIndex];
// 如果是已有定位规则有queryRuleId添加到删除列表
if (positionRule.queryRuleId) {
this.deletedPositionIds.push(positionRule.queryRuleId);
}
2025-11-26 18:25:40 +08:00
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 {
2025-11-28 14:27:39 +08:00
subRuleIndex: 0,
ruleSetType: '',
2025-11-26 18:25:40 +08:00
};
},
/**
2025-11-28 14:27:39 +08:00
* 标签切换事件处理
*/
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;
},
/**
* 保存规则包括提示词
2025-11-26 18:25:40 +08:00
*/
handleSaveRules() {
2025-11-28 14:27:39 +08:00
// 根据当前标签页进行不同验证
if (this.currentTab === 'ruleConfig' && !this.validateRules()) {
return;
}
if (this.currentTab === 'promptConfig' && !this.validatePrompt()) {
2025-11-26 18:25:40 +08:00
return;
}
2025-11-28 14:27:39 +08:00
// 准备提交的数据
2025-11-26 18:25:40 +08:00
const submitData = {
2025-11-28 14:27:39 +08:00
deletedSubRuleIds: this.deletedSubRuleIds,
deletedPositionIds: this.deletedPositionIds,
ruleSetId: this.ruleSets.length > 0 ? this.ruleSets[0].ruleSetId : null,
2025-11-26 18:25:40 +08:00
analysisLevel: this.analysisLevel,
analysisName: this.analysisName,
analysisCode: this.analysisCode,
analysisSort: this.analysisSort,
parentId: this.parentId,
templateId: this.templateId,
analysisLabelItemId: this.selectedLabelItemId,
2025-11-28 14:27:39 +08:00
// 子规则信息
subRules: this.ruleSets.map((ruleSet) => ({
subRuleId: ruleSet.subRuleId,
compositionId: ruleSet.selectedCompositionId,
ruleResourceId: ruleSet.ruleResourceId
2025-11-26 18:25:40 +08:00
})),
2025-11-28 14:27:39 +08:00
positions: [],
// 提示词内容整合为promptWord字段
promptWord: this.promptWord.trim()
2025-11-26 18:25:40 +08:00
};
2025-11-28 14:27:39 +08:00
this.deletedSubRuleIds = [];
this.deletedPositionIds = [];
// 处理定位规则
this.ruleSets.forEach((ruleSet, ruleSetIndex) => {
2025-11-26 18:25:40 +08:00
ruleSet.positionRules.forEach(posRule => {
2025-11-28 14:27:39 +08:00
const positionData = {
2025-11-26 18:25:40 +08:00
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,
2025-11-28 14:27:39 +08:00
subRuleIndex: ruleSetIndex,
2025-11-26 18:25:40 +08:00
shouldIncludeCharactersNum: posRule.shouldIncludeCharactersNum,
endText: posRule.endText
2025-11-28 14:27:39 +08:00
};
if (ruleSet.subRuleId) {
positionData.subRuleId = ruleSet.subRuleId;
}
submitData.positions.push(positionData);
2025-11-26 18:25:40 +08:00
});
});
2025-11-28 14:27:39 +08:00
console.log('提交数据:', submitData)
// 调用接口:区分新增和更新
const isUpdate = this.ruleSets.some(rule => rule.ruleSetId);
const apiMethod = isUpdate ? updateAnalysisRuleSet : addAnalysisRuleSet;
2025-11-26 18:25:40 +08:00
2025-11-28 14:27:39 +08:00
apiMethod(submitData)
2025-11-26 18:25:40 +08:00
.then(res => {
if (res.code === 200) {
2025-11-28 14:27:39 +08:00
this.$message.success(isUpdate ? '规则更新成功' : '规则保存成功');
this.loadRuleSets(); // 重新加载以获取最新ID
2025-11-26 18:25:40 +08:00
} else {
2025-11-28 14:27:39 +08:00
this.$message.error(res.msg || (isUpdate ? '规则更新失败' : '规则保存失败'));
2025-11-26 18:25:40 +08:00
}
})
.catch(err => {
console.error('保存规则失败:', err);
2025-11-28 14:27:39 +08:00
this.$message.error('操作失败,请重试');
2025-11-26 18:25:40 +08:00
});
},
2025-11-28 14:27:39 +08:00
2025-11-26 18:25:40 +08:00
/**
* 验证规则
*/
validateRules() {
for (let i = 0; i < this.ruleSets.length; i++) {
const ruleSet = this.ruleSets[i];
// 验证内容来源
2025-11-28 14:27:39 +08:00
if (!ruleSet.selectedCompositionId) {
2025-11-26 18:25:40 +08:00
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
2025-11-28 14:27:39 +08:00
this.loadTemplateInfo()
2025-11-26 18:25:40 +08:00
if (this.selectedLabelItemId) {
2025-11-28 14:27:39 +08:00
this.loadRuleSets()
2025-11-26 18:25:40 +08:00
}
}
}
}
</script>
<style scoped lang="scss">
2025-11-28 14:27:39 +08:00
/* 原有样式保持不变 */
2025-11-26 18:25:40 +08:00
.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;
2025-11-28 14:27:39 +08:00
.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;
}
}
2025-11-26 18:25:40 +08:00
.tree-search {
margin-bottom: 16px;
::v-deep .el-input__inner {
border-color: #dcdfe6;
border-radius: 4px;
&:focus {
2025-11-28 14:27:39 +08:00
border-color: #1f72ea; // 聚焦时边框色与表格交互元素一致
2025-11-26 18:25:40 +08:00
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%;
2025-11-28 14:27:39 +08:00
height: 100%; // 确保内容充满节点行高
2025-12-05 09:12:28 +08:00
> span:first-child {font-size: 15px !important;}
2025-11-28 14:27:39 +08:00
.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;
}
2025-11-26 18:25:40 +08:00
}
}
// 规则配置区域样式
.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;
}
2025-12-05 09:12:28 +08:00
.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;
}
}
2025-11-26 18:25:40 +08:00
.template-info {
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
h3 {
margin: 0;
color: #1F72EA;
font-size: 16px;
}
}
2025-11-28 14:27:39 +08:00
// 提示词表单样式
.prompt-form {
::v-deep .el-form-item {
margin-bottom: 15px;
}
::v-deep .el-textarea__inner {
2025-12-05 09:12:28 +08:00
min-height: 100%;
2025-11-28 14:27:39 +08:00
}
}
// 规则验证占位样式
.validation-placeholder {
text-align: center;
padding: 50px 0;
color: #999;
}
// 其他原有样式保持不变
2025-11-26 18:25:40 +08:00
.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;
}
}
2025-11-28 14:27:39 +08:00
.button-group {
display: flex;
flex-wrap: wrap;
gap: 10px;
padding: 10px 0;
}
.info-button {
min-width: 120px;
}
2025-11-26 18:25:40 +08:00
</style>