feat(knowledge): 优化知识文档详情页面

- 添加文档详细信息查询功能,包括分词规则和词频规则
- 优化文档查看界面布局和样式
-增加文档来源标签显示- 修复文档关键词显示问题
This commit is contained in:
du.meimei
2025-04-16 19:27:22 +08:00
parent 3546a87e7e
commit f4af6a97ab
4 changed files with 429 additions and 57 deletions

View File

@@ -217,7 +217,14 @@ export function datasetQuerySegments(data) {
params: data params: data
}) })
} }
// 知识文档详情查询包含json的题词和拆分规则
export function datasetDocumentEx(data) {
return request({
url: getUrl(`/datasetDocumentEx/query`),
method: 'get',
params: data
})
}
// 任务轨迹查看详情 // 任务轨迹查看详情
export function getTaskDetail(params) { export function getTaskDetail(params) {
return request({ return request({

View File

@@ -87,43 +87,128 @@
</el-card> </el-card>
<el-drawer :visible.sync="drawer" size="80%" :title="descriptions.knowledgeName"> <el-drawer :visible.sync="drawer" size="80%" :title="descriptions.knowledgeName">
<div class="flex ph20 align-items-b" style="width: 100%"> <div class="drawer-content">
<div> <div class="file-header">
<el-descriptions class="margin-top" :column="1" border> <div class="file-icon">
<el-descriptions-item label="分段模式:"> <i class="el-icon-document"></i>
{{ segmentedMode | filterSegmentedMode }} </div>
</el-descriptions-item> <div class="file-info">
<el-descriptions-item label="是否使用预处理:"> <h3>{{ descriptions.fileName || descriptions.knowledgeName }}</h3>
{{ descriptions.useMineru | filterUseMineru }} <el-tag type="primary" class="mr10">{{ getDocumentSourceLabel(descriptions.documentSource) }}</el-tag>
</el-descriptions-item> <el-tag type="info">{{ descriptions.createdDate }}</el-tag>
<el-descriptions-item label="是否使用ocr协助处理"> </div>
{{ descriptions.mineruUseOcr | filterUseMineru }}
</el-descriptions-item>
<el-descriptions-item label="知识拆分规则:"> </el-descriptions-item>
<el-descriptions-item label="知识题词规则:"> </el-descriptions-item>
</el-descriptions>
</div> </div>
<div style="width:80%" class="ml20">
<el-collapse v-model="activeName" accordion> <el-divider></el-divider>
<el-collapse-item v-for="(item, index) in descriptions.data" :name="index" style="width: 100%" :key="index">
<div slot="title" class="flex" style="width: 100%"> <div class="segment-info">
<div class="ellipsis-title" style="flex:1">{{ item.content }}</div> <div class="segment-header">
<h4>分段规则</h4>
</div>
<div class="rule-card">
<div class="rule-item">
<h5>是否使用预处理:</h5>
<span>{{ descriptions.usePreProcess | filterUseMineru }}</span>
</div>
<div class="rule-item">
<h5>是否使用OCR协助处理:</h5>
<span>{{ descriptions.useOcr | filterUseMineru }}</span>
</div>
<div class="rule-item">
<h5>知识拆分规则:</h5>
<span v-if="!documentDetail.splitRules">
-
</span>
<div v-else>
<div v-for="item in documentDetail.splitRules" :key="item.id" class="rule-detail-item">
<p>
<span class="rule-label">样式:</span> <span class="rule-value">{{ item.titleLevel }}</span>
</p>
<p>
<span class="rule-label">关键词:</span> <span class="rule-value">{{ item.ruleRegex }}</span>
</p>
<p>
<span class="rule-label">备注:</span> <span class="rule-value">{{ item.description }}</span>
</p>
</div>
</div> </div>
<div> </div>
{{ item.content }} <div class="rule-item">
<h5>知识题词规则:</h5>
<span v-if="!documentDetail.extractRules">
-
</span>
<div v-else>
<div v-for="item in documentDetail.extractRules" :key="item.id" class="rule-detail-item">
<p>
<span class="rule-label">属性:</span> <span class="rule-value">{{ item.attribute }}</span>
</p>
<p>
<span class="rule-label">属性描述:</span><span class="rule-value">{{ item.attributeDesc }}</span>
</p>
<p>
<span class="rule-label">关键词:</span> <span class="rule-value">{{ item.keyword }}</span>
</p>
<p>
<span class="rule-label">关键词示例:</span> <span class="rule-value">{{ item.example }}</span>
</p>
<p>
<span class="rule-label">提示词:</span> <span class="rule-value">{{ item.prompt }}</span>
</p>
</div>
</div> </div>
<div class="mt20" style="flex:1"> </div>
<el-tag v-for="(tags, index) in item.keywords" :label="tags" size="mini" type="info" class="mr10 " :key="index">{{ tags }}</el-tag> </div>
</div>
<el-divider></el-divider>
<div class="segment-content-container">
<div class="segment-header mb15">
<h4>分段内容</h4>
<div class="segment-summary">
<span>{{ descriptions.data ? descriptions.data.length : 0 }}个分段</span>
</div>
</div>
<div class="segment-split-view">
<div class="segment-list">
<div
v-for="(segment, index) in descriptions.data"
:key="index"
class="segment-list-item"
:class="{ active: activeSegment === index }"
@click="activeSegment = index"
>
<div class="segment-title">
<span class="segment-number">分段{{ index + 1 }}</span>
<!-- <span class="segment-chars">{{ segment.characters || 0 }} characters</span>-->
</div>
</div> </div>
</el-collapse-item> </div>
</el-collapse> <div class="segment-detail" v-if="descriptions.data && descriptions.data.length > 0">
<div class="segment-content" v-if="activeSegment !== null">
{{ descriptions.data[activeSegment].content }}
<div class="flex align-items-c" v-if="descriptions.data[activeSegment].keywords && descriptions.data[activeSegment].keywords.length">
关键词
<el-tag v-for="(item, index) in descriptions.data[activeSegment].keywords" :key="index" class="mr10" size="mini" type="primary">{{
item
}}</el-tag>
</div>
</div>
<div v-if="activeSegment !== null"></div>
<div class="segment-empty" v-else>
<el-empty description="请选择左侧分段查看详情"></el-empty>
</div>
</div>
</div>
</div> </div>
</div> </div>
</el-drawer> </el-drawer>
</div> </div>
</template> </template>
<script> <script>
import { getDatasetById, datasetUpdate, datasetsExPages, datasetQuerySegments, datasetQueryDelete } from '@/api/generatedApi/index' import { getDatasetById, datasetUpdate, datasetsExPages, datasetQuerySegments, datasetQueryDelete, datasetDocumentEx } from '@/api/generatedApi/index'
import { documentSourceOptions, segmentedModeOptionsMap } from '@/assets/js/utils/utilOptions' import { documentSourceOptions, segmentedModeOptionsMap } from '@/assets/js/utils/utilOptions'
export default { export default {
name: 'index', name: 'index',
@@ -149,7 +234,14 @@ export default {
total: 0, total: 0,
hasList: false, hasList: false,
documentSourceOptions, documentSourceOptions,
descriptions: {} descriptions: { data: [{ content: '' }] },
activeSegmentTab: 'content',
activeSegments: [0],
activeSegment: 0,
documentDetail: {
splitRules: '',
extractRules: ''
}
} }
}, },
props: {}, props: {},
@@ -260,16 +352,54 @@ export default {
}, },
// 查看文档详情 // 查看文档详情
viewDocumentDetail(row) { viewDocumentDetail(row) {
// 调用查询分段信息接口
datasetQuerySegments({ documentId: row.id }).then(res => { datasetQuerySegments({ documentId: row.id }).then(res => {
if (res) { if (res) {
console.log(res.content.content)
this.descriptions = { this.descriptions = {
...row, ...row,
...res.content.content ...res.content.content
} }
this.drawer = true this.drawer = true
// 调用datasetDocumentEx接口获取分词规则和词频规则
this.getDocumentExInfo(row.id)
} }
}) })
}, },
// 获取文档详细信息(包含分词规则和词频规则)
getDocumentExInfo(documentId) {
datasetDocumentEx({ documentId })
.then(res => {
if (res && res.content && res.content.content) {
const docData = res.content.content
// 提取分词规则和词频规则
this.documentDetail.splitRules = docData.splitRules
this.documentDetail.extractRules = docData.extractRules
}
})
.catch(err => {
console.error('获取文档详情失败', err)
})
},
// 提取规则文本处理可能是JSON字符串的情况
extractRuleText(rule) {
if (!rule) return '暂无规则'
try {
// 尝试解析JSON字符串
if (typeof rule === 'string' && (rule.startsWith('{') || rule.startsWith('['))) {
const parsedRule = JSON.parse(rule)
return JSON.stringify(parsedRule, null, 2)
}
return rule
} catch (e) {
// 如果解析失败,直接返回原始文本
return rule
}
},
// 删除文件 // 删除文件
deleteKnowledge(row) { deleteKnowledge(row) {
this.$messageBox( this.$messageBox(
@@ -288,6 +418,11 @@ export default {
'是否确认删除当前知识文件?', '是否确认删除当前知识文件?',
'warning' 'warning'
) )
},
// 获取文档来源标签
getDocumentSourceLabel(sourceValue) {
const source = this.documentSourceOptions.find(item => item.value === String(sourceValue))
return source ? source.label : '未知来源'
} }
}, },
filters: { filters: {
@@ -417,4 +552,202 @@ export default {
color: #409eff; color: #409eff;
} }
} }
.drawer-content {
padding: 20px;
display: flex;
flex-direction: column;
}
.file-header {
display: flex;
align-items: center;
margin-bottom: 20px;
.file-icon {
font-size: 36px;
margin-right: 15px;
color: #409eff;
}
.file-info {
h3 {
margin: 0 0 10px 0;
font-size: 18px;
font-weight: bold;
}
}
}
.segment-info {
margin-top: 20px;
padding: 15px;
background: #f5f7fa;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.segment-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
h4 {
margin: 0;
font-size: 18px;
color: #333;
}
}
.rule-card {
//background: #fff;
//border-radius: 4px;
//padding: 15px;
//box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
.rule-item {
margin-bottom: 12px;
h5 {
display: inline-block;
margin: 0 8px 0 0;
font-weight: bold;
color: #409eff;
}
&:last-child {
margin-bottom: 0;
}
}
.rule-detail-item {
margin-top: 8px;
padding: 10px;
background: #f9f9f9;
border-radius: 4px;
border: 1px solid #e0e0e0;
p {
margin: 5px 0;
}
.rule-label {
font-size: 14px;
color: #606266;
}
.rule-value {
font-size: 14px;
color: #606266;
font-weight: 500;
}
}
}
}
.el-collapse-item__header {
font-weight: bold;
color: #409eff;
}
.mr10 {
margin-right: 10px;
}
.el-descriptions {
margin-bottom: 20px;
}
.collapse-title {
font-weight: bold;
color: #333;
font-size: 16px;
}
.collapse-content {
margin-top: 10px;
color: #666;
}
.tags {
margin-top: 10px;
}
.el-tag {
margin-right: 5px;
}
.segment-content-container {
margin-top: 20px;
}
.segment-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
h4 {
margin: 0;
font-size: 16px;
}
.segment-summary {
color: #606266;
}
}
.segment-split-view {
display: flex;
}
.segment-list {
width: 250px;
padding-right: 20px;
}
.segment-list-item {
padding: 10px;
cursor: pointer;
transition: background-color 0.3s;
&:hover {
background-color: #f0f0f0;
}
&.active {
background-color: #e6f7ff;
}
.segment-title {
display: flex;
justify-content: space-between;
align-items: center;
.segment-number {
font-weight: bold;
color: #999;
}
.segment-chars {
color: #606266;
}
}
}
.segment-detail {
flex: 1;
}
.segment-content {
padding: 10px;
background: #f9f9f9;
border-radius: 4px;
line-height: 1.6;
white-space: pre-wrap;
}
.segment-empty {
padding: 10px;
text-align: center;
}
</style> </style>

View File

@@ -21,7 +21,8 @@ export default {
columns: [ columns: [
{ prop: 'ruleName', key: '规则名称' }, { prop: 'ruleName', key: '规则名称' },
{ {
prop: 'ruleType', key: '规则类型', prop: 'ruleType',
key: '规则类型',
render: (h, params) => { render: (h, params) => {
return h('span', this.tableConfig.ruleType[params.row.ruleType]) return h('span', this.tableConfig.ruleType[params.row.ruleType])
} }
@@ -103,8 +104,7 @@ export default {
}, },
watch: { watch: {
form: { form: {
handler() { handler() {},
},
deep: true deep: true
} }
}, },
@@ -232,8 +232,7 @@ export default {
<el-form-item label="规则类型"> <el-form-item label="规则类型">
<el-select v-model="form.ruleType" placeholder="请选择规则类型"> <el-select v-model="form.ruleType" placeholder="请选择规则类型">
<el-option label="全部规则" value="">全部规则</el-option> <el-option label="全部规则" value="">全部规则</el-option>
<el-option v-for="item in tableConfig.ruleType" :key="item" :label="item" <el-option v-for="item in tableConfig.ruleType" :key="item" :label="item" :value="reverseRuleType(item)" />
:value="reverseRuleType(item)" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -246,9 +245,15 @@ export default {
<!-- 创建时间 --> <!-- 创建时间 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="创建时间"> <el-form-item label="创建时间">
<el-date-picker v-model="form.createdDate" type="daterange" unlink-panels range-separator="至" <el-date-picker
start-placeholder="开始日期" end-placeholder="结束日期" v-model="form.createdDate"
:picker-options="form.pickerOptions"> type="daterange"
unlink-panels
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
:picker-options="form.pickerOptions"
>
</el-date-picker> </el-date-picker>
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -266,17 +271,29 @@ export default {
<!-- 下方规则列表 --> <!-- 下方规则列表 -->
<div class="p20"> <div class="p20">
<r-table :columns="tableConfig.columns" :data="tableData" :deletion="false" :total="tableConfig.total" <r-table
@currentChange="handleCurrentChange" @sizeChange="handleSizeChange" :columns="tableConfig.columns"
:currentPage="tableConfig.currentPage" :data="tableData"
:pageSize="tableConfig.pageSize" /> :deletion="false"
:total="tableConfig.total"
@currentChange="handleCurrentChange"
@sizeChange="handleSizeChange"
:currentPage="tableConfig.currentPage"
:pageSize="tableConfig.pageSize"
/>
</div> </div>
</el-card> </el-card>
<!-- 规则详情弹窗 --> <!-- 规则详情弹窗 -->
<el-drawer :visible.sync="dialogOptions.visible" size="50%" :title="dialogOptions.title"> <el-drawer :visible.sync="dialogOptions.visible" size="50%" :title="dialogOptions.title">
<!-- diglog 弹窗内容组件 --> <!-- diglog 弹窗内容组件 -->
<component class="container" v-if="dialogOptions.visible" :is="dialogOptions.currentComponent" :data="tableData" <component
:columns="columns" :currentRow="dialogOptions.currentRow" /> class="container"
v-if="dialogOptions.visible"
:is="dialogOptions.currentComponent"
:data="tableData"
:columns="columns"
:currentRow="dialogOptions.currentRow"
/>
</el-drawer> </el-drawer>
</div> </div>
</template> </template>

View File

@@ -21,7 +21,6 @@ export default {
taskTime: [] taskTime: []
}, },
tableConfig: { tableConfig: {
uploadStatusType: { uploadStatusType: {
'1': '成功', '1': '成功',
'0': '上传中', '0': '上传中',
@@ -35,7 +34,9 @@ export default {
{ prop: 'datasetName', key: '知识库', width: '180' }, { prop: 'datasetName', key: '知识库', width: '180' },
{ prop: 'fileName', key: '知识文件名称' }, { prop: 'fileName', key: '知识文件名称' },
{ {
prop: 'documentStatus', key: '上传状态', width: '100', prop: 'documentStatus',
key: '上传状态',
width: '100',
render: (h, params) => { render: (h, params) => {
return h('span', this.tableConfig.uploadStatusType[params.row.documentStatus]) return h('span', this.tableConfig.uploadStatusType[params.row.documentStatus])
} }
@@ -193,8 +194,7 @@ export default {
<el-col :span="8"> <el-col :span="8">
<el-form-item label="知识库"> <el-form-item label="知识库">
<!-- 启用远程加载 --> <!-- 启用远程加载 -->
<el-select filterable remote :remote-method="remoteSearchDataset" v-model="form.datasetId" <el-select filterable remote :remote-method="remoteSearchDataset" v-model="form.datasetId" placeholder="输入搜索知识库">
placeholder="输入搜索知识库">
<!-- 后续需要添加懒加载 --> <!-- 后续需要添加懒加载 -->
<el-option v-for="item in form.datasetList" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in form.datasetList" :key="item.id" :label="item.name" :value="item.id" />
</el-select> </el-select>
@@ -220,15 +220,25 @@ export default {
<el-form-item label="上传状态"> <el-form-item label="上传状态">
<el-select v-model="form.documentStatus" placeholder="请选择上传状态"> <el-select v-model="form.documentStatus" placeholder="请选择上传状态">
<el-option label="全部上传状态" value="" /> <el-option label="全部上传状态" value="" />
<el-option v-for="item in Object.keys(this.tableConfig.uploadStatusType)" :key="item" <el-option
:label="tableConfig.uploadStatusType[item]" :value="Number(item)"></el-option> v-for="item in Object.keys(this.tableConfig.uploadStatusType)"
:key="item"
:label="tableConfig.uploadStatusType[item]"
:value="Number(item)"
></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="任务时间"> <el-form-item label="任务时间">
<el-date-picker v-model="form.taskTime" type="daterange" unlink-panels range-separator="至" <el-date-picker
start-placeholder="开始日期" end-placeholder="结束日期"> v-model="form.taskTime"
type="daterange"
unlink-panels
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
>
</el-date-picker> </el-date-picker>
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -243,14 +253,19 @@ export default {
<!-- 任务列表 --> <!-- 任务列表 -->
<div> <div>
<r-table :columns="tableConfig.columns" :data="tableData" :deletion="false" :total="tableConfig.total" <r-table
@currentChange="handleCurrentChange" @sizeChange="handleSizeChange" :columns="tableConfig.columns"
:currentPage="tableConfig.currentPage" :data="tableData"
:pageSize="tableConfig.pageSize" /> :deletion="false"
:total="tableConfig.total"
@currentChange="handleCurrentChange"
@sizeChange="handleSizeChange"
:currentPage="tableConfig.currentPage"
:pageSize="tableConfig.pageSize"
/>
<!-- 弹出提示框 里面是各种详情内容 --> <!-- 弹出提示框 里面是各种详情内容 -->
<el-drawer title="上传任务详情" :visible.sync="infoDialogVisible" size="80%" <el-drawer title="上传任务详情" :visible.sync="infoDialogVisible" size="80%" :before-close="() => (infoDialogVisible = false)">
:before-close="() => (infoDialogVisible = false)">
<knowledge-info :form="activeForm" v-if="infoDialogVisible"></knowledge-info> <knowledge-info :form="activeForm" v-if="infoDialogVisible"></knowledge-info>
</el-drawer> </el-drawer>
</div> </div>