2025-5-20 / 新功能 - 多文件上传

这是一个完整功能

操作:
  多文件上传 只有两步  不做题词拆分
  点击弹窗的下拉 即可份文件预览
This commit is contained in:
陈昱达
2025-05-20 14:40:15 +08:00
parent b0dbb80a50
commit cadd640e9e
11 changed files with 431 additions and 88 deletions

View File

@@ -0,0 +1,9 @@
.el-dropdown-menu__item:focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
background-color: $--color-primary;
color: #fff;
}
.el-dropdown-link {
cursor: pointer;
color: $--color-primary;
}

View File

@@ -72,3 +72,21 @@
//
//}
//.el-pagination.is-background .el-pager li:not(.disabled):hover
.tableBtn {
padding: 5px;
&.is-plain {
&:hover {
background: $--color-primary;
color: #fff;
}
}
&.el-button--danger {
&.is-plain:focus,
&.is-plain:hover {
color: #fff;
background: #ff1a1a;
border-color: #ff1a1a;
}
}
}

View File

@@ -10,6 +10,7 @@
@import 'renderSass/message';
@import 'renderSass/upload';
@import 'renderSass/slider';
@import 'renderSass/drop';
html,
body,

View File

@@ -9,6 +9,9 @@
:closeOnClickModal="closeOnClickModal"
:close-on-press-escape="closeOnPressEscape"
>
<div slot="title">
<slot name="title">{{ title }}</slot>
</div>
<div class="render-dialog-body">
<slot name="default"></slot>
</div>

View File

@@ -58,13 +58,13 @@
class="normal-button"
:disabled="disabled"
icon="el-icon-delete"
@click="del(scope.row, scope.$index)"
@click.stop="del(scope.row, scope.$index)"
></el-button>
<el-button
class="normal-button"
icon="el-icon-edit-outline"
:disabled="disabled"
@click="edit(scope.row, scope.$index)"
@click.stop="edit(scope.row, scope.$index)"
></el-button>
</div>
<!--如果渲染的是selfBtn-->
@@ -75,10 +75,11 @@
:disabled="
item.disabled !== undefined ? item.disabled : disabled
"
:class="item.class"
:type="btn.type ? btn.type : 'primary'"
:size="btn.size ? btn.size : 'mini'"
:key="btnIndex"
@click="handlerMethods(scope.row, scope.$index, btn)"
@click.stop="handlerMethods(scope.row, scope.$index, btn)"
:label="btn.name"
>
{{ btn.name }}
@@ -133,7 +134,7 @@
<el-button
type="primary"
plain
@click="
@click.stop="
e => {
addRow(scope.row, scope.$index)
}
@@ -147,7 +148,7 @@
<el-button
type="danger"
plain
@click="
@click.stop="
e => {
delRow(scope.row, scope.$index)
}

View File

@@ -1,49 +1,102 @@
<template>
<div id="preprocessing-container " class="mt20">
<el-form label-width="180px" :model="form" ref="processForm">
<el-form-item label="数据来源:" required prop="radio" position="top" class="el-form--label-top">
<el-radio-group v-model="form.radio" size="medium" @change="getFileType">
<el-radio-button label="1" size="medium">使用本地文件</el-radio-button>
<el-radio-button label="2" size="medium">使用通用知识文件模板</el-radio-button>
<el-form-item
label="数据来源:"
required
prop="radio"
position="top"
class="el-form--label-top"
>
<el-radio-group
v-model="form.radio"
size="medium"
@change="getFileType"
>
<el-radio-button label="1" size="medium"
>使用本地文件</el-radio-button
>
<el-radio-button label="2" size="medium"
>使用通用知识文件模板</el-radio-button
>
</el-radio-group>
<div class="mt10" v-if="form.radio === '2'">
<el-button type="primary" size="medium" class="fs14" @click="downloadTemplate">下载知识文件模板</el-button>
<el-button
type="primary"
size="medium"
class="fs14"
@click="downloadTemplate"
>下载知识文件模板</el-button
>
</div>
</el-form-item>
<!-- 文件上传-->
<el-form-item label="" required prop="file" label-width="0" class="el-form--label-top">
<el-form-item
label=""
required
prop="file"
label-width="0"
class="el-form--label-top"
>
<div
@click="createFiled"
@click.stop="createFiled"
@dragover.prevent="handleDragOver"
@dragleave.prevent="handleDragLeave"
@drop.prevent="handleDrop"
class="upload-demo"
:class="{ 'drag-over': isDragOver }"
>
<el-empty v-if="!filed">
<el-empty v-if="fileList.length === 0">
<template #image>
<img :src="uloadPng" alt="" style='width: 50px;height: 50px;'></img>
<img :src="uloadPng" alt="" style="width: 50px;height: 50px;" />
</template>
<template #description>
<div class='flex flex-direction-c'>
<span class='upload-tip'>点击或将文件拖拽到这里上传</span>
<span class='upload-tip-field'>支持扩展名.xlsx.doc.pdf.txt.docx</span>
<div class="flex flex-direction-c">
<span class="upload-tip">点击或将文件拖拽到这里上传</span>
<span class="upload-tip-field"
>支持扩展名.xlsx.doc.pdf.txt.docx</span
>
</div>
</template>
</el-empty>
<el-result
v-else
icon="success"
title="文件上传成功"
:sub-title="`已上传文件:${filed.name}`"
style="height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;"
>
<template slot="extra">
<el-button type="primary" size="medium" @click.stop="createFiled">重新上传</el-button>
</template>
</el-result>
<div v-else>
<el-result
v-if="fileList.length === 1"
icon="success"
title="文件上传成功"
:sub-title="`已上传文件:${fileList[0].name}`"
style="height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;"
>
<template slot="extra">
<el-button
type="primary"
size="medium"
@click.stop="removeFile(0)"
>移除</el-button
>
</template>
</el-result>
<el-result v-else class="more-files">
<template slot="icon">
<div class="text-left">
<el-button class="line-button" size="medium">
文件上传</el-button
>
</div>
</template>
<template slot="extra" style="width: 100%;">
<r-table
:data="fileList"
:columns="columns"
style="width: 100%;"
@removeFiled="removeFiled"
:deletion="false"
></r-table>
</template>
</el-result>
</div>
</div>
</el-form-item>
@@ -52,21 +105,40 @@
<el-form-item label="是否进行预处理:" required prop="beMinerU">
<template slot="label">
是否进行预处理
<el-tooltip class="item" effect="dark" content="通过整合最先进的文档解析模型来提高内容提取质量" placement="top">
<el-tooltip
class="item"
effect="dark"
content="通过整合最先进的文档解析模型来提高内容提取质量"
placement="top"
>
<i class="el-icon-info ml5" style="color: #909399;"></i>
</el-tooltip>
</template>
<el-switch v-model="form.beMinerU" size="medium">
<el-switch
v-model="form.beMinerU"
size="medium"
:disabled="fileList.length > 1"
>
<!-- <el-radio :label="true" size="medium"></el-radio>-->
<!-- <el-radio :label="false" size="medium"></el-radio>-->
</el-switch>
</el-form-item>
<el-form-item label="是否ocr协助处理" v-if="form.beMinerU" required prop="beOcr">
<el-form-item
label="是否ocr协助处理"
v-if="form.beMinerU"
required
prop="beOcr"
>
<template slot="label">
是否ocr协助处理
<el-tooltip class="item" effect="dark" content="能更好的协助处理图片、表格类数据" placement="top">
<el-tooltip
class="item"
effect="dark"
content="能更好的协助处理图片、表格类数据"
placement="top"
>
<i class="el-icon-info ml5" style="color: #909399;"></i>
</el-tooltip>
@@ -87,16 +159,18 @@
<!-- </el-drawer>-->
</div>
</template>
<script>
import { uploadFileByCustom, uploadFileByTemplate } from '@/api/generatedApi'
import { downloadKnowledgeTemplate } from '@/api/knowledge/task-page'
import uloadPng from '@/assets/images/konwledge/upload.png'
export default {
name: 'preprocessing',
data() {
return {
uloadPng,
filed: null,
fileList: [], // 文件列表
uploadLoading: false,
headers: {},
fieldList: [],
@@ -106,31 +180,84 @@ export default {
beOcr: false,
datasetId: this.$route.query.datasetId
},
previewDialogVisible: false, // 添加对话框显示控制变量
previewDialogVisible: false,
documentId: '',
isDragOver: false
}
},
props: {},
watch: {
'form.beMinerU': {
handler(value) {
// 如果不进行预处理不进行ocr
if (!value) {
this.form.beOcr = false
fileList: {
handler(val) {
if (val.length > 1) {
this.form.beMinerU = false
}
},
deep: true
}
},
components: {},
filters: {},
computed: {
columns: vm => {
return [
{
type: 'index',
title: '序号'
},
{
title: '文件名称',
prop: 'name'
},
{
title: '文件大小',
prop: 'size',
render: (h, params) => {
return h('span', vm.formatFileSize(params.row.size))
}
},
{
title: '文件类型',
prop: 'type',
render: (h, params) => {
// 根据文件类型展示 是什么类型文件
switch (params.row.type) {
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
return h('span', 'Excel')
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
return h('span', 'Word')
case 'application/pdf':
return h('span', 'PDF')
case 'text/plain':
return h('span', 'TXT')
}
return h('span', params.row.type)
}
},
{
title: '操作',
selfBtn: [
{
name: '移除',
size: 'mini',
type: 'primary',
method: 'removeFiled',
class: 'tableBtn'
}
]
}
]
}
},
methods: {
removeFiled(item) {
this.removeFile(item.index)
},
formatFileSize(size) {
return (size / 1024 / 1024).toFixed(2) + 'MB'
},
getFileType() {
this.$emit('getFileType', this.form.radio)
},
downloadTemplate() {
console.log(`下载模板的链接是:${downloadKnowledgeTemplate().url}`)
window.open(downloadKnowledgeTemplate().url, '_blank')
},
@@ -138,18 +265,40 @@ export default {
createFiled() {
let input = document.createElement('input')
input.type = 'file'
input.multiple = true // 支持多文件选择
// 对文件的类型做出限制
input.accept = '.xlsx,.doc,.docx,.pdf,.txt'
input.onchange = e => {
this.filed = e.target.files[0]
const newFiles = Array.from(e.target.files)
if (this.fileList.length + newFiles.length > 10) {
this.$message.error('最多只能上传10个文件')
return
}
// 校验 最新上传的文件是否之前上传过
for (let i = 0; i < newFiles.length; i++) {
for (let j = 0; j < this.fileList.length; j++) {
if (newFiles[i].name === this.fileList[j].name) {
this.$message.error('请勿重复上传文件,请检查文件')
return
}
}
}
this.fileList = [...this.fileList, ...newFiles] // 将新选择的文件添加到文件列表
}
input.click()
},
// 新增:移除文件
removeFile(index) {
this.fileList.splice(index, 1)
},
uploadFiled(back) {
if (!this.filed) {
if (this.fileList.length === 0) {
this.$message({
type: 'error',
message: '请上传文件'
@@ -159,17 +308,20 @@ export default {
// 0否 1是
let formData = new FormData()
formData.append('file', this.filed)
this.fileList.forEach(file => {
formData.append('file', file)
})
formData.append('datasetId', this.form.datasetId)
let api = this.form.radio === '1' ? uploadFileByCustom : uploadFileByTemplate
let api =
this.form.radio === '1' ? uploadFileByCustom : uploadFileByTemplate
if (this.form.radio === '1') {
formData.append('useMinerU', this.form.beMinerU ? '1': '0')
formData.append('useOcr', this.form.beOcr? '1': '0')
formData.append('useMinerU', this.form.beMinerU ? '1' : '0')
formData.append('useOcr', this.form.beOcr ? '1' : '0')
}
api(formData).then(response => {
if(response){
if (response) {
this.documentId = response.content.content
if(back){
if (back) {
back(this.documentId)
} else {
// 向上导入documentId
@@ -183,10 +335,9 @@ export default {
}
}
}
})
},
handleDragOver() {
this.isDragOver = true
},
@@ -198,8 +349,13 @@ export default {
handleDrop(event) {
this.isDragOver = false
const files = event.dataTransfer.files
this.filed = files[0]
if (this.fileList.length + files.length > 10) {
this.$message.error('最多只能上传10个文件')
return
}
this.fileList = [...this.fileList, ...Array.from(files)] // 将拖拽的文件添加到文件列表
},
handleUploadSuccess(response, file) {
this.fieldList = [file]
this.uploadLoading = false
@@ -236,30 +392,35 @@ export default {
// 返回 true 继续上传,返回 false 停止上传
return true
}
},
created() {},
mounted() {},
computed: {}
}
}
</script>
<style>
.more-files > .el-result__extra,
.more-files > .el-result__icon {
width: 100%;
}
</style>
<style scoped lang="scss">
@import '@/assets/sass/renderSass/theme.scss';
.flex-direction-c{
.flex-direction-c {
flex-direction: column;
line-height: 20px;
}
.upload-tip{
.upload-tip {
font-family: PingFangSC, PingFang SC;
font-weight: 500;
font-size: 14px;
color: #5F5E68;
color: #5f5e68;
font-style: normal;
}
.upload-tip-field{
.upload-tip-field {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 13px;
color: #B1B5C7;
color: #b1b5c7;
font-style: normal;
}
.upload-demo {

View File

@@ -63,6 +63,7 @@
<!-- 添加预览组件 -->
<split-preview
:documentList="documentList"
:documentId="documentId"
:visible.sync="previewVisible"
:preview-data="previewData"
@@ -71,6 +72,7 @@
@confirm="handlePreviewConfirm"
@handleReUpload="handleReUpload"
@handleClose="handleClose"
@changePage="changePage"
/>
</div>
</template>
@@ -88,6 +90,7 @@ export default {
},
data() {
return {
splitIndex: 0,
dialogVisible: false,
activeIndex: 0,
previewVisible: false, // 控制预览组件显示
@@ -112,6 +115,10 @@ export default {
}
},
props: {
documentList: {
type: Array,
default: () => []
},
documentId: {
type: String,
default: ''
@@ -120,6 +127,10 @@ export default {
watch: {},
filters: {},
methods: {
async changePage(index) {
this.previewData = await this.getPreviewOperation(this.documentId, index)
},
handleClick(index) {
this.activeIndex = index
if (index === 1) {
@@ -131,10 +142,15 @@ export default {
// 构建API请求参数
buildExecSplitParams(documentId) {
const params = {
let params = {
documentId: documentId,
beAuto: this.activeIndex === 0
}
let splitDocId = documentId.split(',')
if (splitDocId.length > 1) {
params.documentId = undefined
params.documentIds = splitDocId
}
if (!params.beAuto && this.$refs.customTable.ruleId) {
params.rulesId = this.$refs.customTable.ruleId
@@ -161,7 +177,11 @@ export default {
},
// 获取拆分预览
async getPreviewOperation(documentId) {
async getPreviewOperation(documentId, index) {
let splitDocId = documentId.split(',')
if (splitDocId.length > 1) {
documentId = splitDocId[index ? index : 0]
}
const res = await splitResultPreview({ documentId })
if (res.content.result !== '0') {
const errorMsg =

View File

@@ -6,6 +6,26 @@
width="60%"
:before-close="handleClose"
>
<template slot="title">
<div class="flex align-items-c" style="gap:10px">
<span>拆分结果预览</span>
<el-dropdown @command="command">
<span class="el-dropdown-link">
{{ documentList[activeIndex].name
}}<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
v-for="(item, index) in documentList"
:command="index"
:key="item.id"
>{{ item.name }}</el-dropdown-item
>
</el-dropdown-menu>
</el-dropdown>
</div>
</template>
<div v-loading="loading" class="preview-content">
<template v-if="!loading && previewData">
<el-tree
@@ -23,7 +43,13 @@
</div>
<span slot="footer" class="dialog-footer">
<el-button size="medium" @click="handleReUpload">重新上传</el-button>
<el-button size="medium" type="primary" @click="handleConfirm"
<!-- <el-button size="medium" @click="upFiled">前一个</el-button>-->
<!-- <el-button size="medium" @click="downFiled">后一个</el-button>-->
<el-button
v-if="documentList.length === 1"
size="medium"
type="primary"
@click="handleConfirm"
>下一步</el-button
>
<el-button
@@ -44,6 +70,10 @@ import { embedding } from '@/api/generatedApi'
export default {
name: 'SplitPreview',
props: {
documentList: {
type: Array,
default: () => []
},
documentId: {
type: String,
default: ''
@@ -66,6 +96,7 @@ export default {
},
data() {
return {
activeIndex: 0,
defaultProps: {
children: 'children',
label: 'paragraphTitle'
@@ -73,6 +104,19 @@ export default {
}
},
methods: {
upFiled() {
this.$emit('changePage', 'up')
},
downFiled() {
this.$emit('changePage', 'down')
},
command(index) {
if (index === this.activeIndex) {
return false
}
this.activeIndex = index
this.$emit('changePage', this.activeIndex)
},
emitKnowledgeDataset() {
this.$router.push({
path: '/knowledge/reviewKnowledge',
@@ -105,7 +149,8 @@ export default {
handleConfirm() {
this.$emit('confirm')
}
}
},
watch: {}
}
</script>

View File

@@ -67,6 +67,7 @@
:extract-results="extractResults"
:is-auto-extract="activeIndex === 0"
:loading="previewLoading"
:documentList="documentList"
@confirm="handlePreviewConfirm"
/>
</div>
@@ -113,7 +114,12 @@ export default {
documentId: ''
}
},
props: {},
props: {
documentList: {
type: Array,
default: () => []
}
},
watch: {},
filters: {},
methods: {
@@ -128,10 +134,15 @@ export default {
// 构建执行题词的参数
buildExecExtractParams(documentId) {
const params = {
let params = {
documentId: documentId,
beAuto: this.activeIndex === 0
}
let splitDocIds = documentId.split(',')
if (splitDocIds.length > 1) {
params.documentId = undefined
params.documentIds = splitDocIds
}
if (!params.beAuto && this.$refs.customTable.ruleId) {
params.rulesId = this.$refs.customTable.ruleId

View File

@@ -51,6 +51,7 @@
class="mt10"
v-else
:documentId="documentId"
:documentList="this.documentList"
@saveMarkDown="saveMarkDown"
></r-miner-u>
</div>
@@ -58,11 +59,16 @@
<step-split-config
ref="splitConfig"
v-if="active === 1"
:documentList="this.documentList"
@previewConfirmed="handlePreviewConfirm"
@handleReUpload="handleReUpload"
:documentId="documentId"
></step-split-config>
<step-words ref="words" v-if="active === 2"></step-words>
<step-words
ref="words"
v-if="active === 2"
:documentList="this.documentList"
></step-words>
</transition>
</div>
</div>
@@ -120,7 +126,7 @@ import StepPreprocessing from './components/preprocessing.vue'
import SplitConfig from '@/views/knowledge/detail/components/split/Index.vue'
import Words from '@/views/knowledge/detail/components/words/Index.vue'
import magic from '@/assets/images/konwledge/magic.png'
import { directEmbedding } from '@/api/generatedApi'
import { datasetDocumentEx, directEmbedding } from '@/api/generatedApi'
// import StepC
export default {
name: 'create',
@@ -130,7 +136,8 @@ export default {
visible: false,
active: 0,
documentId: '1365038001244180480',
isMd: false
isMd: false,
documentList: []
}
},
props: {},
@@ -185,9 +192,32 @@ export default {
}
})
},
getDocumentId(id) {
async getDocumentId(id) {
this.documentId = id
let splitDocIds = this.documentId.split(',')
for (let i = 0; i < splitDocIds.length; i++) {
let content = await this.getFileDetail(splitDocIds[i])
this.documentList.push({
name: content.knowledgeName,
id: content.id
})
}
},
// 获取文件详情 主要是名字与ID
async getFileDetail(documentId) {
const res = await datasetDocumentEx({ documentId })
if (res.content.result !== '0') {
const errorMsg =
'获取文件详情失败: ' + (res.content.resultMessage || '未知错误')
this.$message.error(errorMsg)
throw new Error(errorMsg)
}
return res.content.content
},
async nextStep() {
if (this.active === 0) {
this.$refs.stepPreProcessing.uploadFiled()
@@ -211,6 +241,8 @@ export default {
} else {
this.active = 0
}
this.documentList = []
}
},
created() {},

View File

@@ -141,9 +141,24 @@
style="width: 49.5%;height: calc(100vh - 110px)"
class="el-card review"
>
<div class="title">
{{ documentDetail.knowledgeName }}
<div class="title flex align-items-c" style="gap: 10px">
<el-dropdown @command="command">
<span class="el-dropdown-link">
{{
documentDetail.length > 0
? documentDetail[activeIndex].name
: ''
}}<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
v-for="(item, index) in documentDetail"
:command="index"
:key="item.id"
>{{ item.name }}</el-dropdown-item
>
</el-dropdown-menu>
</el-dropdown>
<span class="block" v-if="viewsDetail.total_segments">
预估块{{ viewsDetail.total_segments }}
</span>
@@ -256,7 +271,8 @@ export default {
chunkOverlap: 400
},
datasetDetail: {},
documentDetail: {},
activeIndex: 0,
documentDetail: [],
viewsDetail: {}
}
},
@@ -265,12 +281,24 @@ export default {
components: {},
filters: {},
methods: {
command(index) {
if (index === this.activeIndex) {
return
}
this.activeIndex = index
this.review_logs()
},
resetForm() {
this.form = JSON.parse(JSON.stringify(this.reset))
},
review_logs() {
let { documentId, active } = this.$route.query
let splitDocIds = documentId.split(',')
let api = segmentEstimate
switch (active) {
case '0':
@@ -287,7 +315,7 @@ export default {
break
}
api({
documentId,
documentId: splitDocIds[this.activeIndex],
segmentConfig: {
...this.form,
removeExtraSpaces: this.form.removeExtraSpaces ? 1 : 0,
@@ -299,13 +327,18 @@ export default {
},
// 获取文档详情
getDocumentDetail() {
async getDocumentDetail() {
let { documentId } = this.$route.query
datasetDocumentEx({ documentId }).then(res => {
if (res) {
this.documentDetail = res.content.content
}
})
let splitDocIds = documentId.split(',')
for (let i = 0; i < splitDocIds.length; i++) {
let content = await datasetDocumentEx({ documentId: splitDocIds[i] })
this.documentDetail.push({
name: content.content.content.knowledgeName,
id: content.content.content.id
})
}
},
// 获取知识库详情
@@ -320,6 +353,8 @@ export default {
// 保存 处理
saveUs() {
let { documentId, active } = this.$route.query
let splitDocIds = documentId.split(',')
let api = directEmbedding
switch (active) {
case '0':
@@ -336,14 +371,21 @@ export default {
break
}
api({
let params = {
documentId,
segmentConfig: {
...this.form,
removeExtraSpaces: this.form.removeExtraSpaces ? 1 : 0,
removeUrlsEmails: this.form.removeUrlsEmails ? 1 : 0
}
}).then(res => {
}
if (splitDocIds.length > 1) {
params.documentIds = splitDocIds
params.documentId = undefined
}
api(params).then(res => {
if (res) {
this.$router.push({
path: '/knowledge/detail',
@@ -403,7 +445,7 @@ export default {
& .review {
& .title {
height: 40px;
line-height: 40px;
//line-height: 40px;
border-bottom: 1px solid #ebeef5;
font-weight: 600;
font-size: 15px;