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() {
...
@@ -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
)
{
export
function
firstUpperCase
(
str
:
string
)
{
...
...
src/views/ai/knowledge/document/create/UploadStep.vue
View file @
7cd6a5d9
<
template
>
<
template
>
<el-form
ref=
"formRef"
:model=
"modelData"
:rules=
"rules"
label-width=
"120px"
class=
"mt-20px"
>
<el-form
ref=
"formRef"
:model=
"modelData"
label-width=
"0"
class=
"mt-20px"
>
<el-form-item
label=
"文档名称"
prop=
"name"
class=
"mb-20px"
>
<el-form-item
class=
"mb-20px"
>
<el-input
v-model=
"modelData.name"
clearable
placeholder=
"请输入文档名称"
/>
<div
class=
"w-full"
>
</el-form-item>
<div
class=
"w-full border-2 border-[#dcdfe6] rounded-md text-center hover:border-[#409eff]"
>
<el-form-item
label=
"知识库"
prop=
"knowledgeBaseId"
class=
"mb-20px"
>
<el-upload
<el-select
ref=
"uploadRef"
class=
"!w-full"
class=
"upload-demo"
v-model=
"modelData.knowledgeBaseId"
drag
clearable
:action=
"uploadUrl"
placeholder=
"请选择知识库"
:auto-upload=
"true"
>
:on-success=
"handleUploadSuccess"
<el-option
:on-error=
"handleUploadError"
v-for=
"base in knowledgeBaseList"
:on-change=
"handleFileChange"
:key=
"base.id"
:on-remove=
"handleFileRemove"
:label=
"base.name"
:before-upload=
"beforeUpload"
:value=
"base.id"
:http-request=
"httpRequest"
/>
:file-list=
"fileList"
</el-select>
:multiple=
"true"
</el-form-item>
:show-file-list=
"false"
<el-form-item
label=
"文档类型"
prop=
"documentType"
class=
"mb-20px"
>
:accept=
"acceptedFileTypes"
<el-select
>
class=
"!w-full"
<div
class=
"flex flex-col items-center justify-center py-20px"
>
v-model=
"modelData.documentType"
<el-icon
class=
"text-[48px] text-[#c0c4cc] mb-10px"
><upload-filled
/></el-icon>
clearable
<div
class=
"el-upload__text text-[16px] text-[#606266]"
placeholder=
"请选择文档类型"
>
拖拽文件至此,或者
>
<em
class=
"text-[#409eff] not-italic cursor-pointer"
>
选择文件
</em></div
<el-option
label=
"PDF文档"
value=
"pdf"
/>
>
<el-option
label=
"Word文档"
value=
"word"
/>
<div
class=
"el-upload__tip mt-10px text-[#909399] text-[12px]"
>
<el-option
label=
"文本文件"
value=
"text"
/>
已支持
{{
supportedFileTypes
.
join
(
'、'
)
}}
,每个文件不超过
{{
maxFileSize
}}
MB。
<el-option
label=
"网页链接"
value=
"url"
/>
</div>
</el-select>
</div>
</el-form-item>
</el-upload>
<el-form-item
</div>
label=
"文档内容"
prop=
"content"
<div
class=
"mb-20px"
v-if=
"modelData.list && modelData.list.length > 0"
v-if=
"modelData.documentType === 'text'"
class=
"mt-15px grid grid-cols-1 gap-3"
>
>
<el-input
<div
v-model=
"modelData.content"
v-for=
"(file, index) in modelData.list"
type=
"textarea"
:key=
"index"
:rows=
"6"
class=
"flex justify-between items-center p-10px border-2 border-[#c0c4cc] rounded-md shadow-sm hover:border-[#409eff] transition-colors duration-300"
placeholder=
"请输入文档内容"
>
/>
<div
class=
"flex items-center"
>
</el-form-item>
<el-icon
class=
"mr-8px text-[#909399]"
><document
/></el-icon>
<el-form-item
<span
class=
"text-[14px] text-[#606266] break-all"
>
{{
file
.
name
}}
</span>
label=
"网页链接"
</div>
prop=
"url"
<el-button
type=
"danger"
link
@
click=
"removeFile(index)"
>
class=
"mb-20px"
<el-icon><delete
/></el-icon>
v-if=
"modelData.documentType === 'url'"
</el-button>
>
<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)'
}}
</div>
</div>
</
template
>
</
div
>
</
el-upload
>
</
div
>
</el-form-item>
</el-form-item>
<!-- 添加下一步按钮 -->
<!-- 添加下一步按钮 -->
<el-form-item>
<el-form-item>
<div
class=
"flex justify-end"
>
<div
class=
"flex justify-end w-full"
>
<el-button
type=
"primary"
@
click=
"handleNextStep"
>
下一步
</el-button>
<el-button
type=
"primary"
@
click=
"handleNextStep"
:disabled=
"!isAllUploaded"
>
下一步
</el-button>
</div>
</div>
</el-form-item>
</el-form-item>
</el-form>
</el-form>
</
template
>
</
template
>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
PropType
}
from
'vue'
import
{
PropType
,
ref
,
computed
,
inject
,
getCurrentInstance
,
onMounted
}
from
'vue'
import
{
UploadFilled
}
from
'@element-plus/icons-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
({
const
props
=
defineProps
({
modelValue
:
{
modelValue
:
{
...
@@ -98,128 +81,191 @@ const props = defineProps({
...
@@ -98,128 +81,191 @@ const props = defineProps({
const
emit
=
defineEmits
([
'update:modelValue'
])
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)
//
获取父组件实例
//
构建 accept 属性值,用于限制文件选择对话框中可见的文件类型
const
parent
=
inject
(
'parent'
,
null
)
const
acceptedFileTypes
=
computed
(()
=>
generateAcceptedFileTypes
(
supportedFileTypes
)
)
/
/ 表单数据
/
** 表单数据 */
const
modelData
=
computed
({
const
modelData
=
computed
({
get
:
()
=>
props
.
modelValue
,
get
:
()
=>
{
return
props
.
modelValue
},
set
:
(
val
)
=>
emit
(
'update:modelValue'
,
val
)
set
:
(
val
)
=>
emit
(
'update:modelValue'
,
val
)
})
})
// 知识库列表
/** 确保 list 属性存在 */
interface
KnowledgeBase
{
const
ensureListExists
=
()
=>
{
id
:
number
if
(
!
props
.
modelValue
.
list
)
{
name
:
string
emit
(
'update:modelValue'
,
{
...
props
.
modelValue
,
list
:
[]
})
}
}
}
const
knowledgeBaseList
=
ref
<
KnowledgeBase
[]
>
([])
/** 是否所有文件都已上传完成 */
const
isAllUploaded
=
computed
(()
=>
{
// 表单校验规则
return
modelData
.
value
.
list
&&
modelData
.
value
.
list
.
length
>
0
&&
uploadingCount
.
value
===
0
const
rules
=
{
})
name
:
[{
required
:
true
,
message
:
'请输入文档名称'
,
trigger
:
'blur'
}],
knowledgeBaseId
:
[{
required
:
true
,
message
:
'请选择知识库'
,
trigger
:
'change'
}],
/**
documentType
:
[{
required
:
true
,
message
:
'请选择文档类型'
,
trigger
:
'change'
}],
* 上传前检查文件类型和大小
content
:
[
*
{
* @param file 待上传的文件
required
:
true
,
* @returns 是否允许上传
message
:
'请输入文档内容'
,
*/
trigger
:
'blur'
,
const
beforeUpload
=
(
file
)
=>
{
validator
:
(
rule
,
value
,
callback
)
=>
{
// 1.1 检查文件扩展名
if
(
modelData
.
value
.
documentType
===
'text'
&&
!
value
)
{
const
fileName
=
file
.
name
.
toLowerCase
()
callback
(
new
Error
(
'请输入文档内容'
))
const
fileExtension
=
fileName
.
substring
(
fileName
.
lastIndexOf
(
'.'
)
+
1
)
}
else
{
if
(
!
allowedExtensions
.
includes
(
fileExtension
))
{
callback
()
message
.
error
(
'不支持的文件类型!'
)
}
return
false
}
}
}
// 1.2 检查文件大小
],
if
(
!
(
file
.
size
/
1024
/
1024
<
maxFileSize
))
{
url
:
[
message
.
error
(
`文件大小不能超过
${
maxFileSize
}
MB!`
)
{
return
false
required
:
true
,
}
message
:
'请输入网页链接'
,
trigger
:
'blur'
,
// 2. 增加上传中的文件计数
validator
:
(
rule
,
value
,
callback
)
=>
{
uploadingCount
.
value
++
if
(
modelData
.
value
.
documentType
===
'url'
&&
!
value
)
{
return
true
callback
(
new
Error
(
'请输入网页链接'
))
}
}
else
{
callback
()
/**
}
* 文件上传成功处理
}
*
}
* @param response 上传响应
],
* @param file 上传的文件
file
:
[
*/
{
const
handleUploadSuccess
=
(
response
,
file
)
=>
{
required
:
true
,
if
(
response
&&
response
.
data
)
{
message
:
'请上传文件'
,
// 添加到文件列表
trigger
:
'change'
,
ensureListExists
()
validator
:
(
rule
,
value
,
callback
)
=>
{
emit
(
'update:modelValue'
,
{
if
([
'pdf'
,
'word'
].
includes
(
modelData
.
value
.
documentType
)
&&
!
modelData
.
value
.
file
)
{
...
props
.
modelValue
,
callback
(
new
Error
(
'请上传文件'
))
list
:
[
}
else
{
...
props
.
modelValue
.
list
,
callback
()
{
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
* @param file 变更的文件
if
(
parentEl
&&
typeof
parentEl
.
exposed
?.
goToNextStep
===
'function'
)
{
*/
parentEl
.
exposed
.
goToNextStep
()
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()
* @param file 被移除的文件
*/
// 模拟数据
const
handleFileRemove
=
(
file
)
=>
{
knowledgeBaseList
.
value
=
[
if
(
file
.
status
===
'uploading'
)
{
{
id
:
1
,
name
:
'产品知识库'
},
uploadingCount
.
value
=
Math
.
max
(
0
,
uploadingCount
.
value
-
1
)
{
id
:
2
,
name
:
'技术文档库'
},
}
{
id
:
3
,
name
:
'客户服务知识库'
}
]
}
}
// 表单校验
/**
const
validate
=
()
=>
{
* 从列表中移除文件
return
new
Promise
((
resolve
,
reject
)
=>
{
*
formRef
.
value
?.
validate
((
valid
)
=>
{
* @param index 要移除的文件索引
if
(
valid
)
{
*/
resolve
(
true
)
const
removeFile
=
(
index
:
number
)
=>
{
}
else
{
// 从列表中移除文件
reject
(
new
Error
(
'请完善表单信息'
))
const
newList
=
[...
props
.
modelValue
.
list
]
}
newList
.
splice
(
index
,
1
)
})
// 更新表单数据
emit
(
'update:modelValue'
,
{
...
props
.
modelValue
,
list
:
newList
})
})
}
}
// 对外暴露方法
/** 下一步按钮处理 */
defineExpose
({
const
handleNextStep
=
()
=>
{
validate
// 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
(()
=>
{
onMounted
(()
=>
{
initData
()
ensureListExists
()
})
})
</
script
>
</
script
>
<
style
lang=
"scss"
scoped
>
<
style
lang=
"scss"
scoped
></
style
>
.upload-demo
{
width
:
100%
;
}
</
style
>
src/views/ai/knowledge/document/create/index.vue
View file @
7cd6a5d9
...
@@ -90,12 +90,7 @@ const steps = [{ title: '上传文档' }, { title: '文档分段' }, { title: '
...
@@ -90,12 +90,7 @@ const steps = [{ title: '上传文档' }, { title: '文档分段' }, { title: '
// 表单数据
// 表单数据
const
formData
=
ref
({
const
formData
=
ref
({
id
:
undefined
,
id
:
undefined
,
name
:
''
,
list
:
[],
// 用于存储上传的文件列表
knowledgeBaseId
:
undefined
,
documentType
:
undefined
,
content
:
''
,
file
:
null
,
segments
:
[],
status
:
0
// 0: 草稿, 1: 处理中, 2: 已完成
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