历史记录

This commit is contained in:
LHD_HY 2025-12-05 09:12:28 +08:00
parent e92e9d805e
commit 5c60586092
18 changed files with 1931 additions and 100 deletions

View File

@ -13,6 +13,14 @@ export function listAnalysisRuleSet(query) {
})
}
export function listAnalysisRuleSetHistory(query) {
return request({
url: '/smartBid/analysisRuleSet/list/history',
method: 'get',
params: query
})
}
// 2. 查询解析规则配置详情携带解析规则ID包含关联定位查询规则信息
export function getAnalysisRuleSetDetail(params) {
return request({

View File

@ -57,3 +57,22 @@ export function checkTemplateNameUnique(params) {
params: params
})
}
// 7. 发布模板
export function publishTemplate(templateId) {
return request({
url: `/smartBid/templateInfo/publish/${templateId}`,
method: 'post'
})
}
// 8. 查询模板历史版本列表
export function getTemplateHistoryVersions(templateId) {
return request({
url: '/smartBid/templateInfo/history/versions',
method: 'get',
params: {
templateId: templateId
}
})
}

View File

@ -681,6 +681,21 @@ export const dynamicRoutes = [
}
]
},
{
path: '/templateHistory',
component: Layout,
hidden: true,
permissions: ['analysis:rule:list'],
children: [
{
path: 'index',
component: () => import('@/views/template/templateInfo/components/TemplateHistory'),
name: 'TemplateHistory',
meta: { title: '模板历史详情', activeMenu: '/template', noCache: true }
}
]
},
]
// 防止连续点击多次路由报错

View File

@ -160,6 +160,7 @@
size="40%"
:before-close="handleDetailDrawerClose"
custom-class="custom-qualification-drawer qualification-drawer"
:append-to-body="false"
>
<QualificationDetailComp
ref="qualificationDetail"

View File

@ -208,7 +208,7 @@ export default {
// ID-
loadTreeData() {
const allItems = []; //
const pageSize = 10; //
const pageSize = 1000; //
let currentPage = 1; //
//
@ -537,6 +537,10 @@ export default {
width: 100%;
height: 100%; //
> span:first-child {
font-size: 15px !important;
}
.tree-node-actions {
opacity: 0;
transition: opacity 0.3s;

View File

@ -74,7 +74,6 @@
maxlength="128"
clearable
style="width:100%"
:disabled="type === 'edit'"
/>
</el-form-item>

View File

@ -76,6 +76,9 @@
<!-- 添加规则按钮 -->
<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"
@ -264,7 +267,6 @@
</el-form>
</div>
<!-- 提示词配置内容 -->
<!-- 提示词配置内容 -->
<div v-if="currentTab === 'promptConfig'">
<div class="button-group">
@ -310,7 +312,7 @@
type="textarea"
rows="15"
placeholder="请输入提示词内容"
style="width: 100%;"
style="height: 100%;"
></el-input>
</div>
@ -447,6 +449,9 @@ export default {
this.templateName = res.data.templateName || '';
const compositionList = res.data.compositionList || [];
console.log('detail',res.data)
//
const uniqueSources = [];
const seenIds = new Set();
@ -476,7 +481,7 @@ export default {
//
loadTreeData() {
const allItems = []; //
const pageSize = 10; //
const pageSize = 1000; //
let currentPage = 1; //
//
@ -1120,6 +1125,8 @@ export default {
width: 100%;
height: 100%; //
> span:first-child {font-size: 15px !important;}
.tree-node-actions {
opacity: 0;
transition: opacity 0.3s;
@ -1160,6 +1167,56 @@ export default {
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;
@ -1179,7 +1236,7 @@ export default {
}
::v-deep .el-textarea__inner {
min-height: 100px;
min-height: 100%;
}
}

View File

@ -136,21 +136,24 @@ export default {
this.$refs.analysisLabelRef.validate()
]);
console.log('ProjectFileData',projectFileData)
console.log('ProjectFileData', projectFileData)
// 2.
const formData = {
...basicData,
...analysisLabelData,
templateId: this.type === 'edit' ? this.templateId : null,
useState: '0',
useState: '1',
publishedStatus: '1',
files: [], // /
delFiles: [] //
delFiles: [], //
delComposition: [] // compositionId
};
// 3. (ProjectFile)
projectFileData.forEach(tab => {
// { formData: [], delComposition: [] }
const projectFormData = projectFileData.formData || projectFileData;
projectFormData.forEach(tab => {
// 3.1 compositionType
if (tab.fileList && tab.fileList.length > 0) {
const processedFiles = tab.fileList.map(file => {
@ -169,11 +172,10 @@ export default {
compositionName: file.compositionName,
compositionFileType: file.compositionFileType,
compositionType: tab.compositionType, // <-- Tab compositionType
compositionId: tab.compositionId // compositionId
};
}).filter(Boolean); // map null
// console.log('processedFiles', ...processedFiles); //
console.log('processedFiles', processedFiles);
formData.files.push(...processedFiles);
}
@ -183,8 +185,15 @@ export default {
}
});
// 4. / (SectionFile)
sectionFileData.forEach(tab => {
// delComposition
if (projectFileData.delComposition && projectFileData.delComposition.length > 0) {
formData.delComposition.push(...projectFileData.delComposition);
}
// 4. / (SectionFile)
// { formData: [], delComposition: [] }
const sectionFormData = sectionFileData.formData || sectionFileData;
sectionFormData.forEach(tab => {
// 4.1 compositionType
if (tab.fileList && tab.fileList.length > 0) {
const processedFiles = tab.fileList.map(file => {
@ -202,10 +211,10 @@ export default {
compositionName: file.compositionName,
compositionFileType: file.compositionFileType,
compositionType: tab.compositionType, // <-- Tab compositionType
compositionId: tab.compositionId // compositionId
};
}).filter(Boolean); // map null
// console.log('123123123', processedFiles);
formData.files.push(...processedFiles);
}
@ -215,6 +224,14 @@ export default {
}
});
// delComposition
if (sectionFileData.delComposition && sectionFileData.delComposition.length > 0) {
formData.delComposition.push(...sectionFileData.delComposition);
}
// delComposition
formData.delComposition = [...new Set(formData.delComposition)];
// --- ---
// 5.
@ -228,12 +245,14 @@ export default {
// }
// }
console.log('最终提交数据', formData);
// 6.
if (this.type === 'add') {
console.log('formData',formData)
console.log('formData', formData)
await addTemplateInfo(formData);
} else {
console.log('12345678',formData)
console.log('12345678', formData)
await updateTemplateInfo(formData);
}
@ -269,9 +288,9 @@ export default {
const res = await getTemplateInfoDetail({ templateId: this.templateId });
const detail = res.data || {};
console.log('detail',detail)
console.log('detail', detail)
//
// compositionId
this.$refs.basicInfoRef.setFormData(detail);
this.$refs.projectFileRef.setFormData(detail.projectFiles || []);
this.$refs.sectionFileRef.setFormData(detail.sectionFiles || []);
@ -284,7 +303,7 @@ export default {
mounted() {
//
this.$bus.$on('startUpload', this.handleStartUpload);
this.$bus.$off('endUpload', this.handleEndUpload);
this.$bus.$off('endUpload', this.handleEndUpload); //
this.$bus.$on('endUpload', this.handleEndUpload);
// typetemplateId

View File

@ -0,0 +1,179 @@
<template>
<div class="app-container">
<!-- 页面头部仅保留返回按钮 -->
<div class="content-header">
<div class="header-actions">
<el-button class="reset-btn" @click="handleBack()" >返回</el-button>
</div>
</div>
<!-- 页面主体分两个区域 -->
<div class="content-body">
<!-- 区域1模板信息 -->
<div class="section-pane template-info-pane">
<TemplateInfo ref="templateInfoRef" />
</div>
<!-- 区域2版本内容新增- 修复添加v-if控制渲染时机确保templateId有值 -->
<div class="section-pane version-content-pane" v-if="templateId">
<!-- 将获取到的 analysisLabelId 通过 props 传递下去 -->
<VersionContent
:version-list="versionList"
:analysis-label-id="analysisLabelId"
:template-id="templateId"
/>
</div>
</div>
</div>
</template>
<script>
import { decryptWithSM4 } from '@/utils/sm'
import TemplateInfo from './child/TemplateInfo.vue'
import VersionContent from './child/VersionContent.vue'
import { getTemplateInfoDetail, getTemplateHistoryVersions } from '@/api/template/templateInfo/templateInfo'
export default {
name: 'TemplateHistory',
components: {
TemplateInfo,
VersionContent
},
data() {
return {
templateId: null,
versionList: [],
analysisLabelId: '' // ID
}
},
methods: {
//
handleBack() {
const obj = { path: "/templateInfo/index" };
this.$tab.closeOpenPage(obj);
},
//
async loadTemplateHistoryData() {
if (!this.templateId) return
try {
//
const [templateInfoRes, versionListRes] = await Promise.all([
getTemplateInfoDetail({ templateId: this.templateId }),
getTemplateHistoryVersions(this.templateId)
])
const templateInfo = templateInfoRes.data || {}
this.versionList = versionListRes.data || []
// analysisLabelId
// API
this.analysisLabelId = templateInfo.analysisLabelId || ''
console.log('模板详情:', templateInfo)
console.log('版本列表:', this.versionList)
console.log('获取到的 analysisLabelId:', this.analysisLabelId)
// TemplateInfo
if (this.$refs.templateInfoRef) {
this.$refs.templateInfoRef.setFormData(templateInfo)
}
} catch (error) {
console.error('加载模板历史失败:', error)
this.$message.error('加载数据失败,请刷新页面重试。')
}
}
},
mounted() {
// templateId
const { templateId: encryptedTemplateId } = this.$route.query
if (encryptedTemplateId) {
try {
this.templateId = decryptWithSM4(encryptedTemplateId)
// templateId
if (this.templateId === 'null' || this.templateId === 'undefined') {
this.templateId = null
} else {
//
this.loadTemplateHistoryData()
}
} catch (e) {
console.error('解析 templateId 失败:', e)
this.$message.error('参数错误,无法加载数据')
}
}
}
}
</script>
<style scoped lang="scss">
/* 样式部分保持不变 */
.app-container {
padding: 24px;
background: linear-gradient(180deg, #F1F6FF 20%, #E5EFFF 100%);
min-height: 100vh;
overflow: hidden;
}
.content-header {
display: flex;
justify-content: right;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 15px;
}
.header-actions {
display: flex;
gap: 12px;
}
.content-body {
display: flex;
flex-direction: column;
gap: 24px;
height: calc(100vh + 120px);
overflow-y: auto;
padding-right: 8px;
}
.section-pane {
background: #fff;
border-radius: 16px;
box-shadow: 0px 4px 20px rgba(31, 35, 55, 0.08);
padding: 24px;
}
.reset-btn {
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
padding: 12px 24px;
border-radius: 6px;
box-shadow: 0px 4px 12px 0px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
&:hover {
background: #f5f7fa;
color: #409EFF;
}
}
.template-info-pane {
max-height: 240px; //
overflow-y: auto;
scrollbar-width: thin; //
scrollbar-color: #409EFF #f5f5f5;
}
//
.version-content-pane {
flex: 1; //
min-height: 400px; //
overflow: hidden; //
}
</style>

View File

@ -18,6 +18,7 @@
<el-col :span="8">
<el-form-item label="解析标签组" prop="analysisLabelId">
<el-select
:disabled="currentVersion !== null "
v-model="formData.analysisLabelId"
placeholder="请选择解析标签组"
clearable
@ -53,10 +54,10 @@ export default {
},
data() {
return {
currentVersion: '',
labelOptions: [], //
labelLoading: false, //
formData: {
analysisLabelId: '',
analysisLabelName: '',
},
@ -108,6 +109,7 @@ export default {
//
setFormData(data) {
this.currentVersion = data.currentVersion
this.formData = {
analysisLabelId: data.analysisLabelId || '',
analysisLabelName: data.analysisLabelName || '',

View File

@ -19,7 +19,8 @@
<el-input
v-model="formData.templateName"
placeholder="请输入"
maxlength="128"
show-word-limit
maxlength="64"
></el-input>
</el-form-item>
</el-col>
@ -78,7 +79,7 @@ export default {
rules: {
templateName: [
{ required: true, message: '请输入模板名称', trigger: 'blur' },
{ max: 128, message: '模板名称不能超过128个字符', trigger: 'blur' }
{ max: 128, message: '模板名称不能超过64个字符', trigger: 'blur' }
],
industryType: [
{ required: true, message: '请选择行业类型', trigger: 'change' }

View File

@ -44,9 +44,10 @@
<el-input
v-model="tab.formData.fileName"
placeholder="请输入"
maxlength="128"
@input="handleFileNameChange(index)"
@change="handleFileNameChange(index)"
show-word-limit
maxlength="64"
></el-input>
</el-form-item>
</el-col>
@ -111,6 +112,8 @@ export default {
},
data() {
return {
delComposition: [], // compositionId
tabFiles: [],
uploadType: 'doc、docx、pdf、xls、xlsx、wps',
maxFileTips: '50MB',
fileUploadRule: { fileUploadType: 'project_file', suffix: 'template' },
@ -118,6 +121,7 @@ export default {
fileTabs: [
{
formData: {
compositionId: '', // compositionId
compositionType: '1',
fileName: '',
compositionFileType: '',
@ -160,6 +164,7 @@ export default {
handleAddTab() {
this.fileTabs.push({
formData: {
compositionId: '', // TabID
compositionType: '1',
fileName: '',
compositionFileType: '',
@ -175,6 +180,15 @@ export default {
this.$message.warning('至少保留一个文件')
return
}
// TabcompositionIdID
const tab = this.fileTabs[index]
if (this.isEditMode && tab.formData.compositionId) {
if (!this.delComposition.includes(tab.formData.compositionId)) {
this.delComposition.push(tab.formData.compositionId)
}
}
this.fileTabs.splice(index, 1)
// tab
if (this.activeTabIndex === index.toString()) {
@ -184,8 +198,19 @@ export default {
},
handleFileChange(files, index) {
const oldFileList = this.fileTabs[index].formData.fileList
this.fileTabs[index].formData.fileList = files;
// compositionIdID
if (this.isEditMode && oldFileList.length > 0 && this.fileTabs[index].formData.compositionId) {
const compositionId = this.fileTabs[index].formData.compositionId
if (!this.delComposition.includes(compositionId)) {
this.delComposition.push(compositionId)
}
// TabcompositionId
this.fileTabs[index].formData.compositionId = ''
}
if (files.length > 0 && files[0].status === 'success') {
const currentTab = this.fileTabs[index];
files[0].compositionName = currentTab.formData.fileName;
@ -204,6 +229,17 @@ export default {
delFileList.push(filePath)
}
}
// compositionIdID
if (this.isEditMode && this.fileTabs[index].formData.compositionId) {
const compositionId = this.fileTabs[index].formData.compositionId
if (!this.delComposition.includes(compositionId)) {
this.delComposition.push(compositionId)
console.log('ttt',this.delComposition)
}
// TabcompositionId
this.fileTabs[index].formData.compositionId = ''
}
},
handleFileNameChange(index) {
@ -227,15 +263,20 @@ export default {
//
const index = tab.index;
console.log(`切换到了第 ${index + 1} 个 tab`);
this.setFormData(this.tabFiles)
},
setFormData(projectFiles) {
this.tabFiles = projectFiles
//
this.delComposition = []
// fileTabs
this.fileTabs = [];
if (!projectFiles || projectFiles.length === 0) {
this.fileTabs.push({
formData: {
compositionId: '', //
compositionType: '1',
fileName: '',
compositionFileType: '',
@ -265,6 +306,7 @@ export default {
this.fileTabs.push({
formData: {
compositionId: file.compositionId || '', // compositionId
compositionType: '1',
fileName: formattedFile.compositionName,
compositionFileType: formattedFile.compositionFileType,
@ -295,9 +337,17 @@ export default {
}
})
})
allFormData.push({ ...this.fileTabs[i].formData })
allFormData.push({
...this.fileTabs[i].formData,
//
isDeleted: false
})
}
resolve(allFormData)
// compositionId
resolve({
formData: allFormData,
delComposition: [...new Set(this.delComposition)] //
})
} catch (error) {
reject(error)
}

View File

@ -0,0 +1,623 @@
<template>
<!-- 右侧弹出抽屉 -->
<el-drawer
title="规则详情"
:visible="value"
:direction="'rtl'"
:size="800"
@close="handleClose"
:append-to-body="true"
>
<div class="analysis-rule-container" style="height: calc(100vh - 140px); overflow-y: auto;">
<el-row :gutter="20">
<!-- 右侧解析规则配置区域去除左侧树状图 -->
<el-col :span="24">
<div class="rule-config-container">
<!-- 标签切换禁用切换功能 -->
<el-tabs v-model="currentTab" @tab-click.prevent="handleTabClick">
<el-tab-pane label="规则配置" name="ruleConfig"></el-tab-pane>
<el-tab-pane label="提示词配置" name="promptConfig"></el-tab-pane>
</el-tabs>
<!-- 规则配置内容 -->
<div v-if="currentTab === 'ruleConfig'">
<el-form ref="ruleForm" :model="ruleForm" label-width="100px" class="rule-sets">
<!-- 规则集列表 -->
<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>
<div class="rule-set-content">
<!-- 内容来源单选禁用切换 -->
<el-form-item label="内容来源">
<el-radio-group
v-model="ruleSet.selectedCompositionId"
class="content-source-radios"
@click.prevent
@change.prevent
>
<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.prevent="handleRuleTypeChange(positionRule, posIndex, index)"
placeholder="请选择规则类型"
style="width: 280px;"
@click.prevent
>
<el-option
v-for="option in ruleSetTypeOptions"
:key="option.value"
:label="option.label"
:value="option.value"
></el-option>
</el-select>
<!-- 规则类型对应的字段输入框添加readonly -->
<div class="rule-fields" v-if="positionRule.ruleSetType === 'headSpecifyLength'">
<el-input
v-model="positionRule.specifiedWordCount"
placeholder="指定字数"
type="number"
style="width: 150px; margin-left: 10px;"
readonly
></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;"
readonly
></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;"
readonly
></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;"
readonly
></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;"
readonly
></el-input>
<el-input
v-model="positionRule.specifiedKeywords"
placeholder="指定关键词"
style="width: 180px; margin-left: 10px;"
readonly
></el-input>
<el-input
v-model="positionRule.afterCharactersNum"
placeholder="后面字数"
type="number"
style="width: 120px; margin-left: 10px;"
readonly
></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;"
readonly
></el-input>
<el-input
v-model="positionRule.specifiedWordCountLength"
placeholder="指定字数长度"
type="number"
style="width: 150px; margin-left: 10px;"
readonly
></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;"
readonly
></el-input>
<el-input
v-model="positionRule.specifiedText"
placeholder="指定文字"
style="width: 200px; margin-left: 10px;"
readonly
></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;"
readonly
></el-input>
<el-input
v-model="positionRule.shouldIncludeCharactersNum"
placeholder="需要包含的字数"
type="number"
style="width: 150px; margin-left: 10px;"
readonly
></el-input>
<el-input
v-model="positionRule.endText"
placeholder="尾部文字"
style="width: 150px; margin-left: 10px;"
readonly
></el-input>
</div>
</el-form-item>
</div>
</div>
</div>
</div>
</el-form>
</div>
<!-- 提示词配置内容 -->
<div v-if="currentTab === 'promptConfig'">
<!-- 提示词文本框添加readonly -->
<el-input
v-model="promptWord"
type="textarea"
rows="15"
placeholder="请输入提示词内容"
style="width: 100%;"
readonly
></el-input>
</div>
</div>
</el-col>
</el-row>
</div>
<div class="back-container">
<el-button
type="default"
size="small"
@click="handleClose"
class="back-btn"
>
关闭
</el-button>
</div>
</el-drawer>
</template>
<script>
import {
listAnalysisRuleSetHistory //
} from '@/api/template/analysisRuleSet/analysisRuleSet'
import { getTemplateInfoDetail } from '@/api/template/templateInfo/templateInfo'
export default {
name: 'RuleDetailDrawer',
props: {
// /
value: {
type: Boolean,
default: false
},
// ID
analysisLabelItemId: {
type: [String, Number],
default: ''
},
//
currentVersion: {
type: String,
default: ''
},
// ID
templateId: {
type: [String, Number],
default: ''
}
},
data() {
return {
promptWord: '',
ruleForm: {},
//
currentTab: 'ruleConfig',
//
contentSources: [],
//
ruleSets: [],
//
ruleSetTypeOptions: []
}
},
watch: {
//
value(newVal) {
console.log('newVal', newVal)
if (newVal) {
// 使 $nextTick props
this.$nextTick(() => {
if (this.analysisLabelItemId && this.currentVersion && this.templateId) {
console.log('开始加载规则详情数据')
this.loadData();
} else {
console.warn('打开抽屉失败:缺少必要的参数。', {
analysisLabelItemId: this.analysisLabelItemId,
currentVersion: this.currentVersion,
templateId: this.templateId
});
}
});
} else {
//
}
},
// props
'$props': {
handler() {
if (this.value && this.analysisLabelItemId && this.currentVersion && this.templateId) {
this.loadData();
}
},
deep: true
}
},
methods: {
//
handleClose() {
this.$emit('input', false);
this.$emit('update:visible', false);
this.$emit('close');
},
//
loadData() {
//
this.loadTemplateInfo().then(() => {
//
this.loadRuleSets();
});
//
this.loadRuleSetTypeDict();
},
//
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);
});
},
//
loadTemplateInfo() {
return new Promise((resolve) => {
if (this.templateId) {
getTemplateInfoDetail({ templateId: this.templateId })
.then(res => {
if (res.code === 200) {
const compositionList = res.data.compositionList || [];
//
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;
}
resolve();
})
.catch(err => {
console.error('加载模板信息失败:', err);
resolve();
});
} else {
resolve();
}
});
},
// 使
loadRuleSets() {
if (this.analysisLabelItemId && this.templateId && this.currentVersion) {
console.log('currentVersion', this.currentVersion)
listAnalysisRuleSetHistory({
templateId: this.templateId,
analysisLabelItemId: this.analysisLabelItemId,
currentVersion: this.currentVersion //
}).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 }))
: []
};
this.ruleSets.push(ruleSetItem);
});
});
}
}).catch(err => {
console.error('加载历史规则集失败:', err);
});
}
},
//
handleRuleTypeChange(positionRule) {
const allFields = [
'specifiedWordCount', 'specifiedText', 'specifiedKeywords',
'beforeCharactersNum', 'afterCharactersNum', 'specifiedWordCountLength',
'beforeSpecifiedCharactersNum', 'firstText', 'shouldIncludeCharactersNum', 'endText'
];
allFields.forEach(field => {
if (!positionRule.hasOwnProperty(field)) {
this.$set(positionRule, field, '');
}
});
},
// @tab-click.prevent
handleTabClick(tab) {
this.currentTab = tab.name;
},
// prevent
handleLabelIdClick() {},
handleLabelNameClick() {},
handleSegmentIdClick() {},
handleSegmentNameClick() {},
handleExtractedContentClick() {}
}
}
</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);
}
}
}
.analysis-rule-container {
height: calc(100vh - 250px);
overflow: hidden;
padding: 16px;
}
//
.rule-config-container {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
padding: 20px;
height: calc(100% - 40px);
overflow-y: auto;
box-sizing: border-box;
}
.rule-sets {
::v-deep .el-form-item {
margin-bottom: 15px;
}
::v-deep .el-form-item__label {
text-align: left;
color: #303133 !important; //
}
}
.rule-set-item {
background: #fff;
border-radius: 6px;
padding: 15px;
margin-bottom: 20px;
position: relative;
border: 1px solid #f0f0f0;
}
.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-content {
margin-top: 15px;
}
.content-source-radios {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-top: 5px;
}
//
::v-deep .el-radio {
cursor: default !important; //
}
::v-deep .el-radio__label {
color: #303133 !important;
opacity: 1 !important;
}
::v-deep .el-radio__input.is-checked .el-radio__inner {
background-color: #409EFF !important;
border-color: #409EFF !important;
}
//
::v-deep .el-select {
cursor: default !important;
}
::v-deep .el-select__inner {
color: #303133 !important;
border-color: #dcdfe6 !important;
background-color: #fff !important;
opacity: 1 !important;
}
::v-deep .el-select__icon {
color: #c0c4cc !important;
}
// readonly
::v-deep .el-input__inner[readonly] {
color: #303133 !important;
border-color: #dcdfe6 !important;
background-color: #fff !important;
opacity: 1 !important;
cursor: default !important;
}
.position-rules {
margin-top: 15px;
}
.position-rule-item {
margin-bottom: 15px;
background: #fff;
padding: 10px;
border-radius: 4px;
}
.position-rule-item ::v-deep .el-form-item__content {
margin-left: 0 !important;
}
.rule-fields {
display: inline-flex;
align-items: center;
margin-top: 10px;
}
.button-group {
display: flex;
flex-wrap: wrap;
gap: 10px;
padding: 10px 0;
margin-bottom: 15px;
}
.info-button {
min-width: 120px;
cursor: default !important; //
background-color: #ecf5ff !important;
border-color: #b3d8ff !important;
color: #409EFF !important;
}
// hover
::v-deep .el-button.info-button:hover {
background-color: #ecf5ff !important;
border-color: #b3d8ff !important;
color: #409EFF !important;
}
</style>

View File

@ -44,9 +44,10 @@
<el-input
v-model="tab.formData.fileName"
placeholder="请输入"
maxlength="128"
@input="handleFileNameChange(index)"
@change="handleFileNameChange(index)"
show-word-limit
maxlength="64"
></el-input>
</el-form-item>
</el-col>
@ -112,6 +113,8 @@ export default {
},
data() {
return {
delComposition: [], // compositionId
tabFiles: [],
uploadType: 'doc、docx、pdf、xls、xlsx、wps',
maxFileTips: '50MB',
fileUploadRule: { fileUploadType: 'section_file', suffix: 'template' },
@ -119,6 +122,7 @@ export default {
fileTabs: [
{
formData: {
compositionId: '', // compositionId
compositionType: '2',
fileName: '',
compositionFileType: '',
@ -161,6 +165,7 @@ export default {
handleAddTab() {
this.fileTabs.push({
formData: {
compositionId: '', // TabID
compositionType: '2',
fileName: '',
compositionFileType: '',
@ -176,6 +181,15 @@ export default {
this.$message.warning('至少保留一个文件')
return
}
// TabcompositionIdID
const tab = this.fileTabs[index]
if (this.isEditMode && tab.formData.compositionId) {
if (!this.delComposition.includes(tab.formData.compositionId)) {
this.delComposition.push(tab.formData.compositionId)
}
}
this.fileTabs.splice(index, 1)
// tab
if (this.activeTabIndex === index.toString()) {
@ -184,8 +198,19 @@ export default {
},
handleFileChange(files, index) {
const oldFileList = this.fileTabs[index].formData.fileList
this.fileTabs[index].formData.fileList = files;
// compositionIdID
if (this.isEditMode && oldFileList.length > 0 && this.fileTabs[index].formData.compositionId) {
const compositionId = this.fileTabs[index].formData.compositionId
if (!this.delComposition.includes(compositionId)) {
this.delComposition.push(compositionId)
}
// TabcompositionId
this.fileTabs[index].formData.compositionId = ''
}
if (files.length > 0 && files[0].status === 'success') {
const currentTab = this.fileTabs[index];
files[0].compositionName = currentTab.formData.fileName;
@ -205,6 +230,16 @@ export default {
delFileList.push(filePath)
}
}
// compositionIdID
if (this.isEditMode && this.fileTabs[index].formData.compositionId) {
const compositionId = this.fileTabs[index].formData.compositionId
if (!this.delComposition.includes(compositionId)) {
this.delComposition.push(compositionId)
}
// TabcompositionId
this.fileTabs[index].formData.compositionId = ''
}
},
handleFileNameChange(index) {
@ -228,15 +263,20 @@ export default {
//
const index = tab.index;
console.log(`切换到了第 ${index + 1} 个 tab`);
this.setFormData(this.tabFiles)
},
setFormData(sectionFiles) {
this.tabFiles = sectionFiles
//
this.delComposition = []
// fileTabs
this.fileTabs = [];
if (!sectionFiles || sectionFiles.length === 0) {
this.fileTabs.push({
formData: {
compositionId: '', //
compositionType: '2',
fileName: '',
compositionFileType: '',
@ -265,6 +305,7 @@ export default {
this.fileTabs.push({
formData: {
compositionId: file.compositionId || '', // compositionId
compositionType: '2',
fileName: formattedFile.compositionName,
compositionFileType: formattedFile.compositionFileType,
@ -295,9 +336,16 @@ export default {
}
})
})
allFormData.push({ ...this.fileTabs[i].formData })
allFormData.push({
...this.fileTabs[i].formData,
isDeleted: false
})
}
resolve(allFormData)
// compositionId
resolve({
formData: allFormData,
delComposition: [...new Set(this.delComposition)] //
})
} catch (error) {
reject(error)
}

View File

@ -0,0 +1,127 @@
<template>
<div class="template-info">
<h3 class="section-title">模板信息</h3>
<el-form :model="formData" label-width="140px" class="info-form" size="medium">
<el-row :gutter="32">
<el-col :span="8"><el-form-item label="模板名称"><el-input v-model="formData.templateName" disabled class="info-input" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="行业类型"><el-input v-model="formData.industryType" disabled class="info-input" /></el-form-item></el-col>
<!-- 新增项目文件组成 -->
<el-col :span="8"><el-form-item label="项目文件组成"><el-input v-model="formData.projectFileNames" disabled class="info-input" /></el-form-item></el-col>
<!-- 新增标段/标包文件组成 -->
<el-col :span="8"><el-form-item label="标段/标包文件组成"><el-input v-model="formData.sectionFileNames" disabled class="info-input" /></el-form-item></el-col>
<el-col :span="8">
<el-form-item label="解析标签组">
<!-- 显示加载状态 -->
<el-input
v-model="formData.analysisLabelName"
disabled
class="info-input"
style="color: #1F72EA; font-weight: 500;"
/>
</el-form-item>
</el-col>
<el-col :span="8"><el-form-item label="最近更新时间"><el-input v-model="formData.updateTime" disabled class="info-input" /></el-form-item></el-col>
</el-row>
</el-form>
</div>
</template>
<script>
import { listAnalysisLabel } from '@/api/template/analysisLabel/analysisLabel'
export default {
name: 'TemplateInfo',
data() {
return {
formData: {
templateName: '',
industryType: '',
projectFileNames: '', //
sectionFileNames: '', // /
currentVersion: '',
publishStatusText: '',
updateTime: '',
templateDesc: '',
analysisLabelName: '',
analysisLabelId: ''
}
}
},
methods: {
/**
* 接收父组件传递的模板数据并格式化
* @param {Object} data - 父组件传递的模板详情数据 (来自 getTemplateInfoDetail)
*/
async setFormData(data) {
const compositionList = data.compositionList || [];
// (compositionType === '1')
const projectFiles = compositionList
.filter(item => item.compositionType === '1')
.map(item => item.fileName)
.join('、') || '无';
// / (compositionType === '2')
const sectionFiles = compositionList
.filter(item => item.compositionType === '2')
.map(item => item.fileName)
.join('、') || '无';
//
this.formData = {
...this.formData, //
templateName: data.templateName || '-',
industryType: data.industryType || '-',
projectFileNames: projectFiles,
sectionFileNames: sectionFiles,
currentVersion: data.currentVersion || '-',
publishStatusText: data.publishedStatus === '1' ? '已发布' : '未发布',
updateTime: data.updateTime || '-',
templateDesc: data.templateDesc || '无',
analysisLabelId: data.analysisLabelId || ''
};
// analysisLabelId
if (this.formData.analysisLabelId) {
try {
// API
// listAnalysisLabelID
const response = await listAnalysisLabel({
// APIID
analysisLabelId: this.formData.analysisLabelId
});
console.log('asdasd',response)
//
if (response.code === 200 && response.rows && response.rows.length > 0) {
console.log('asdasd',response)
this.formData.analysisLabelName = response.rows[0].analysisLabelName || '无';
} else {
this.formData.analysisLabelName = '无';
}
} catch (error) {
console.error('查询解析标签失败:', error);
this.formData.analysisLabelName = '查询失败';
}
} else {
// ID
this.formData.analysisLabelName = '无';
}
}
}
}
</script>
<style scoped lang="scss">
/* 样式部分保持不变 */
.template-info { width: 100%; }
.section-title { font-size: 16px; font-weight: 600; color: #303133; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid #ebeef5; }
.info-form { width: 100%; }
.info-input { background-color: #f8faff; border-color: #e6f7ff; border-radius: 8px; ::v-deep .el-input__inner { background-color: #f8faff; color: #303133; } }
.info-textarea { background-color: #f8faff; border-color: #e6f7ff; border-radius: 8px; ::v-deep .el-textarea__inner { background-color: #f8faff; border-color: #e6f7ff; color: #303133; resize: none; } }
@media (max-width: 768px) {
.info-form .el-row { flex-direction: column; }
.info-form .el-col { width: 100% !important; margin-bottom: 12px; }
}
</style>

View File

@ -0,0 +1,632 @@
<template>
<div class="version-content">
<!-- 左侧版本记录时间线 -->
<div class="version-timeline">
<h3 class="section-title">版本记录</h3>
<div class="timeline-list">
<div
v-for="(version, index) in filteredVersionList"
:key="version.version"
class="timeline-item"
:class="{ 'active': currentVersion === version.version }"
@click="handleVersionClick(version.version)"
>
<div class="timeline-dot"></div>
<div class="timeline-line" v-if="index < filteredVersionList.length - 1"></div>
<div class="timeline-content">
<div class="version-number">{{ version.version }}</div>
<div class="version-time">{{ formatDate(version.updateTime) }}</div>
</div>
</div>
<div v-if="filteredVersionList.length === 0" class="empty-version">
暂无有效版本记录
</div>
</div>
</div>
<!-- 右侧版本规则详情 -->
<div class="version-detail">
<h3 class="section-title">
版本规则详情
<span class="current-version" v-if="currentVersion"> - {{ currentVersion }}</span>
</h3>
<div class="detail-content">
<!-- 内容区域使用 flex 布局使其占满高度 -->
<el-row :gutter="20" class="rule-layout">
<!-- 左侧树状图区域 -->
<el-col :span="6">
<div class="tree-container">
<el-input
placeholder="搜索标签项"
v-model="treeSearchKey"
class="tree-search"
clearable
size="small"
/>
<div
class="tree-wrapper"
v-loading="isTreeLoading"
element-loading-text="加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(255, 255, 255, 0.8)"
>
<el-tree
ref="labelItemTree"
:data="treeData"
:props="treeProps"
node-key="analysisLabelItemId"
:filter-node-method="filterTreeNode"
default-expand-all
@node-click="handleTreeNodeClick"
@current-change="handleTreeNodeChange"
v-if="treeData.length > 0"
>
<template #default="{ node, data }">
<span class="tree-node-content">
<span>{{ node.label }}</span>
</span>
</template>
</el-tree>
<div v-else-if="!isTreeLoading" class="empty-tree">暂无树节点数据</div>
</div>
</div>
</el-col>
<!-- 右侧表格区域 -->
<el-col :span="18">
<div>
<div
class="table-wrapper"
v-loading="!isTableReady"
element-loading-text="加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(255, 255, 255, 0.8)"
>
<TableModel
v-if="isTableReady"
:showOperation="true"
:showRightTools="false"
:showBtnCrews="false"
:showSearch="false"
ref="analysisLabelItemTableRef"
:columnsList="columnsItemList"
:request-api="listAnalysisLabelItem"
:sendParams="tableSendParams"
>
<!-- 表格标题 -->
<template v-slot:tableTitle>
<h3>标签项列表</h3>
</template>
<!-- 表格操作按钮完全移除新增按钮 -->
<template v-slot:tableActions>
<!-- 此处留白 -->
</template>
<!-- 上级标签显示 -->
<template v-slot:parentName="{ data }">
<span>
{{ getParentName(data.parentId) || '无' }}
</span>
</template>
<!-- 层级显示 -->
<template v-slot:analysisLevel="{ data }">
<span>
{{ levelMap[data.analysisLevel] || data.analysisLevel }}
</span>
</template>
<template v-slot:analysisSort="{ data }">
<span>
{{ data.analysisSort }}
</span>
</template>
<!-- 操作按钮-->
<template slot="handle" slot-scope="{ data }">
<el-button
type="text"
class="action-btn view-btn"
@click="handleView(data)"
>
查看
</el-button>
</template>
</TableModel>
</div>
</div>
</el-col>
</el-row>
</div>
</div>
<RuleDetailDrawer
v-model="drawerVisible"
:analysis-label-item-id="currentAnalysisLabelItemId"
:current-version="currentVersion"
:template-id="templateId"
/>
</div>
</template>
<script>
import TableModel from '@/components/TableModel2'
import { listAnalysisLabelItem } from '@/api/template/analysisLabelItem/analysisLabelItem'
import { columnsItemList } from "../../../analysisLabel/components/config";
import RuleDetailDrawer from './RuleDetailDrawer.vue'
export default {
name: 'VersionContent',
components: {
RuleDetailDrawer,
TableModel
// 4Loading
},
props: {
versionList: {
type: Array,
default: () => []
},
// analysisLabelId
analysisLabelId: {
type: [String, Number],
default: ''
},
templateId: {
type: [String, Number],
required: true,
// templateIdnull/undefined
validator: (value) => {
return value !== null && value !== undefined
}
}
},
computed: {
//
filteredVersionList() {
return this.versionList.filter(item => item.version && item.version.trim());
}
},
data() {
return {
drawerVisible: false,
currentAnalysisLabelItemId: '',
currentVersion: null,
selectedVersion: null,
isTableReady: false, //
isTreeLoading: false, //
// --- ---
columnsItemList,
listAnalysisLabelItem, // API
//
treeData: [],
treeProps: {
label: 'analysisName',
children: 'children'
},
treeSearchKey: '',
labelItemMap: new Map(), // ID
//
tableSendParams: {
analysisId: '', // analysisLabelId
nodeIds: ''
},
//
levelMap: {
1: '一级',
2: '二级',
3: '三级'
}
}
},
watch: {
//
filteredVersionList(newList) {
// analysisLabelId
if (newList && newList.length > 0 && this.analysisLabelId) {
this.handleVersionClick(newList[0].version);
} else if (newList && newList.length > 0 && !this.analysisLabelId) {
// analysisLabelId100ms
setTimeout(() => {
if (this.analysisLabelId) {
this.handleVersionClick(newList[0].version);
}
}, 100);
} else {
this.currentVersion = null;
this.selectedVersion = null;
}
},
// analysisLabelId
analysisLabelId(newVal) {
if (newVal) {
this.tableSendParams.analysisId = newVal;
// 使 .then()
this.loadTreeData().then(() => {
// 使 $nextTick DOM
this.$nextTick(() => {
//
this.handleTreeNodeChange(null);
//
this.isTableReady = true;
});
}).catch(error => {
console.error('加载树数据失败:', error);
this.isTableReady = false;
this.isTreeLoading = false; // loading
});
}
},
//
treeSearchKey(val) {
if (this.$refs.labelItemTree) {
this.$refs.labelItemTree.filter(val);
}
},
// IDID
currentAnalysisLabelItemId(newVal) {
if (newVal && !this.drawerVisible) { //
this.$nextTick(() => {
this.drawerVisible = true;
console.log('通过watch打开抽屉ID:', newVal); // 便
});
}
}
},
methods: {
//
handleVersionClick(version) {
this.currentVersion = version;
this.selectedVersion = this.filteredVersionList.find(v => v.version === version) || null;
// analysisLabelIdtableSendParams.analysisId
if (this.analysisLabelId && this.tableSendParams.analysisId && (!this.treeData || this.treeData.length === 0)) {
this.loadTreeData().catch(error => {
console.error('切换版本时加载树数据失败:', error);
});
} else if (!this.tableSendParams.analysisId && this.analysisLabelId) {
// analysisLabelIdtableSendParams
this.tableSendParams.analysisId = this.analysisLabelId;
this.loadTreeData().catch(error => {
console.error('切换版本时加载树数据失败:', error);
});
}
},
//
loadTreeData() {
// 5true
this.isTreeLoading = true;
// analysisIdreject
if (!this.tableSendParams.analysisId) {
// analysisIdprops
if (this.analysisLabelId) {
this.tableSendParams.analysisId = this.analysisLabelId;
} else {
console.warn('analysisId is not provided.');
this.isTreeLoading = false; // loading
return Promise.reject(new Error('analysisId is empty'));
}
}
return new Promise((resolve, reject) => {
const allItems = [];
const pageSize = 1000;
let currentPage = 1;
const fetchPage = () => {
listAnalysisLabelItem({
analysisId: this.tableSendParams.analysisId,
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);
this.isTreeLoading = false; // loading
resolve(); // resolve Promise
}
} else {
this.isTreeLoading = false; // loading
reject(new Error(`获取树数据失败: ${res.msg || '未知错误'}`));
}
}).catch(error => {
this.isTreeLoading = false; // loading
reject(error); // reject Promise
});
};
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), item.analysisName);
});
},
// parentId
getParentName(parentId) {
if (!parentId) return '';
return this.labelItemMap.get(String(parentId)) || '未知';
},
//
filterTreeNode(value, data) {
if (!value) return true;
return data.analysisName.includes(value) || (data.analysisCode && data.analysisCode.includes(value));
},
//
handleTreeNodeClick(data) {
const allNodeIds = this.collectAllNodeIds(data);
this.tableSendParams.nodeIds = allNodeIds;
this.$nextTick(() => {
this.handleQuery();
});
},
//
handleTreeNodeChange(currentNode) {
if (currentNode) {
const allNodeIds = this.collectAllNodeIds(currentNode);
this.tableSendParams.nodeIds = allNodeIds;
} else {
this.tableSendParams.nodeIds = ''; //
}
this.$nextTick(() => {
this.handleQuery();
});
},
// ID
collectAllNodeIds(node) {
if (!node || !node.analysisLabelItemId) return '';
const currentId = String(node.analysisLabelItemId);
const ids = [currentId];
if (node.children && node.children.length > 0) {
node.children.forEach(child => {
const childIds = this.collectAllNodeIds(child);
if (childIds) {
ids.push(...childIds.split(',').filter(id => id));
}
});
}
return [...new Set(ids)].join(',');
},
//
handleView(data) {
console.log('点击查看分析标签项ID为:', data.analysisLabelItemId); //
// 1.
this.drawerVisible = false;
// 2. ID使$set
this.$set(this, 'currentAnalysisLabelItemId', data.analysisLabelItemId);
// 3. watch
this.$nextTick(() => {
this.drawerVisible = true;
console.log('手动打开抽屉drawerVisible:', this.drawerVisible);
});
},
//
handleQuery() {
if (this.$refs.analysisLabelItemTableRef && this.isTableReady) {
this.$refs.analysisLabelItemTableRef.getTableList();
}
},
//
formatDate(dateString, fmt = 'YYYY-MM-DD HH:mm:ss') {
if (!dateString) return '';
const date = new Date(dateString);
if (isNaN(date.getTime())) return '';
const o = {
'M+': date.getMonth() + 1,
'D+': date.getDate(),
'H+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds(),
'q+': Math.floor((date.getMonth() + 3) / 3),
'S': date.getMilliseconds()
};
if (/(Y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (const k in o) {
if (new RegExp('(' + k + ')').test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));
}
}
return fmt;
}
},
mounted() {
// tableSendParams.analysisId
if (this.analysisLabelId) {
this.tableSendParams.analysisId = this.analysisLabelId;
}
}
}
</script>
<style scoped lang="scss">
.version-content {
display: flex;
gap: 30px;
height: 100%;
min-height: 600px;
overflow-y: auto;
padding-bottom: 20px; /* 底部预留空间 */
}
/* --- 左侧时间线样式 --- */
.version-timeline {
min-width: 220px;
flex: 1;
border-right: 1px solid #ebeef5;
overflow-y: auto;
padding-right: 60px;
min-height: 100%;
}
.timeline-list { position: relative; padding-left: 30px; }
.timeline-item { position: relative; padding-bottom: 25px; cursor: pointer; transition: all 0.3s ease; }
.timeline-item:hover .timeline-content .version-number { color: #409EFF; }
.timeline-dot { position: absolute; left: -29px; top: 4px; width: 12px; height: 12px; border-radius: 50%; background-color: #dcdfe6; transition: all 0.3s ease; z-index: 1; }
.timeline-line { position: absolute; left: -23px; top: 16px; width: 2px; height: calc(100% - 12px); background-color: #ebeef5; z-index: 0; }
.timeline-content { position: relative; z-index: 2; }
.version-number { font-size: 15px; font-weight: 500; color: #303133; transition: color 0.3s ease; }
.version-time { font-size: 12px; color: #909399; margin-top: 4px; }
.empty-version { color: #909399; font-size: 14px; padding: 20px 0; }
.timeline-item.active .timeline-dot { background-color: #409EFF; box-shadow: 0 0 0 4px rgba(64, 158, 255, 0.2); }
.timeline-item.active .timeline-content .version-number { color: #409EFF; }
/* --- 右侧详情区域样式 --- */
.version-detail {
overflow: hidden;
max-width: calc(100% - 250px);
flex: 4;
padding-left: 10px;
display: flex;
flex-direction: column;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid #ebeef5;
}
.current-version {
font-size: 14px;
color: #409EFF;
font-weight: normal;
margin-left: 8px;
}
/* --- 集成的树和表格样式 --- */
.rule-layout {
display: flex;
height: 100%;
min-height: 450px;
}
.rule-layout > .el-col {
display: flex;
flex-direction: column;
min-height: 0;
}
.tree-container {
margin-top: 6px;
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
padding: 16px;
flex-grow: 1; /* 让树容器占满列的高度 */
display: flex;
overflow-y: auto;
flex-direction: column;
box-sizing: border-box;
max-height: calc(100vh - 270px);
}
.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);
}
}
}
/* 修复6树容器样式优化 */
.tree-wrapper {
flex: 1;
overflow-y: auto;
position: relative;
min-height: 300px;
}
.el-tree {
width: 100%;
background: transparent;
&::-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: 36px; &:hover { background-color: #f5f7fa; } }
::v-deep .el-tree-node.is-current > .el-tree-node__content { background-color: #e6f7ff; font-weight: 400; font-size: 10px; }
}
.tree-node-content { display: flex; justify-content: space-between; align-items: center; width: 100%; height: 100%; > span:first-child {font-size: 15px !important;
}}
/* 空树状态样式 */
.empty-tree {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: #909399;
font-size: 14px;
}
/* 表格容器样式 */
.table-wrapper {
background-color: #fff;
position: relative;
padding-bottom: 16px;
max-height: calc(100vh - 270px);
}
/* 查看按钮样式 */
.action-btn.view-btn {
color: #409EFF;
}
.action-btn.view-btn:hover {
color: #66b1ff;
}
::v-deep .table-card {
height: calc(100vh - 270px) !important;
}
</style>

View File

@ -45,7 +45,7 @@ export const columnsList = [
},
{ t_props: 'industryType', t_label: '行业类型' },
{ t_props: 'compositionName', t_label: '模板组成' },
{ t_props: 'currentVersion', t_label: '当前版本' },
{ t_props: 'currentVersion', t_label: '当前版本', t_slot: 'versionLink' },
{ t_props: 'useState', t_label: '状态', t_slot: 'useState' },
{ t_props: 'updateTime', t_label: '更新时间', format: 'yyyy/MM/dd HH:mm' },
];

View File

@ -41,6 +41,17 @@
/>
</template>
<template slot="versionLink" slot-scope="{ data }">
<span
class="version-link"
@click="handleVersionClick(data)"
v-if="data.currentVersion"
>
{{ data.currentVersion }}
</span>
<span v-else>--</span>
</template>
<!-- 操作按钮调整更多按钮样式增加菜单项 -->
<template slot="handle" slot-scope="{ data }">
<el-button
@ -53,6 +64,8 @@
解析规则
</el-button>
<!-- 更多下拉菜单调整样式与解析规则按钮保持一致 -->
<el-dropdown trigger="click" class="more-dropdown">
<span class="el-dropdown-link action-btn" style="color: #1F72EA;">
@ -73,18 +86,17 @@
@click.native="handleCopy(data)"
class="dropdown-item"
>
复制
复制
</el-dropdown-item>
<!-- 发布 -->
<el-dropdown-item
v-hasPermi="['template:info:publish']"
@click.native="handlePublish(data)"
class="dropdown-item"
:style="{ color: data.useState === '0' ? '#999' : '#1F72EA' }"
:disabled="data.useState === '0'"
:style="{ color: data.isPublished ? '#999' : '#1F72EA' }"
:disabled="data.isPublished"
>
{{ data.useState === '0' ? '已发布' : '发布' }}
{{ data.publishStatusText }}
</el-dropdown-item>
<!-- 删除 -->
<el-dropdown-item
@ -93,7 +105,7 @@
class="dropdown-item"
style="color: #DB3E29;"
>
删除
删除
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
@ -111,8 +123,8 @@ import {
listTemplateInfo,
delTemplateInfo,
updateTemplateInfo,
copyTemplateInfo, //
publishTemplateInfo //
copyTemplateInfo,
publishTemplate
} from '@/api/template/templateInfo/templateInfo'
export default {
@ -127,7 +139,6 @@ export default {
},
created() {
this.processedListTemplateInfo()
this.loadIndustryTypeDict()
this.$nextTick(() => {
if (this.$refs.templateInfoTableRef) {
@ -137,28 +148,48 @@ export default {
},
methods: {
handleVersionClick(data) {
this.$router.push({
name: 'TemplateHistory', // router/index.js
query: {
templateId: encryptWithSM4(data.templateId.toString()),
},
title: `模板历史详情`
});
},
processedListTemplateInfo(params) {
return listTemplateInfo(params).then(res => {
if (res.code === 200 && res.rows && res.rows.length > 0) {
// 使rows
// ID
const groupMap = {};
//
res.rows.forEach(item => {
const key = item.templateId;
if (!groupMap[key]) {
groupMap[key] = item; // compositionName
if (item.publishedStatus === '1') {
item.publishStatusText = '已发布'
item.isPublished = true
} else {
item.publishStatusText = '发布'
item.isPublished = false
}
});
res.rows = Object.values(groupMap);
})
//
const groupMap = {}
res.rows.forEach(item => {
const key = item.templateId
if (!groupMap[key]) {
groupMap[key] = item
}
})
res.rows = Object.values(groupMap)
}
return res;
});
return res
})
},
//
handleBack() {
const obj = { path: "/template" };
this.$tab.closeOpenPage(obj);
const obj = { path: '/template' }
this.$tab.closeOpenPage(obj)
},
//
@ -167,42 +198,42 @@ export default {
const industryTypeOptions = response.data.map(item => ({
label: item.dictLabel,
value: item.dictLabel
}));
const industryTypeItem = this.formLabel.find(item => item.f_model === 'industryType');
}))
const industryTypeItem = this.formLabel.find(item => item.f_model === 'industryType')
if (industryTypeItem) {
industryTypeItem.f_selList = industryTypeOptions;
this.industryTypeOptions = industryTypeOptions;
industryTypeItem.f_selList = industryTypeOptions
this.industryTypeOptions = industryTypeOptions
}
}).catch(error => {
console.error('加载行业类型字典失败:', error);
});
console.error('加载行业类型字典失败:', error)
})
},
//
handleAdd() {
this.$router.push({
name: "TemplateForm",
name: 'TemplateForm',
query: {
type: encryptWithSM4('add'),
templateId: encryptWithSM4('')
},
title: '新建模板'
});
})
},
//
handleUpdate(data) {
this.$router.push({
name: "TemplateForm",
name: 'TemplateForm',
query: {
type: encryptWithSM4('edit'),
templateId: encryptWithSM4((data.templateId ?? '').toString())
},
title: '编辑模板'
});
})
},
//
//
handleCopy(data) {
this.$modal.confirm(`是否确认复制“${data.templateName}”模板?`)
.then(() => {
@ -210,67 +241,71 @@ export default {
templateId: encryptWithSM4(data.templateId.toString())
}).then(res => {
if (res.code === 200) {
this.$modal.msgSuccess('复制成功');
this.handleQuery();
this.$modal.msgSuccess('复制成功')
this.handleQuery()
} else {
this.$modal.msgError(res.msg);
this.$modal.msgError(res.msg)
}
});
});
})
})
},
//
handlePublish(data) {
if (data.useState === '0') {
this.$modal.msgInfo('该模板已发布');
return;
if (data.isPublished) {
this.$modal.msgInfo('该模板已发布')
return
}
this.$modal.confirm(`是否确认发布“${data.templateName}”模板?`)
.then(() => {
publishTemplateInfo({
templateId: encryptWithSM4(data.templateId.toString()),
useState: '0' //
}).then(res => {
if (res.code === 200) {
this.$modal.msgSuccess('发布成功');
this.handleQuery();
} else {
this.$modal.msgError(res.msg);
}
});
});
publishTemplate(data.templateId)
.then(res => {
if (res.code === 200) {
this.$modal.msgSuccess('发布成功')
this.handleQuery()
} else {
this.$modal.msgError(res.msg || '发布失败')
}
})
.catch(error => {
console.error('发布模板请求异常:', error)
this.$modal.msgError('发布请求异常,请稍后重试')
})
})
},
//
handleDetail(data) {
this.$router.push({
name: "AnalysisRule",
name: 'AnalysisRule',
query: {
analysisLabelId: encryptWithSM4(data.analysisLabelId.toString()),
templateId: encryptWithSM4(data.templateId.toString())
},
title: '解析规则'
});
})
},
//
handleStateChange(row) {
const originalState = row.useState;
const params = { ...row, useState: row.useState, templateId: row.templateId };
console.log('params',params)
updateTemplateInfo(params)
const originalState = row.useState
updateTemplateInfo({
templateId: row.templateId,
useState: row.useState
})
.then(res => {
if (res.code === 200) {
this.$modal.msgSuccess('状态更新成功');
this.handleQuery();
this.$modal.msgSuccess('状态更新成功')
this.handleQuery()
} else {
this.$modal.msgError(res.msg);
row.useState = originalState;
this.$modal.msgError(res.msg)
row.useState = originalState
}
})
.catch(() => {
row.useState = originalState;
});
row.useState = originalState
})
},
//
@ -281,18 +316,20 @@ export default {
templateId: row.templateId
}).then(res => {
if (res.code === 200) {
this.$modal.msgSuccess('删除成功');
this.handleQuery();
this.$modal.msgSuccess('删除成功')
this.handleQuery()
} else {
this.$modal.msgError(res.msg);
this.$modal.msgError(res.msg)
}
});
});
})
})
},
//
handleQuery() {
this.$refs.templateInfoTableRef.getTableList();
if (this.$refs.templateInfoTableRef) {
this.$refs.templateInfoTableRef.getTableList()
}
}
}
}
@ -402,4 +439,14 @@ export default {
display: inline-flex;
align-items: center;
}
.version-link {
color: #1F72EA;
cursor: pointer;
transition: color 0.3s ease;
&:hover {
color: #4A8BFF;
}
}
</style>