Files
ebiz-ai-knowledge-manage/src/views/knowledge/detail/index.vue
陈昱达 6a321263e6 refactor(knowledge): 优化知识详情页面功能
- 修改 Tooltip组件的代码格式
- 更新知识详情页面的数据处理逻辑
-调整文本分割设置的参数
2025-05-14 16:29:19 +08:00

1046 lines
26 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class=" render-container">
<div class="clearfix flex align-items-c justify-content-b ">
<div class="hover-back mr5">
<el-icon
class="el-icon-arrow-left"
style="font-size: 24px;cursor: pointer"
@click.native="$router.history.go(-1)"
></el-icon>
</div>
<img
:src="knowledgeImage ? knowledgeImage : knowledgePng_1"
class="header-icon"
/>
<div class="ml20" style="flex:1">
<div class="flex align-items-c">
<div class="mr20 header" v-if="!editKnowledge">
{{ knowledgeName ? knowledgeName : '' }}
</div>
<el-input
class="mr20 w400"
size="small"
v-else
v-model="copyKnowledgeName"
>{{ knowledgeName }}</el-input
>
<el-icon
class="fs16 el-icon-edit-outline cursor-pointer"
@click.native="editKnowledgeName"
v-if="!editKnowledge"
/>
<div v-else>
<el-button
type="primary"
size="medium"
class="render-button"
@click="saveKnowledgeName"
>保存</el-button
>
<el-button
size="medium"
class="render-button"
@click="cancelKnowledgeName"
>取消</el-button
>
</div>
<span class="segment-content">{{
segmentedMode | filterSegmentedMode
}}</span>
</div>
<p class="mt10 fs14" style="line-height: 20px">
描述{{ knowledgeDesc }}
</p>
<!-- <p class="mt10 fs14" style="line-height: 20px">分段模式{{ segmentedMode | filterSegmentedMode }}</p>-->
</div>
<div>
<!-- <el-button type="primary" size="medium" class="normal-button" @click="jumpEditKnowledge">修改知识库</el-button>-->
<el-button
type="primary"
size="medium"
icon="el-icon-plus"
class="primary-button"
disbaled
@click="jumpAddKnowledge"
>上传知识
</el-button>
<el-button
type="primary"
size="medium"
icon="el-icon-edit-outline"
class="primary-button"
@click="jumpEditKnowledge"
>修改知识库
</el-button>
<el-button
type="primary"
size="medium"
icon="el-icon-search"
class="line-button"
@click="searchSetting"
>检索设置
</el-button>
<el-button
type="primary"
icon="el-icon-s-promotion"
size="medium"
class="line-button"
@click="handleHitTestClick"
>命中测试
</el-button>
<el-button
type="primary"
icon="el-icon-help"
size="medium"
class="line-button"
@click="handleMetaData"
>元数据
</el-button>
</div>
</div>
<div class="mt20 card-body">
<el-empty v-if="!hasList">
<div class="mt20">
<el-button
type="primary"
size="medium"
class="fs14"
@click="jumpAddKnowledge"
>立即添加</el-button
>
</div>
</el-empty>
<div class="table-container" v-else>
<div class="flex align-items-c justify-content-b">
<el-form
:model="form"
label-width="100px"
label-position="top"
inline
>
<el-form-item label="知识文件名称" prop="fileName">
<el-input
v-model="form.knowledgeNameLike"
size="medium"
placeholder="请输入知识文件名称"
@keydown.enter.native="search"
></el-input>
</el-form-item>
<el-form-item label="知识文件来源" prop="documentSource">
<el-select v-model="form.documentSource" size="medium">
<el-option label="全部" value=""></el-option>
<el-option
v-for="item in documentSourceOptions"
:label="item.label"
:value="item.value"
:key="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="上传用户" prop="createdUserLike">
<el-select v-model="form.createdUserLike" size="medium">
<el-option label="全部" value=""></el-option>
<el-option
v-for="item in createdUserOptions"
:label="item.label"
:value="item.value"
:key="item.value"
></el-option>
</el-select>
</el-form-item>
<!-- <el-form-item label="关键字">-->
<!-- <el-input-->
<!-- v-model="form.name"-->
<!-- placeholder="请输入关键字/敏感词"-->
<!-- disabled-->
<!-- size="medium"-->
<!-- ></el-input>-->
<!-- </el-form-item>-->
<el-form-item label="上传时间" prop="times">
<el-date-picker
size="medium"
style="width:100%"
v-model="form.times"
value-format="yyyy-MM-dd"
start-placeholder="开始时间"
end-placeholder="结束时间"
type="daterange"
></el-date-picker>
</el-form-item>
</el-form>
<div class="mt15 flex align-items-c justify-content-b">
<el-button size="medium" type="primary" @click="search"
>查询</el-button
>
<el-button size="medium" @click="reset">重置</el-button>
</div>
</div>
<r-table
:columns="columns"
:data="list"
:deletion="false"
:total="total"
@page-change="pageChange"
@current-change="currentChange"
:current-page="page"
:page-size="pageSize"
></r-table>
</div>
</div>
<document-drawer
:visible.sync="drawer"
:descriptions="descriptions"
:document-detail="documentDetail"
:active-segment="activeSegment"
@openMetaDrawer="handleMetaData"
@update:visible="val => (drawer = val)"
/>
<knowledgeForm
:visible.sync="drawerForm"
:datasetId="$route.query.datasetId"
@update:visible="getKnowledgeDetail"
>
</knowledgeForm>
<!-- 命中测试抽屉弹窗 -->
<el-drawer
:title="hitTestConfig.title"
:visible.sync="hitTestConfig.visible"
:wrapperClosable="false"
size="80%"
>
<hitTest></hitTest>
</el-drawer>
<!--元数据-->
<el-drawer
:title="metaDataDrawer.title"
:visible.sync="metaDataDrawer.visible"
:wrapperClosable="false"
size="30%"
>
<meta-data ref="metaData" :datasetId="$route.query.datasetId"></meta-data>
</el-drawer>
<!--元数据操作抽屉-->
<el-drawer
:title="metadataOperatorDrawer.title"
:visible.sync="metadataOperatorDrawer.visible"
size="30%"
:wrapperClosable="false"
>
<metadata-operator
ref="metadataOperator"
@openMetaDrawer="handleMetaData"
@close="close"
></metadata-operator>
</el-drawer>
<!--检索设置弹窗-->
<search-setting ref="searchSetting"></search-setting>
</div>
</template>
<script>
import {
datasetDocumentEx,
datasetQueryDelete,
datasetQuerySegments,
datasetsExPages,
datasetUpdate,
getDatasetById
} from '@/api/generatedApi'
import { getUserList } from '@/api/generatedApi/system'
import {
documentSourceOptions,
segmentedModeOptionsMap
} from '@/assets/js/utils/utilOptions'
import DocumentDrawer from './components/documentDetail/DocumentDrawer.vue'
import knowledgeForm from '@/views/knowledge/detail/components/knowledgeForm.vue'
import knowledgePng_1 from '@/assets/images/konwledge/knowledge-1.png'
import hitTest from '@/views/knowledge/detail/components/HitTest/Index.vue'
import MetaData from '@/views/knowledge/detail/components/metaData/Index.vue'
import MetadataOperator from '@/views/knowledge/detail/components/metaData/MetadataOperator.vue'
import SearchSetting from '@/views/knowledge/detail/components/SearchSetting/Index.vue'
import { displayStatus } from '@/assets/js/utils/utilOptions'
export default {
name: 'index',
data() {
return {
displayStatus,
hitTestConfig: {
title: '命中测试',
visible: false
},
metaDataDrawer: {
title: '元数据',
visible: false
},
metadataOperatorDrawer: {
title: '标注元数据',
visible: false
},
knowledgePng_1,
activeName: -1,
drawer: false,
drawerForm: false,
editKnowledge: false,
form: {
knowledgeNameLike: '',
documentSource: '',
createdUserLike: '',
times: ''
},
createdUserOptions: [],
//知识库名称
knowledgeName: '监管',
copyKnowledgeName: '监管',
knowledgeDesc: '监管',
segmentedMode: '分段模式',
knowledgeImage: '',
list: [],
page: 1,
pageSize: 10,
total: 0,
hasList: false,
documentSourceOptions,
descriptions: { data: [{ content: '' }], dataset: { knowledgeName: '' } },
activeSegmentTab: 'content',
activeSegments: [0],
activeSegment: 0,
documentDetail: {
splitRules: '',
extractRules: ''
}
}
},
props: {},
watch: {},
components: {
SearchSetting,
MetaData,
hitTest,
knowledgeForm,
DocumentDrawer,
MetadataOperator
},
methods: {
getUserData() {
getUserList({}).then(res => {
this.createdUserOptions = res.content.content.map(item => {
return {
label: item.realName,
value: item.userName
}
})
})
},
// 开启编辑 知识库标题
editKnowledgeName() {
this.editKnowledge = true
this.copyKnowledgeName = this.knowledgeName
},
// 保存知识库标题
saveKnowledgeName() {
let { datasetId } = this.$route.query
this.knowledgeName = this.copyKnowledgeName
// 调用update接口
datasetUpdate({
name: this.knowledgeName,
description: this.knowledgeDesc,
segmentedMode: this.segmentedMode,
id: datasetId
}).then(res => {
this.editKnowledge = false
})
},
// 取消保存
cancelKnowledgeName() {
this.editKnowledge = false
},
// 跳转去上传文件
jumpAddKnowledge() {
sessionStorage.removeItem('documentId')
let { datasetId } = this.$route.query
this.$router.push({
path: '/knowledge/detail/create',
query: {
datasetId: datasetId
}
})
},
// 跳转到知识库编辑
jumpEditKnowledge() {
this.drawerForm = true
// let { datasetId } = this.$route.query
// this.$router.push({
// path: '/knowledge/knowledge-create',
// query: {
// datasetId: datasetId
// }
// })
},
/**
* 检索设置
*/
searchSetting() {
this.$refs.searchSetting.init()
},
/**
* @name 根据id 获取知识内容详情
* @author Chen Yuda
* @created_date 2025/4/9
* @description
**/
getKnowledgeDetail() {
let { datasetId } = this.$route.query
getDatasetById({ id: datasetId }).then(res => {
this.knowledgeName = res.content.content.name
this.knowledgeDesc = res.content.content.description
this.segmentedMode = res.content.content.segmentedMode
this.knowledgeImage = res.content.content.image
})
},
// 查询
search() {
this.page = 1
this.getKnowledgeFiledList()
},
reset() {
this.form = {
fileName: '',
documentSource: '',
createdUserLike: '',
times: ''
}
this.getKnowledgeFiledList()
},
//获取知识库文件
getKnowledgeFiledList() {
let { datasetId } = this.$route.query
datasetsExPages({
...this.form,
datasetId,
page: this.page,
pageSize: this.pageSize,
startCreatedDate: this.form.times[0]
? this.form.times[0] + ' 00:00:00'
: '',
endCreatedDate: this.form.times[1]
? this.form.times[1] + ' 23:59:59'
: ''
}).then(res => {
this.list = res.content.content.list
this.total = res.content.content.total
if (!this.hasList && this.list.length > 0) {
this.hasList = true
}
})
},
pageChange(pageSize) {
this.pageSize = pageSize
this.page = 1
this.getKnowledgeFiledList()
},
currentChange(page) {
this.page = page
this.getKnowledgeFiledList()
},
// 查看文档详情
viewDocumentDetail(row) {
// 调用查询分段信息接口
datasetQuerySegments({ documentId: row.id }).then(res => {
if (res) {
this.descriptions = {
dataset: {
knowledgeName: this.knowledgeName,
segmentedMode: this.segmentedMode,
knowledgeImage: this.knowledgeImage
},
...row,
...res.content.content
}
this.drawer = true
// 调用datasetDocumentEx接口获取分词规则和词频规则
this.getDocumentExInfo(row.id)
}
})
},
jumpToUpload(params) {
sessionStorage.removeItem('documentId')
let { datasetId } = this.$route.query
let querys = {}
querys.datasetId = datasetId
querys.documentId = params.row.id
if (params.row.optStatus === 0) {
if (params.row.useMineru === 1) {
querys.activeLevel = 0
querys.isMd = true
} else {
querys.activeLevel = 1
}
} else {
querys.activeLevel = params.row.optStatus - 1
querys.isMd = true
}
this.$router.push({
path: '/knowledge/detail/create',
query: {
...querys
}
})
},
// 元数据操作
handleAddMetadata(row) {
this.metadataOperatorDrawer.visible = true
this.$nextTick(() => {
if (this.$refs.metadataOperator) {
// 如果需要初始化或传递数据,可以在这里处理
// 例如传递当前行数据
this.$refs.metadataOperator.init(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) {
this.$messageBox(
() => {
datasetQueryDelete({ id: row.id }).then(res => {
if (res) {
this.$message({
type: 'success',
message: '删除成功!'
})
this.page = 1
this.getKnowledgeFiledList()
}
})
},
'是否确认删除当前知识文件?',
'warning'
)
},
/**
* @name 点击命中测试控制弹窗
*/
handleHitTestClick() {
this.hitTestConfig.visible = true
},
handleMetaData() {
this.metaDataDrawer.visible = true
this.$nextTick(() => {
this.$refs.metaData.init()
})
},
close() {
this.metadataOperatorDrawer.visible = false
}
},
filters: {
filterSegmentedMode(val) {
switch (val) {
case 0:
return '传统分段模式'
case 1:
return ' Q&A分段模式'
default:
return ''
}
},
filterUseMineru(val) {
let item = segmentedModeOptionsMap.find(
item => item.value === String(val)
)
return item ? item.label : '否'
}
},
created() {},
async mounted() {
this.getKnowledgeDetail()
// 获取知识库文件列表
this.getKnowledgeFiledList()
// 获取用户下拉列表
this.getUserData()
let documentId = sessionStorage.getItem('documentId')
if (documentId) {
setTimeout(() => {
let row = this.list.filter(item => item.id === documentId)
this.viewDocumentDetail(row[0])
sessionStorage.removeItem('documentId')
}, 1000)
}
},
computed: {
datasetId() {
return {
datasetId: this.$route.query.datasetId,
segmentedMode: this.filterSegmentedMode(this.segmentedMode)
}
},
columns(vm) {
return [
{
key: '知识文件名称',
prop: 'knowledgeName'
},
{
key: '知识文件来源',
prop: 'documentSource',
render: (h, params) => {
let text = documentSourceOptions.find(
item => item.value === String(params.row.documentSource)
).label
return h(
'div',
{
class: 'flex align-items-c'
},
[
h('svg-icon', {
props: {
iconClass: params.row.useMineru === 1 ? 'miner' : 'none'
},
class: 'mr5',
style: {
// display: params.row.useMineru === 1 ? '' : 'none',
width: '15px',
fontSize: '15px'
}
}),
h('span', {}, text)
]
)
}
},
{
key: '状态',
prop: 'displayStatus',
render: (h, params) => {
let label = this.displayStatus.find(item => {
return item.value === params.row.displayStatus
}).label
return h('div', label ? label : 'N/A')
}
},
{
key: '召回次数',
prop: 'hitCount',
render: (h, params) => {
return h('div', !params.row.hitCount ? '0' : params.row.hitCount)
}
},
{
key: '上传用户',
prop: 'createdUser',
width: '100',
render: (h, params) => {
return h(
'div',
!params.row.createdUser ? '-' : params.row.createdUser
)
}
},
{
key: '上传时间',
prop: 'createdDate',
width: '100'
},
{
key: '操作',
prop: 'knowledgeDesc',
isRedraw: true,
render: (h, params) => {
return h('div', [
h('el-button', {
class: 'floatSpan',
props: {
type: 'primary',
size: 'mini',
icon: 'el-icon-tickets',
title: '查看详情'
},
on: {
click: () => this.viewDocumentDetail(params.row)
}
}),
h(
'el-button',
{
class: 'floatSpan',
props: {
type: 'danger',
size: 'mini',
icon: 'el-icon-delete',
title: '删除'
},
on: {
click: () => this.deleteKnowledge(params.row)
}
}
// '删除'
),
// h(
// 'el-button',
// {
// class: 'normal-button',
// props: {
// type: 'primary',
// size: 'mini',
// disabled: true,
// icon: 'el-icon-edit-outline',
// title: '编辑'
// },
// on: {}
// },
// '编辑'
// ),
h(
'el-button',
{
class: 'floatSpan',
props: {
type: 'primary',
size: 'mini',
icon: 'el-icon-help',
title: '添加元数据',
disabled: params.row.documentStatus !== 1
},
on: {
click: () => this.handleAddMetadata(params.row)
}
}
// '标注元数据'
),
params.row.optStatus < 4
? h(
'el-button',
{
class: 'floatSpan',
props: {
type: 'primary',
size: 'mini',
icon: 'el-icon-video-play',
title: '继续处理'
},
on: {
click: () => this.jumpToUpload(params)
}
}
// '文件拆分处理'
)
: ''
])
}
}
]
}
}
}
</script>
<style scoped lang="scss">
.ellipsis-title {
// 最多展示一行 省略号展示
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
word-break: break-all;
white-space: nowrap;
//width: 50%;
&:hover {
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: #666;
}
&:last-child {
margin-bottom: 0;
}
}
.rule-detail-item {
margin-top: 8px;
padding: 10px;
background: #f9f9f9;
border-radius: 8px;
border: 1px solid #e0e0e0;
margin-right: 10px;
p {
margin: 5px 0;
}
.rule-label {
font-size: 14px;
color: #606266;
margin-bottom: 10px;
}
.rule-value {
font-size: 14px;
color: #606266;
font-weight: 500;
margin-left: 15px;
}
}
}
}
.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 {
//flex: 1;
width: 45%;
margin-right: 10px;
}
.segment-list-item {
cursor: pointer;
margin-bottom: 10px;
padding: 15px 0 10px 15px;
border-radius: 15px;
transition: background-color 0.3s;
font-size: 14px;
&:hover {
background: #f3f5f7;
}
&.active {
background: #f3f5f7;
}
p {
margin: 10px 0;
}
.segment-number {
color: #0a84ff;
}
.segment-keywords {
color: #606266;
}
}
.segment-detail {
width: 55%;
}
.segment-content {
background: #f6f8fa;
border-radius: 4px;
font-family: PingFangSC;
font-size: 12px;
color: #4e5969;
line-height: 17px;
text-align: left;
font-style: normal;
margin-left: 20px;
padding: 2px 5px;
}
.segment-empty {
padding: 10px;
text-align: center;
}
.clearfix {
padding-bottom: 20px;
position: relative;
&:after {
position: absolute;
bottom: 0;
left: 0;
content: '';
width: 100%;
border-bottom: 1px solid #979797;
// 宽度缩小
transform: scaleY(0.2);
}
& .header {
font-family: PingFangSC, PingFang SC;
font-weight: 600;
font-size: 16px;
color: #000000;
text-align: left;
font-style: normal;
}
}
.hover-back {
padding: 10px 5px;
&:hover {
background: rgba(87, 104, 161, 0.2);
border-radius: 5px;
}
}
</style>