2025-5-19: 新增分段, 分段删除

- 添加分段创建、更新和删除接口
- 实现新增分段对话框和相关逻辑
- 优化分段显示和操作界面
- 添加分段相关的样式和交互
This commit is contained in:
陈昱达
2025-05-19 14:39:12 +08:00
parent 9518e9c240
commit 45714cbe71
6 changed files with 391 additions and 58 deletions

View File

@@ -421,6 +421,7 @@ export function uploadImage(data) {
}) })
} }
// 分段编辑
export function segmentUpdate(data) { export function segmentUpdate(data) {
return request({ return request({
url: getUrl(`/datasetDocumentEx/segment/update`), url: getUrl(`/datasetDocumentEx/segment/update`),
@@ -428,3 +429,20 @@ export function segmentUpdate(data) {
data data
}) })
} }
// 分段删除
export function segmentDelete(data) {
return request({
url: getUrl(`/datasetDocumentEx/segment/delete`),
method: 'get',
params: data
})
}
// 新增分段
export function segmentCreate(data) {
return request({
url: getUrl(`/datasetDocumentEx/segment/create`),
method: 'post',
data
})
}

View File

@@ -315,10 +315,10 @@ export default {
<el-button <el-button
class="line-button" class="line-button"
size="medium" size="medium"
icon="el-icon-odometer" icon="el-icon-more"
@click.stop="jumpToLogs(listItem.id)" @click.stop="jumpToLogs(listItem.id)"
type="primary" type="primary"
>日志与监测 >更多
</el-button> </el-button>
</div> </div>
</el-card> </el-card>

View File

@@ -24,11 +24,14 @@
v-if="!noEdit" v-if="!noEdit"
> >
<!-- 删除 编辑--> <!-- 删除 编辑-->
<!-- <el-icon class="el-icon-edit" @click.native.stop=""></el-icon>--> <el-icon
<!-- <el-icon--> class="el-icon-edit mr10"
<!-- class="el-icon-delete"--> @click.native.stop="handleSegmentClick(index)"
<!-- @click.native.stop="deleteSegment"--> ></el-icon>
<!-- ></el-icon>--> <el-icon
class="el-icon-delete"
@click.native.stop="deleteSegment(segment, index)"
></el-icon>
<!-- <el-switch--> <!-- <el-switch-->
<!-- size="mini"--> <!-- size="mini"-->
<!-- @click.native.stop=""--> <!-- @click.native.stop=""-->
@@ -72,20 +75,20 @@
<div class="segment-content"> <div class="segment-content">
<div> <div>
<div> <div>
<p>QUESTION</p> <p class="">QUESTION</p>
<p contenteditable class="resetHtml" ref="content"> <p contenteditable class="resetHtml fs13" ref="content">
{{ descriptions.data[activeSegment].content }} {{ descriptions.data[activeSegment].content }}
</p> </p>
</div> </div>
<div> <div>
<p>ANSWER</p> <p class="">ANSWER</p>
<p contenteditable class="resetHtml" ref="answer"> <p contenteditable class="resetHtml fs13" ref="answer">
{{ descriptions.data[activeSegment].answer }} {{ descriptions.data[activeSegment].answer }}
</p> </p>
</div> </div>
</div> </div>
<div <div
class="flex align-items-c mt20" class="flex align-items-c mt20 fs13"
v-if=" v-if="
descriptions.data[activeSegment].keywords && descriptions.data[activeSegment].keywords &&
descriptions.data[activeSegment].keywords.length descriptions.data[activeSegment].keywords.length
@@ -99,8 +102,10 @@
class="mr10" class="mr10"
size="medium" size="medium"
type="info" type="info"
:closable="!noEdit" :closable="
@close="tagClose(item)" descriptions.data[activeSegment].keywords.length > 1 && !noEdit
"
@close="tagClose(item, index)"
> >
{{ item }} {{ item }}
</el-tag> </el-tag>
@@ -133,7 +138,7 @@
</div> </div>
</template> </template>
<script> <script>
import { segmentUpdate } from '@/api/generatedApi' import { segmentUpdate, segmentDelete } from '@/api/generatedApi'
export default { export default {
name: 'QAModel', name: 'QAModel',
@@ -152,6 +157,7 @@ export default {
default: () => ({}) default: () => ({})
} }
}, },
watch: {},
data() { data() {
return { return {
createdTag: false, createdTag: false,
@@ -160,10 +166,22 @@ export default {
dialogVisible: false dialogVisible: false
} }
}, },
mounted() {},
methods: { methods: {
deleteSegment() { deleteSegment(segment, index) {
this.$messageBox( this.$messageBox(
() => {}, () => {
segmentDelete({
documentId: this.parentForm.id,
segmentId: segment.id
}).then(res => {
if (res) {
this.$message.success('删除成功')
this.descriptions.data.splice(index, 1)
}
})
},
'是否删除当前分段,删除后不可恢复', '是否删除当前分段,删除后不可恢复',
'warning', 'warning',
'提示' '提示'
@@ -183,20 +201,15 @@ export default {
showInput() { showInput() {
this.createdTag = true this.createdTag = true
}, },
tagClose() { tagClose(item, index) {
this.descriptions.data[this.activeSegment].keywords.splice( this.descriptions.data[this.activeSegment].keywords.splice(index, 1)
this.descriptions.data[this.activeSegment].keywords.indexOf( // console.log(this.descriptions.data[this.activeSegment].keywords)
this.inputValue
),
1
)
let params = { let params = {
keywords: this.descriptions.data[this.activeSegment].keywords, keywords: this.descriptions.data[this.activeSegment].keywords,
content: this.$refs.content.innerHTML, content: this.$refs.content.innerHTML,
answer: this.$refs.answer.innerHTML answer: this.$refs.answer.innerHTML
} }
this.saveSegment(params) // this.saveSegment(params)
}, },
handleInputConfirm() { handleInputConfirm() {
@@ -265,6 +278,21 @@ export default {
border-radius: 2px; border-radius: 2px;
transition: background-color 0.3s; transition: background-color 0.3s;
border-bottom: 1px solid #f3f5f7; border-bottom: 1px solid #f3f5f7;
position: relative;
.actions {
position: absolute;
display: none; /* 默认隐藏 */
background: #fff;
padding: 3px 5px;
border-radius: 5px;
right: 10px;
top: 15px;
}
&:hover .actions {
display: flex; /* 悬停或激活时显示操作按钮 */
gap: 10px;
}
&:hover { &:hover {
background: #f3f5f7; background: #f3f5f7;
} }

View File

@@ -27,11 +27,14 @@
v-if="!noEdit" v-if="!noEdit"
> >
<!-- 删除 编辑--> <!-- 删除 编辑-->
<!-- <el-icon class="el-icon-edit" @click.native.stop=""></el-icon>--> <el-icon
<!-- <el-icon--> class="el-icon-edit mr10"
<!-- class="el-icon-delete"--> @click.native.stop="handleSegmentClick(index)"
<!-- @click.native.stop="deleteSegment"--> ></el-icon>
<!-- ></el-icon>--> <el-icon
class="el-icon-delete"
@click.native.stop="deleteSegment(segment, index)"
></el-icon>
<!-- <el-switch--> <!-- <el-switch-->
<!-- size="mini"--> <!-- size="mini"-->
<!-- @click.native.stop=""--> <!-- @click.native.stop=""-->
@@ -81,7 +84,7 @@
" "
> >
<div class="segment-content"> <div class="segment-content">
<p contenteditable class="resetHtml" ref="content"> <p contenteditable class="resetHtml fs13" ref="content">
{{ descriptions.data[activeSegment].content }} {{ descriptions.data[activeSegment].content }}
</p> </p>
<div <div
@@ -95,10 +98,12 @@
<span>关键词 </span> <span>关键词 </span>
<el-tag <el-tag
v-for="(item, index) in descriptions.data[activeSegment].keywords" v-for="(item, index) in descriptions.data[activeSegment].keywords"
:key="index" :key="item"
class="mr10 ellipsis" class="mr10 ellipsis"
size="medium" size="medium"
:closable="!noEdit" :closable="
descriptions.data[activeSegment].keywords.length > 1 && !noEdit
"
type="info" type="info"
@close="tagClose(item)" @close="tagClose(item)"
> >
@@ -133,7 +138,7 @@
</div> </div>
</template> </template>
<script> <script>
import { segmentUpdate } from '@/api/generatedApi' import { segmentDelete, segmentUpdate } from '@/api/generatedApi'
export default { export default {
name: 'TextModel', name: 'TextModel',
@@ -161,10 +166,22 @@ export default {
selectedSegments: [] selectedSegments: []
} }
}, },
watch: {},
methods: { methods: {
deleteSegment() { deleteSegment(segment, index) {
this.$messageBox( this.$messageBox(
() => {}, () => {
segmentDelete({
documentId: this.parentForm.id,
segmentId: segment.id
}).then(res => {
if (res) {
this.$message.success('删除成功')
this.descriptions.data.splice(index, 1)
}
})
},
'是否删除当前分段,删除后不可恢复', '是否删除当前分段,删除后不可恢复',
'warning', 'warning',
'提示' '提示'
@@ -186,13 +203,7 @@ export default {
this.createdTag = true this.createdTag = true
}, },
tagClose() { tagClose() {
this.descriptions.data[this.activeSegment].keywords.splice( this.descriptions.data[this.activeSegment].keywords.splice(index, 1)
this.descriptions.data[this.activeSegment].keywords.indexOf(
this.inputValue
),
1
)
let params = { let params = {
keywords: this.descriptions.data[this.activeSegment].keywords, keywords: this.descriptions.data[this.activeSegment].keywords,
content: this.$refs.content.innerHTML, content: this.$refs.content.innerHTML,
@@ -222,8 +233,9 @@ export default {
JSON.stringify(res.content.content) JSON.stringify(res.content.content)
) )
// res.content.content.keywords // res.content.content.keywords
// this.descriptions.data[this.activeSegment].content = this.descriptions.data[this.activeSegment].keywords = JSON.parse(
// res.content.content.content JSON.stringify(res.content.content.keywords)
)
// this.descriptions.data[this.activeSegment].answer = // this.descriptions.data[this.activeSegment].answer =
// res.content.content.answer // res.content.content.answer
this.createdTag = false this.createdTag = false
@@ -273,8 +285,27 @@ export default {
transition: background-color 0.3s; transition: background-color 0.3s;
font-size: 14px; font-size: 14px;
border-bottom: 1px solid #f3f5f7; border-bottom: 1px solid #f3f5f7;
position: relative;
//margin-bottom: 10px; //margin-bottom: 10px;
&.active {
background: #f3f5f7;
}
.actions {
position: absolute;
display: none; /* 默认隐藏 */
background: #fff;
padding: 3px 5px;
border-radius: 5px;
right: 10px;
top: 15px;
}
&:hover .actions {
display: flex; /* 悬停或激活时显示操作按钮 */
gap: 10px;
}
&:hover { &:hover {
background: #f3f5f7; background: #f3f5f7;
} }

View File

@@ -0,0 +1,215 @@
<template>
<div class="add-segment-container">
<div class="add-segment-content">
<div class="add-segment-text fs13">
<!-- Q&A-->
<div v-if="descriptions.doc_form === 'qa_model'">
<div class="mv10">Question</div>
<el-input
type="textarea"
class="text-input-q-a"
v-model="question"
placeholder="请填写分段内容"
></el-input>
<div class="mv10">Answer</div>
<el-input
type="textarea"
class="text-input-q-a"
v-model="answer"
placeholder="请填写分段内容"
></el-input>
</div>
<!-- model-->
<div v-else>
<el-input
type="textarea"
class="text-input"
v-model="question"
placeholder="请填写分段内容"
></el-input>
</div>
</div>
</div>
<div class="add-segment-bottom ">
<div class=" mt5 fs13 mb5" style="flex:none">关键词</div>
<div
class="add-segment-tags mb10 fs14 flex align-items-s justify-content-s "
>
<div
class="flex align-items-s mb10"
style="max-height: 80px;overflow: auto;gap: 10px;flex-wrap: wrap;align-items: self-start"
>
<el-tag
v-for="(item, index) in keywords"
size="medium"
:closable="keywords.length > 1"
type="info"
@close="keywords.splice(index, 1)"
>{{ item }}</el-tag
>
<el-input
class="input-new-tag"
v-if="createdTag"
v-model="inputValue"
ref="saveTagInput"
size="small"
@keyup.enter.native="pushKeyWords"
/>
<el-button
v-if="!createdTag"
size="small"
@click="showInput"
class="fs12"
style="padding: 7px;border-radius: 4px;font-size: 12px"
>添加标签</el-button
>
</div>
</div>
<div class="flex align-items-c justify-content-b">
<el-checkbox v-model="continueAdd">连续新增</el-checkbox>
<div>
<el-button size="medium" @click="close">取消</el-button>
<el-button
class="line-button"
size="medium"
@click="handleInputConfirm"
>保存</el-button
>
</div>
</div>
</div>
</div>
</template>
<script>
import { segmentCreate } from '@/api/generatedApi'
export default {
name: 'addSegment',
data() {
return {
continueAdd: false,
question: '',
answer: '',
createdTag: false,
inputValue: '',
keywords: []
}
},
props: {
descriptions: {
type: Object,
default: () => ({ data: [] })
},
documentId: {
type: String,
default: () => ''
}
},
methods: {
pushKeyWords() {
this.keywords.push(this.inputValue)
this.createdTag = false
},
showInput() {
this.createdTag = true
},
handleInputConfirm() {
let params = {
documentId: this.documentId,
segment: {
keywords: this.keywords,
content: this.question,
answer: this.answer
}
}
segmentCreate(params).then(res => {
if (res) {
this.$message.success('新增分段成功')
this.descriptions.data.push({
...res.content.content
})
if (!this.continueAdd) {
this.close()
}
}
})
// this.saveSegment(params)
},
close() {
this.$emit('close', true)
},
addSegment() {
this.$emit('addSegment', this.text)
this.text = ''
}
}
}
</script>
<style scoped lang="scss">
::v-deep .text-input {
height: 100%;
.el-textarea__inner {
padding: 5px 8px;
height: calc(100vh - 310px);
//border: unset;
//resize: unset;
//height: 100%;
}
}
::v-deep .text-input-q-a {
height: 100%;
.el-textarea__inner {
padding: 5px 8px;
height: calc(100vh - 700px);
//border: unset;
//resize: unset;
//height: 100%;
}
}
.add-segment-container {
height: 100%;
overflow-y: auto;
overflow-x: hidden;
&::-webkit-scrollbar {
width: 0;
}
& .add-segment-content {
//background: rebeccapurple;
//overflow-y: hidden;
overflow-x: hidden;
& .add-segment-text {
height: calc(100vh - 310px);
//height: 100%;
//height: 999px;
//background: blue;
}
}
& .add-segment-bottom {
width: 100%;
padding: 10px 0 0 0;
position: sticky;
background: #fff;
bottom: 0;
& .add-segment-tags {
height: 100px;
//background: red;
}
}
}
.input-new-tag {
width: 100px;
}
</style>

View File

@@ -4,15 +4,16 @@ import TextModel from '@/views/knowledge/detail/components/documentDetail/TextMo
import QAModel from '@/views/knowledge/detail/components/documentDetail/QAModel.vue' import QAModel from '@/views/knowledge/detail/components/documentDetail/QAModel.vue'
import RenderFile from '@/components/RenderFile/Index.vue' import RenderFile from '@/components/RenderFile/Index.vue'
import MetadataOperator from '@/views/knowledge/detail/components/metaData/MetadataOperator.vue' import MetadataOperator from '@/views/knowledge/detail/components/metaData/MetadataOperator.vue'
import AddSegment from '@/views/knowledge/detail/components/documentDetail/addSegment.vue'
export default { export default {
name: 'index', name: 'index',
components: { MetadataOperator, QAModel, TextModel, RenderFile }, components: { MetadataOperator, QAModel, TextModel, RenderFile, AddSegment },
data() { data() {
return { return {
iframeSrc: window.location.origin, iframeSrc: window.location.origin,
newForm: {}, newForm: {},
descriptions: null descriptions: null,
addSegmentDialog: false
} }
}, },
props: { props: {
@@ -75,6 +76,11 @@ export default {
} }
}, },
methods: { methods: {
// 新增分段
addSegment() {
this.addSegmentDialog = true
},
toSplit() { toSplit() {
this.$router.push({ this.$router.push({
path: '/knowledge/detail/create', path: '/knowledge/detail/create',
@@ -163,11 +169,26 @@ export default {
</div> </div>
<div class="card-body"> <div class="card-body">
<el-row :gutter="20"> <div :class="!noEdit ? 'mt10' : 'mt40'">
<el-button
class="line-button"
size="medium"
v-if="!noEdit"
@click="addSegment"
>新增分段</el-button
>
</div>
<el-row :gutter="20" class="mt10">
<!-- 左侧表单内容和知识内容 --> <!-- 左侧表单内容和知识内容 -->
<el-col :span="newForm.knowledgeName.indexOf('.pdf') >= 0 ? 12 : 24"> <el-col
:span="
newForm.knowledgeName && newForm.knowledgeName.indexOf('.pdf') >= 0
? 12
: 24
"
>
<!-- 知识内容 --> <!-- 知识内容 -->
<div class="content-card el-card mt20"> <div class="content-card el-card ">
<div class="knowledge-content" v-if="descriptions"> <div class="knowledge-content" v-if="descriptions">
<text-model <text-model
:noEdit="noEdit" :noEdit="noEdit"
@@ -196,8 +217,13 @@ export default {
</el-col> </el-col>
<!-- 右侧原文内容 --> <!-- 右侧原文内容 -->
<el-col :span="12" v-if="newForm.knowledgeName.indexOf('.pdf') >= 0"> <el-col
<div class="content-card el-card full-height mt20"> :span="12"
v-if="
newForm.knowledgeName && newForm.knowledgeName.indexOf('.pdf') >= 0
"
>
<div class="content-card el-card full-height ">
<!-- <metadata-operator--> <!-- <metadata-operator-->
<!-- @openMetaDrawer="openMetaDrawer"--> <!-- @openMetaDrawer="openMetaDrawer"-->
<!-- ></metadata-operator>--> <!-- ></metadata-operator>-->
@@ -206,6 +232,21 @@ export default {
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
<el-drawer
title="新增分段"
:visible.sync="addSegmentDialog"
:wrapperClosable="false"
width="50%"
append-to-body
:modal="false"
>
<add-segment
:descriptions="descriptions"
:document-id="form.id"
@close="addSegmentDialog = false"
></add-segment>
</el-drawer>
</div> </div>
</template> </template>
@@ -218,12 +259,12 @@ export default {
} }
.content-card { .content-card {
margin-bottom: 20px; //margin-bottom: 20px;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
.knowledge-content { .knowledge-content {
height: calc(100vh - 240px); height: calc(100vh - 250px);
overflow-y: auto; overflow-y: auto;
//&::-webkit-scrollbar { //&::-webkit-scrollbar {
// width: 4px; // width: 4px;
@@ -232,7 +273,7 @@ export default {
} }
.full-height { .full-height {
height: calc(100vh - 240px); height: calc(100vh - 250px);
padding-bottom: 20px; padding-bottom: 20px;
} }