Files
ebiz-ai-knowledge-manage/src/views/knowledge/detail/index.vue
du.meimei 6dd1e9ed71 feat(track): 优化上传用户选择功能
- 从后端获取用户列表,替代前端去重处理
- 更新用户选择框的数据显示和逻辑
- 修复了一些相关的 bug
2025-04-25 11:16:48 +08:00

873 lines
21 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 ">
<img :src="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 }}
</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-edit-outline"
class="primary-button"
@click="jumpEditKnowledge"
>修改知识库</el-button
>
<el-button
type="primary"
size="medium"
icon="el-icon-plus"
class="primary-button"
@click="jumpAddKnowledge"
>上传知识</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-edit-outline"-->
<!-- 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"
@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"
size="60%"
>
<hitTest></hitTest>
</el-drawer>
<!--元数据-->
<el-drawer
:title="meteDataDrawer.title"
:visible.sync="meteDataDrawer.visible"
size="30%"
>
<mete-data></mete-data>
</el-drawer>
</div>
</template>
<script>
import {
datasetDocumentEx,
datasetQueryDelete,
datasetQuerySegments,
datasetsExPages,
datasetUpdate,
getDatasetById
} from '@/api/generatedApi/index'
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/konwledge-1.png'
import hitTest from '@/views/knowledge/detail/components/HitTest/Index.vue'
import meteData from '@/views/knowledge/detail/components/meteData/Index.vue'
import MeteData from '@/views/knowledge/detail/components/meteData/Index.vue'
export default {
name: 'index',
data() {
return {
hitTestConfig: {
title: '命中测试',
visible: false
},
meteDataDrawer: {
title: '元数据',
visible: false
},
knowledgePng_1,
activeName: -1,
drawer: false,
drawerForm: false,
editKnowledge: false,
form: {
knowledgeNameLike: '',
documentSource: '',
createdUserLike: '',
times: ''
},
createdUserOptions: [],
//知识库名称
knowledgeName: '监管',
copyKnowledgeName: '监管',
knowledgeDesc: '监管',
segmentedMode: '分段模式',
list: [],
page: 1,
pageSize: 10,
total: 0,
hasList: false,
documentSourceOptions,
descriptions: { data: [{ content: '' }] },
activeSegmentTab: 'content',
activeSegments: [0],
activeSegment: 0,
documentDetail: {
splitRules: '',
extractRules: ''
}
}
},
props: {},
watch: {},
components: {
MeteData,
hitTest,
knowledgeForm,
DocumentDrawer
},
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
// }
// })
},
/**
* @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
})
},
// 查询
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) {
console.log(res.content.content)
this.descriptions = {
dataset: {
knowledgeName: this.knowledgeName,
segmentedMode: this.segmentedMode
},
...row,
...res.content.content
}
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) {
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.meteDataDrawer.visible = true
}
},
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',
width: '200px'
},
{
key: '知识文件来源',
prop: 'documentSource',
width: '200px',
render: (h, params) => {
let text = documentSourceOptions.find(
item => item.value === String(params.row.documentSource)
).label
return h('div', [h('span', text)])
}
},
{
key: '召回次数',
prop: 'hitCount',
render: (h, params) => {
return h('div', !params.row.hitCount ? '0' : params.row.hitCount)
}
},
{
key: '上传用户',
prop: 'createdUser',
render: (h, params) => {
return h(
'div',
!params.row.createdUser ? '-' : params.row.createdUser
)
}
},
{
key: '上传时间',
prop: 'createdDate'
},
{
key: '操作',
prop: 'knowledgeDesc',
width: '200px',
isRedraw: true,
render: (h, params) => {
return h('div', [
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-tickets',
title: '查看详情'
},
on: {
click: () => this.viewDocumentDetail(params.row)
}
},
'查看详情'
)
])
}
}
]
}
}
}
</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;
}
}
</style>