Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
7cd6a5d9
authored
Feb 28, 2025
by
YunaiV
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【功能新增】AI:知识库文档上传:20%,UploadStep 基本搭建出来
parent
b7d7b11d
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
282 additions
and
183 deletions
+282
-183
src/utils/index.ts
+58
-0
src/views/ai/knowledge/document/create/UploadStep.vue
+223
-177
src/views/ai/knowledge/document/create/index.vue
+1
-6
No files found.
src/utils/index.ts
View file @
7cd6a5d9
...
...
@@ -117,6 +117,64 @@ export function toAnyString() {
}
/**
* 根据支持的文件类型生成 accept 属性值
*
* @param supportedFileTypes 支持的文件类型数组,如 ['PDF', 'DOC', 'DOCX']
* @returns 用于文件上传组件 accept 属性的字符串
*/
export
const
generateAcceptedFileTypes
=
(
supportedFileTypes
:
string
[]):
string
=>
{
const
allowedExtensions
=
supportedFileTypes
.
map
((
ext
)
=>
ext
.
toLowerCase
())
const
mimeTypes
:
string
[]
=
[]
// 添加常见的 MIME 类型映射
if
(
allowedExtensions
.
includes
(
'txt'
))
{
mimeTypes
.
push
(
'text/plain'
)
}
if
(
allowedExtensions
.
includes
(
'pdf'
))
{
mimeTypes
.
push
(
'application/pdf'
)
}
if
(
allowedExtensions
.
includes
(
'html'
)
||
allowedExtensions
.
includes
(
'htm'
))
{
mimeTypes
.
push
(
'text/html'
)
}
if
(
allowedExtensions
.
includes
(
'csv'
))
{
mimeTypes
.
push
(
'text/csv'
)
}
if
(
allowedExtensions
.
includes
(
'xlsx'
)
||
allowedExtensions
.
includes
(
'xls'
))
{
mimeTypes
.
push
(
'application/vnd.ms-excel'
)
mimeTypes
.
push
(
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
)
}
if
(
allowedExtensions
.
includes
(
'docx'
)
||
allowedExtensions
.
includes
(
'doc'
))
{
mimeTypes
.
push
(
'application/msword'
)
mimeTypes
.
push
(
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
)
}
if
(
allowedExtensions
.
includes
(
'pptx'
)
||
allowedExtensions
.
includes
(
'ppt'
))
{
mimeTypes
.
push
(
'application/vnd.ms-powerpoint'
)
mimeTypes
.
push
(
'application/vnd.openxmlformats-officedocument.presentationml.presentation'
)
}
if
(
allowedExtensions
.
includes
(
'xml'
))
{
mimeTypes
.
push
(
'application/xml'
)
mimeTypes
.
push
(
'text/xml'
)
}
if
(
allowedExtensions
.
includes
(
'md'
)
||
allowedExtensions
.
includes
(
'markdown'
))
{
mimeTypes
.
push
(
'text/markdown'
)
}
if
(
allowedExtensions
.
includes
(
'epub'
))
{
mimeTypes
.
push
(
'application/epub+zip'
)
}
if
(
allowedExtensions
.
includes
(
'eml'
))
{
mimeTypes
.
push
(
'message/rfc822'
)
}
if
(
allowedExtensions
.
includes
(
'msg'
))
{
mimeTypes
.
push
(
'application/vnd.ms-outlook'
)
}
// 添加文件扩展名
const
extensions
=
allowedExtensions
.
map
((
ext
)
=>
`.
${
ext
}
`
)
return
[...
mimeTypes
,
...
extensions
].
join
(
','
)
}
/**
* 首字母大写
*/
export
function
firstUpperCase
(
str
:
string
)
{
...
...
src/views/ai/knowledge/document/create/UploadStep.vue
View file @
7cd6a5d9
<
template
>
<el-form
ref=
"formRef"
:model=
"modelData"
:rules=
"rules"
label-width=
"120px"
class=
"mt-20px"
>
<el-form-item
label=
"文档名称"
prop=
"name"
class=
"mb-20px"
>
<el-input
v-model=
"modelData.name"
clearable
placeholder=
"请输入文档名称"
/>
</el-form-item>
<el-form-item
label=
"知识库"
prop=
"knowledgeBaseId"
class=
"mb-20px"
>
<el-select
class=
"!w-full"
v-model=
"modelData.knowledgeBaseId"
clearable
placeholder=
"请选择知识库"
>
<el-option
v-for=
"base in knowledgeBaseList"
:key=
"base.id"
:label=
"base.name"
:value=
"base.id"
/>
</el-select>
</el-form-item>
<el-form-item
label=
"文档类型"
prop=
"documentType"
class=
"mb-20px"
>
<el-select
class=
"!w-full"
v-model=
"modelData.documentType"
clearable
placeholder=
"请选择文档类型"
>
<el-option
label=
"PDF文档"
value=
"pdf"
/>
<el-option
label=
"Word文档"
value=
"word"
/>
<el-option
label=
"文本文件"
value=
"text"
/>
<el-option
label=
"网页链接"
value=
"url"
/>
</el-select>
</el-form-item>
<el-form-item
label=
"文档内容"
prop=
"content"
class=
"mb-20px"
v-if=
"modelData.documentType === 'text'"
>
<el-input
v-model=
"modelData.content"
type=
"textarea"
:rows=
"6"
placeholder=
"请输入文档内容"
/>
</el-form-item>
<el-form-item
label=
"网页链接"
prop=
"url"
class=
"mb-20px"
v-if=
"modelData.documentType === 'url'"
>
<el-input
v-model=
"modelData.url"
clearable
placeholder=
"请输入网页链接"
/>
</el-form-item>
<el-form-item
label=
"上传文件"
prop=
"file"
class=
"mb-20px"
v-if=
"['pdf', 'word'].includes(modelData.documentType)"
>
<el-upload
class=
"upload-demo"
drag
action=
"#"
:auto-upload=
"false"
:on-change=
"handleFileChange"
:limit=
"1"
>
<el-icon
class=
"el-icon--upload"
><upload-filled
/></el-icon>
<div
class=
"el-upload__text"
>
拖拽文件到此处,或
<em>
点击上传
</em>
</div>
<template
#
tip
>
<div
class=
"el-upload__tip"
>
{{
modelData
.
documentType
===
'pdf'
?
'PDF文件'
:
'Word文件(.docx, .doc)'
}}
<el-form
ref=
"formRef"
:model=
"modelData"
label-width=
"0"
class=
"mt-20px"
>
<el-form-item
class=
"mb-20px"
>
<div
class=
"w-full"
>
<div
class=
"w-full border-2 border-[#dcdfe6] rounded-md text-center hover:border-[#409eff]"
>
<el-upload
ref=
"uploadRef"
class=
"upload-demo"
drag
:action=
"uploadUrl"
:auto-upload=
"true"
:on-success=
"handleUploadSuccess"
:on-error=
"handleUploadError"
:on-change=
"handleFileChange"
:on-remove=
"handleFileRemove"
:before-upload=
"beforeUpload"
:http-request=
"httpRequest"
:file-list=
"fileList"
:multiple=
"true"
:show-file-list=
"false"
:accept=
"acceptedFileTypes"
>
<div
class=
"flex flex-col items-center justify-center py-20px"
>
<el-icon
class=
"text-[48px] text-[#c0c4cc] mb-10px"
><upload-filled
/></el-icon>
<div
class=
"el-upload__text text-[16px] text-[#606266]"
>
拖拽文件至此,或者
<em
class=
"text-[#409eff] not-italic cursor-pointer"
>
选择文件
</em></div
>
<div
class=
"el-upload__tip mt-10px text-[#909399] text-[12px]"
>
已支持
{{
supportedFileTypes
.
join
(
'、'
)
}}
,每个文件不超过
{{
maxFileSize
}}
MB。
</div>
</div>
</el-upload>
</div>
<div
v-if=
"modelData.list && modelData.list.length > 0"
class=
"mt-15px grid grid-cols-1 gap-3"
>
<div
v-for=
"(file, index) in modelData.list"
:key=
"index"
class=
"flex justify-between items-center p-10px border-2 border-[#c0c4cc] rounded-md shadow-sm hover:border-[#409eff] transition-colors duration-300"
>
<div
class=
"flex items-center"
>
<el-icon
class=
"mr-8px text-[#909399]"
><document
/></el-icon>
<span
class=
"text-[14px] text-[#606266] break-all"
>
{{
file
.
name
}}
</span>
</div>
<el-button
type=
"danger"
link
@
click=
"removeFile(index)"
>
<el-icon><delete
/></el-icon>
</el-button>
</div>
</
template
>
</
el-upload
>
</
div
>
</
div
>
</el-form-item>
<!-- 添加下一步按钮 -->
<el-form-item>
<div
class=
"flex justify-end"
>
<el-button
type=
"primary"
@
click=
"handleNextStep"
>
下一步
</el-button>
<div
class=
"flex justify-end w-full"
>
<el-button
type=
"primary"
@
click=
"handleNextStep"
:disabled=
"!isAllUploaded"
>
下一步
</el-button>
</div>
</el-form-item>
</el-form>
</
template
>
<
script
lang=
"ts"
setup
>
import
{
PropType
}
from
'vue'
import
{
UploadFilled
}
from
'@element-plus/icons-vue'
import
{
PropType
,
ref
,
computed
,
inject
,
getCurrentInstance
,
onMounted
}
from
'vue'
import
{
UploadFilled
,
Document
,
Delete
}
from
'@element-plus/icons-vue'
import
{
useMessage
}
from
'@/hooks/web/useMessage'
import
{
useUpload
}
from
'@/components/UploadFile/src/useUpload'
import
{
generateAcceptedFileTypes
}
from
'@/utils'
const
props
=
defineProps
({
modelValue
:
{
...
...
@@ -98,128 +81,191 @@ const props = defineProps({
const
emit
=
defineEmits
([
'update:modelValue'
])
// 表单引用
const
formRef
=
ref
()
const
formRef
=
ref
()
// 表单引用
const
uploadRef
=
ref
()
// 上传组件引用
const
parent
=
inject
(
'parent'
,
null
)
// 获取父组件实例
const
{
uploadUrl
,
httpRequest
}
=
useUpload
()
// 使用上传组件的钩子
const
message
=
useMessage
()
// 消息弹窗
const
fileList
=
ref
([])
// 文件列表
const
uploadingCount
=
ref
(
0
)
// 上传中的文件数量
// 支持的文件类型和大小限制
const
supportedFileTypes
=
[
'TXT'
,
'MARKDOWN'
,
'MDX'
,
'PDF'
,
'HTML'
,
'XLSX'
,
'XLS'
,
'DOC'
,
'DOCX'
,
'CSV'
,
'EML'
,
'MSG'
,
'PPTX'
,
'XML'
,
'EPUB'
,
'PPT'
,
'MD'
,
'HTM'
]
const
allowedExtensions
=
supportedFileTypes
.
map
((
ext
)
=>
ext
.
toLowerCase
())
// 小写的扩展名列表
const
maxFileSize
=
15
// 最大文件大小(MB)
//
获取父组件实例
const
parent
=
inject
(
'parent'
,
null
)
//
构建 accept 属性值,用于限制文件选择对话框中可见的文件类型
const
acceptedFileTypes
=
computed
(()
=>
generateAcceptedFileTypes
(
supportedFileTypes
)
)
/
/ 表单数据
/
** 表单数据 */
const
modelData
=
computed
({
get
:
()
=>
props
.
modelValue
,
get
:
()
=>
{
return
props
.
modelValue
},
set
:
(
val
)
=>
emit
(
'update:modelValue'
,
val
)
})
// 知识库列表
interface
KnowledgeBase
{
id
:
number
name
:
string
/** 确保 list 属性存在 */
const
ensureListExists
=
()
=>
{
if
(
!
props
.
modelValue
.
list
)
{
emit
(
'update:modelValue'
,
{
...
props
.
modelValue
,
list
:
[]
})
}
}
const
knowledgeBaseList
=
ref
<
KnowledgeBase
[]
>
([])
// 表单校验规则
const
rules
=
{
name
:
[{
required
:
true
,
message
:
'请输入文档名称'
,
trigger
:
'blur'
}],
knowledgeBaseId
:
[{
required
:
true
,
message
:
'请选择知识库'
,
trigger
:
'change'
}],
documentType
:
[{
required
:
true
,
message
:
'请选择文档类型'
,
trigger
:
'change'
}],
content
:
[
{
required
:
true
,
message
:
'请输入文档内容'
,
trigger
:
'blur'
,
validator
:
(
rule
,
value
,
callback
)
=>
{
if
(
modelData
.
value
.
documentType
===
'text'
&&
!
value
)
{
callback
(
new
Error
(
'请输入文档内容'
))
}
else
{
callback
()
}
}
}
],
url
:
[
{
required
:
true
,
message
:
'请输入网页链接'
,
trigger
:
'blur'
,
validator
:
(
rule
,
value
,
callback
)
=>
{
if
(
modelData
.
value
.
documentType
===
'url'
&&
!
value
)
{
callback
(
new
Error
(
'请输入网页链接'
))
}
else
{
callback
()
}
}
}
],
file
:
[
{
required
:
true
,
message
:
'请上传文件'
,
trigger
:
'change'
,
validator
:
(
rule
,
value
,
callback
)
=>
{
if
([
'pdf'
,
'word'
].
includes
(
modelData
.
value
.
documentType
)
&&
!
modelData
.
value
.
file
)
{
callback
(
new
Error
(
'请上传文件'
))
}
else
{
callback
()
/** 是否所有文件都已上传完成 */
const
isAllUploaded
=
computed
(()
=>
{
return
modelData
.
value
.
list
&&
modelData
.
value
.
list
.
length
>
0
&&
uploadingCount
.
value
===
0
})
/**
* 上传前检查文件类型和大小
*
* @param file 待上传的文件
* @returns 是否允许上传
*/
const
beforeUpload
=
(
file
)
=>
{
// 1.1 检查文件扩展名
const
fileName
=
file
.
name
.
toLowerCase
()
const
fileExtension
=
fileName
.
substring
(
fileName
.
lastIndexOf
(
'.'
)
+
1
)
if
(
!
allowedExtensions
.
includes
(
fileExtension
))
{
message
.
error
(
'不支持的文件类型!'
)
return
false
}
// 1.2 检查文件大小
if
(
!
(
file
.
size
/
1024
/
1024
<
maxFileSize
))
{
message
.
error
(
`文件大小不能超过
${
maxFileSize
}
MB!`
)
return
false
}
// 2. 增加上传中的文件计数
uploadingCount
.
value
++
return
true
}
/**
* 文件上传成功处理
*
* @param response 上传响应
* @param file 上传的文件
*/
const
handleUploadSuccess
=
(
response
,
file
)
=>
{
if
(
response
&&
response
.
data
)
{
// 添加到文件列表
ensureListExists
()
emit
(
'update:modelValue'
,
{
...
props
.
modelValue
,
list
:
[
...
props
.
modelValue
.
list
,
{
name
:
file
.
name
,
url
:
response
.
data
}
}
}
]
]
})
}
else
{
message
.
error
(
`文件
${
file
.
name
}
上传失败`
)
}
// 减少上传中的文件计数
uploadingCount
.
value
=
Math
.
max
(
0
,
uploadingCount
.
value
-
1
)
}
// 文件上传处理
const
handleFileChange
=
(
file
)
=>
{
modelData
.
value
.
file
=
file
.
raw
/**
* 文件上传失败处理
*
* @param error 错误信息
* @param file 上传的文件
*/
const
handleUploadError
=
(
error
,
file
)
=>
{
message
.
error
(
`文件
${
file
.
name
}
上传失败:
${
error
}
`
)
// 减少上传中的文件计数
uploadingCount
.
value
=
Math
.
max
(
0
,
uploadingCount
.
value
-
1
)
}
// 下一步按钮处理
const
handleNextStep
=
()
=>
{
// 获取父组件的goToNextStep方法
const
parentEl
=
parent
||
getCurrentInstance
()?.
parent
if
(
parentEl
&&
typeof
parentEl
.
exposed
?.
goToNextStep
===
'function'
)
{
parentEl
.
exposed
.
goToNextStep
()
/**
* 文件变更处理
*
* @param file 变更的文件
*/
const
handleFileChange
=
(
file
)
=>
{
if
(
file
.
status
===
'success'
||
file
.
status
===
'fail'
)
{
uploadingCount
.
value
=
Math
.
max
(
0
,
uploadingCount
.
value
-
1
)
}
}
// 初始化数据
const
initData
=
async
()
=>
{
// 获取知识库列表
// knowledgeBaseList.value = await KnowledgeBaseApi.getKnowledgeBaseList()
// 模拟数据
knowledgeBaseList
.
value
=
[
{
id
:
1
,
name
:
'产品知识库'
},
{
id
:
2
,
name
:
'技术文档库'
},
{
id
:
3
,
name
:
'客户服务知识库'
}
]
/**
* 文件移除处理
*
* @param file 被移除的文件
*/
const
handleFileRemove
=
(
file
)
=>
{
if
(
file
.
status
===
'uploading'
)
{
uploadingCount
.
value
=
Math
.
max
(
0
,
uploadingCount
.
value
-
1
)
}
}
// 表单校验
const
validate
=
()
=>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
formRef
.
value
?.
validate
((
valid
)
=>
{
if
(
valid
)
{
resolve
(
true
)
}
else
{
reject
(
new
Error
(
'请完善表单信息'
))
}
})
/**
* 从列表中移除文件
*
* @param index 要移除的文件索引
*/
const
removeFile
=
(
index
:
number
)
=>
{
// 从列表中移除文件
const
newList
=
[...
props
.
modelValue
.
list
]
newList
.
splice
(
index
,
1
)
// 更新表单数据
emit
(
'update:modelValue'
,
{
...
props
.
modelValue
,
list
:
newList
})
}
// 对外暴露方法
defineExpose
({
validate
})
/** 下一步按钮处理 */
const
handleNextStep
=
()
=>
{
// 1.1 检查是否有文件上传
if
(
!
modelData
.
value
.
list
||
modelData
.
value
.
list
.
length
===
0
)
{
message
.
warning
(
'请上传至少一个文件'
)
return
}
// 1.2 检查是否有文件正在上传
if
(
uploadingCount
.
value
>
0
)
{
message
.
warning
(
'请等待所有文件上传完成'
)
return
}
// 2. 获取父组件的goToNextStep方法
const
parentEl
=
parent
||
getCurrentInstance
()?.
parent
if
(
parentEl
&&
typeof
parentEl
.
exposed
?.
goToNextStep
===
'function'
)
{
parentEl
.
exposed
.
goToNextStep
()
}
}
/
/ 初始化
/
** 初始化 */
onMounted
(()
=>
{
initData
()
ensureListExists
()
})
</
script
>
<
style
lang=
"scss"
scoped
>
.upload-demo
{
width
:
100%
;
}
</
style
>
<
style
lang=
"scss"
scoped
></
style
>
src/views/ai/knowledge/document/create/index.vue
View file @
7cd6a5d9
...
...
@@ -90,12 +90,7 @@ const steps = [{ title: '上传文档' }, { title: '文档分段' }, { title: '
// 表单数据
const
formData
=
ref
({
id
:
undefined
,
name
:
''
,
knowledgeBaseId
:
undefined
,
documentType
:
undefined
,
content
:
''
,
file
:
null
,
segments
:
[],
list
:
[],
// 用于存储上传的文件列表
status
:
0
// 0: 草稿, 1: 处理中, 2: 已完成
})
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment