You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
830 lines
25 KiB
830 lines
25 KiB
<template> |
|
<div class="app-container"> |
|
<el-row :gutter="20"> |
|
<!-- 左侧分类菜单 --> |
|
<el-col :span="6"> |
|
<el-form @submit.native.prevent> |
|
<el-form-item prop="name"> |
|
<el-input |
|
v-model="queryRightParams.title" |
|
placeholder="输入内容过滤" |
|
clearable |
|
size="small" |
|
prefix-icon="el-icon-search" |
|
@keyup.enter.native="handleRightQuery" |
|
> |
|
<template slot="append"> |
|
<el-button |
|
slot="append" |
|
icon="el-icon-refresh-right" |
|
@click="resetRightQuery" |
|
/> |
|
</template> |
|
</el-input> |
|
</el-form-item> |
|
</el-form> |
|
<el-menu :default-active="defaultActive" @select="handleSelect"> |
|
<!-- 自定义标签 --> |
|
<el-menu-item |
|
v-for="(item, index) in cateList" |
|
:key="item.id" |
|
:index="item.id" |
|
class="custom-item" |
|
> |
|
<div class="left"> |
|
<i :class="getBuiltInIcon(index) || 'el-icon-collection-tag'" /> |
|
<span>{{ item.name }}</span> |
|
</div> |
|
<div class="right" v-if="item.type == 'user'"> |
|
<el-button |
|
type="text" |
|
icon="el-icon-edit" |
|
@click.stop="handleEditTag(item)" |
|
/> |
|
<el-button |
|
type="text" |
|
icon="el-icon-delete" |
|
@click.stop="handleLeftDelete(item)" |
|
/> |
|
</div> |
|
</el-menu-item> |
|
<el-menu-item> |
|
<div class="left" @click.stop="handleAddTag"> |
|
<i class="el-icon-edit-outline" /> |
|
<span>自定义标签</span> |
|
</div> |
|
</el-menu-item> |
|
</el-menu> |
|
</el-col> |
|
|
|
<!-- 右侧资源列表 --> |
|
<el-col :span="18"> |
|
<el-card shadow="never"> |
|
<div slot="header" class="clearfix"> |
|
<span>{{ queryRightParams.name }}</span> |
|
<el-button |
|
style="float: right; padding: 3px 0" |
|
type="text" |
|
icon="el-icon-upload2" |
|
@click="openUploadTypeDialog" |
|
v-if=" |
|
(queryRightParams.cate_id == 0 && |
|
queryRightParams.is_mine == 1) || |
|
(queryRightParams.cate_id != 0 && |
|
queryRightParams.is_mine === 0) |
|
" |
|
> |
|
上传 |
|
</el-button> |
|
</div> |
|
<!-- 资源列表 --> |
|
<el-table |
|
:data="list" |
|
height="calc(100vh - 264px)" |
|
:show-header="false" |
|
> |
|
<el-table-column |
|
label="内容" |
|
prop="thumbnail_path" |
|
align="center" |
|
width="250" |
|
> |
|
<template slot-scope="scope"> |
|
<el-image |
|
class="resource-thumb" |
|
fit="contain" |
|
:src="getFullFilePath(scope.row.thumbnail_path)" |
|
@click="handlePlay(scope.row)" |
|
> |
|
<div |
|
slot="error" |
|
class="image-slot" |
|
@click="handlePlay(scope.row)" |
|
> |
|
<i :class="getFileIconClass(scope.row.file_type)" /> |
|
</div> |
|
</el-image> |
|
</template> |
|
</el-table-column> |
|
<el-table-column |
|
label="标题" |
|
prop="title" |
|
:show-overflow-tooltip="true" |
|
/> |
|
<el-table-column |
|
label="创建时间" |
|
align="center" |
|
prop="create_time" |
|
width="200" |
|
> |
|
<template slot-scope="scope"> |
|
<span>{{ parseTime(scope.row.create_time) }}上传</span> |
|
</template> |
|
</el-table-column> |
|
<el-table-column |
|
label="文件大小" |
|
align="center" |
|
prop="file_size" |
|
width="120" |
|
> |
|
<template slot-scope="scope"> |
|
<span>{{ formatfile_size(scope.row.file_size) }}</span> |
|
</template> |
|
</el-table-column> |
|
<el-table-column |
|
label="操作" |
|
align="center" |
|
width="240" |
|
class-name="small-padding fixed-width" |
|
> |
|
<template slot-scope="scope"> |
|
<el-button |
|
type="text" |
|
icon="el-icon-share" |
|
@click="handleShare(scope.row)" |
|
> |
|
分享 |
|
</el-button> |
|
<el-button |
|
v-if="scope.row.is_mine == 1" |
|
type="text" |
|
icon="el-icon-edit" |
|
@click="handleUpdateFileName(scope.row)" |
|
> |
|
修改 |
|
</el-button> |
|
<el-button |
|
v-if="scope.row.is_mine == 1" |
|
type="text" |
|
icon="el-icon-delete" |
|
@click="handleRightDelete(scope.row)" |
|
> |
|
删除 |
|
</el-button> |
|
</template> |
|
</el-table-column> |
|
</el-table> |
|
<!-- 分页 --> |
|
<pagination |
|
v-show="queryRightParams.total > 0" |
|
:total="queryRightParams.total" |
|
:page.sync="queryRightParams.page" |
|
:limit.sync="queryRightParams.pageSize" |
|
@pagination="getKnowledgeList" |
|
/> |
|
</el-card> |
|
</el-col> |
|
</el-row> |
|
|
|
<!-- 自定义标签弹窗 --> |
|
<el-dialog |
|
title="自定义标签名称" |
|
:visible.sync="tagDialogVisible" |
|
width="400px" |
|
destroy-on-close |
|
> |
|
<el-form |
|
:model="tagForm" |
|
:rules="tagRules" |
|
ref="tagFormRef" |
|
label-width="80px" |
|
> |
|
<el-form-item label="标签名称" prop="name"> |
|
<el-input |
|
v-model="tagForm.name" |
|
placeholder="请输入最多8个字符" |
|
maxlength="8" |
|
show-word-limit |
|
/> |
|
</el-form-item> |
|
</el-form> |
|
<div slot="footer" class="dialog-footer"> |
|
<el-button type="primary" @click="submitTagForm">确认</el-button> |
|
<el-button @click="cancelTagForm">取消</el-button> |
|
</div> |
|
</el-dialog> |
|
|
|
<!-- 修改文件名弹窗 --> |
|
<el-dialog |
|
title="修改" |
|
:visible.sync="fileNameDialogVisible" |
|
width="400px" |
|
destroy-on-close |
|
> |
|
<el-form |
|
:model="fileNameForm" |
|
:rules="fileNameRules" |
|
ref="fileNameFormRef" |
|
label-width="80px" |
|
> |
|
<el-form-item label="标题" prop="title"> |
|
<el-input |
|
v-model="fileNameForm.title" |
|
placeholder="请输入标题" |
|
show-word-limit |
|
maxlength="8" |
|
/> |
|
</el-form-item> |
|
</el-form> |
|
<div slot="footer" class="dialog-footer"> |
|
<el-button type="primary" @click="submitFileNameForm">确认</el-button> |
|
<el-button @click="cancelFileNameForm">取消</el-button> |
|
</div> |
|
</el-dialog> |
|
|
|
<!-- 选择上传类型弹窗 --> |
|
<el-dialog |
|
title="请选择上传文件类型" |
|
:visible.sync="uploadTypeDialogVisible" |
|
width="400px" |
|
:show-footer="false" |
|
> |
|
<div class="upload-type-box"> |
|
<div class="type-item" @click="selectFileType('video')"> |
|
<i class="el-icon-video-play" /> |
|
<span>视频文件(MP4)</span> |
|
</div> |
|
<div class="type-item" @click="selectFileType('pdf')"> |
|
<i class="el-icon-document" /> |
|
<span>PDF 文件</span> |
|
</div> |
|
</div> |
|
</el-dialog> |
|
|
|
<!-- 隐藏的文件输入(用于触发上传) --> |
|
<input |
|
ref="fileInput" |
|
type="file" |
|
:accept="uploadAccept" |
|
class="hidden-upload-input" |
|
@change="handleFileSelect" |
|
/> |
|
|
|
<!-- 上传进度弹窗 --> |
|
<el-dialog |
|
title="文件上传中" |
|
:visible.sync="uploadProgressDialogVisible" |
|
width="400px" |
|
:close-on-click-modal="false" |
|
:show-footer="false" |
|
> |
|
<div class="progress-box"> |
|
<el-progress :percentage="uploadPercent" /> |
|
<p style="margin-top: 15px">资源上传中,请稍候...</p> |
|
</div> |
|
</el-dialog> |
|
|
|
<!-- 分享弹窗 - 选择联系人 --> |
|
<CreateGroupDialog |
|
:visible.sync="shareDialogVisible" |
|
title="选择人员" |
|
:min-select-count="1" |
|
@confirm="handleShareToContacts" |
|
/> |
|
</div> |
|
</template> |
|
|
|
<script> |
|
import { |
|
postKnowledgeCateList, |
|
postKnowledgeCateCreate, |
|
postKnowledgeCateEdit, |
|
postKnowledgeCateDelete, |
|
postKnowledgeList, |
|
postKnowledgeCreate, |
|
postKnowledgeDetail, |
|
postKnowledgePlay, |
|
postKnowledgeEdit, |
|
postKnowledgeDelete, |
|
postMessagePushToUser, |
|
} from "@/api/knowledge"; |
|
import { mapGetters } from "vuex"; |
|
import CreateGroupDialog from "@/views/message/components/CreateGroupDialog"; |
|
import { |
|
uploadFileWithThumbnail, |
|
parseMinioFilePath, |
|
uploadFile, |
|
} from "@/utils/requestMinio"; |
|
import { MessageType } from "@/utils/constants"; |
|
|
|
export default { |
|
name: "Knowledge", |
|
components: { |
|
CreateGroupDialog, |
|
}, |
|
computed: { |
|
...mapGetters(["config"]), |
|
}, |
|
data() { |
|
return { |
|
// 知识库左侧-默认选中分类 |
|
defaultActive: "2", |
|
// 知识库左侧-系统分类 |
|
systemCateList: [], |
|
// 知识库左侧-用户分类 |
|
userCateList: [], |
|
cateList: [], |
|
// 知识库左侧-自定义标签 |
|
tagDialogVisible: false, |
|
// 知识库左侧-自定义标签-表单数据 |
|
tagForm: { id: null, name: "" }, |
|
// 知识库左侧-自定义标签-表单验证规则 |
|
tagRules: { |
|
name: [ |
|
{ required: true, message: "标签名称不能为空", trigger: "blur" }, |
|
{ max: 8, message: "最多8个字符", trigger: "blur" }, |
|
], |
|
}, |
|
// 知识库右侧-查询参数 |
|
queryRightParams: { |
|
title: "", |
|
cate_id: 0, |
|
is_mine: "-1", |
|
page: 1, |
|
pageSize: 10, |
|
total: 0, |
|
}, |
|
// 知识库右侧-列表 |
|
list: [], |
|
// 知识库右侧-文件名弹窗 |
|
fileNameDialogVisible: false, |
|
// 知识库右侧-文件名弹窗-表单数据 |
|
fileNameForm: { userId: undefined, title: "" }, |
|
// 知识库右侧-文件名弹窗-表单验证规则 |
|
fileNameRules: { |
|
title: [{ required: true, message: "标题不能为空", trigger: "blur" }], |
|
}, |
|
|
|
loading: true, |
|
userList: [], |
|
// ================== 上传相关 ================== |
|
uploadTypeDialogVisible: false, // 类型选择弹窗 |
|
currentFileType: "", // 当前选择的文件类型 video/pdf |
|
uploadAccept: "", |
|
uploadProgressDialogVisible: false, |
|
uploadPercent: 0, |
|
currentUploadFile: null, // 当前上传的文件对象 |
|
// ================== 分享相关 ================== |
|
shareDialogVisible: false, // 分享弹窗 |
|
shareItem: null, // 当前分享的文件 |
|
}; |
|
}, |
|
created() { |
|
this.getKnowledgeCateList(); |
|
}, |
|
methods: { |
|
// 获取完整文件路径 |
|
// index.vue - methods |
|
getFullFilePath(relativePath) { |
|
if (!relativePath) return ""; |
|
|
|
// 1. 如果是完整 URL,直接返回 |
|
if ( |
|
relativePath.startsWith("http://") || |
|
relativePath.startsWith("https://") |
|
) { |
|
return relativePath; |
|
} |
|
|
|
// 2. 解析服务器返回的 bucket/object 格式路径(C++ 端逻辑) |
|
const { bucket, object } = parseMinioFilePath(relativePath); |
|
if (!bucket || !object) { |
|
console.warn("无效的 MinIO 路径格式:", relativePath); |
|
return relativePath; |
|
} |
|
|
|
// 3. 获取 MinIO 配置(从 Vuex 中获取) |
|
const { netConfig } = this.$store.state.user; |
|
const useSSL = |
|
netConfig.MINIO_SECURE === "1" || netConfig.MINIO_SECURE === true; |
|
const protocol = useSSL ? "https" : "http"; |
|
const endpoint = netConfig.MINIO_ENDPOINT || "47.92.6.51:9100"; |
|
|
|
// 4. 拼接完整 URL(与 C++ 端公式一致:protocol://endpoint/bucket/object) |
|
return `${protocol}://${endpoint}/${bucket}/${object}`; |
|
}, |
|
// 知识库左侧-图标 |
|
getBuiltInIcon(idx) { |
|
return ["el-icon-coin", "el-icon-video-camera", "el-icon-user"][idx]; |
|
}, |
|
// 知识库左侧-分类列表 |
|
getKnowledgeCateList() { |
|
postKnowledgeCateList().then((res) => { |
|
this.systemCateList = res.data.system_cate_list.map((item) => ({ |
|
...item, |
|
id: item.id.toString(), |
|
is_mine: item.id.toString(), |
|
type: "system", |
|
})); |
|
this.userCateList = res.data.user_cate_list.map((item) => ({ |
|
...item, |
|
id: item.id.toString(), |
|
is_mine: 0, |
|
cate_id: item.id.toString(), |
|
type: "user", |
|
})); |
|
this.cateList = [...this.systemCateList, ...this.userCateList]; |
|
console.log("知识库左侧分类列表", res.data); |
|
this.handleSelect(this.cateList[0].id); |
|
}); |
|
}, |
|
// 知识库左侧-自定义标签-新增 |
|
handleAddTag() { |
|
this.tagForm = { name: "" }; |
|
this.tagDialogVisible = true; |
|
}, |
|
// 知识库左侧-自定义标签-编辑 |
|
handleEditTag(t) { |
|
this.tagForm = { ...t, cate_id: t.id }; |
|
this.tagDialogVisible = true; |
|
}, |
|
// 知识库左侧-自定义标签-提交 |
|
submitTagForm() { |
|
this.$refs["tagFormRef"].validate((valid) => { |
|
if (valid) { |
|
if (this.tagForm.id) { |
|
postKnowledgeCateEdit(this.tagForm).then(() => { |
|
this.$modal.msgSuccess("修改成功"); |
|
this.tagDialogVisible = false; |
|
this.getKnowledgeCateList(); |
|
}); |
|
} else { |
|
postKnowledgeCateCreate(this.tagForm).then(() => { |
|
this.$modal.msgSuccess("新增成功"); |
|
this.tagDialogVisible = false; |
|
this.getKnowledgeCateList(); |
|
}); |
|
} |
|
} |
|
}); |
|
}, |
|
// 知识库左侧-自定义标签-取消 |
|
cancelTagForm() { |
|
this.tagDialogVisible = false; |
|
}, |
|
// 知识库右侧-查询 |
|
handleSelect(key) { |
|
this.defaultActive = key; |
|
// 获取选中的菜单项名称s |
|
const selectedItem = this.findMenuItem(key); |
|
this.queryRightParams.is_mine = selectedItem.is_mine; |
|
this.queryRightParams.cate_id = selectedItem.cate_id; |
|
if (selectedItem) { |
|
this.queryRightParams.name = selectedItem.name; |
|
} |
|
this.handleRightQuery(); |
|
}, |
|
// 删除资源 |
|
handleLeftDelete(row) { |
|
this.$modal |
|
.confirm( |
|
`是否删除该自定义标签?若删除,该标签下资源将迁移至[个人收藏录像]标签下。` |
|
) |
|
.then(function () { |
|
return postKnowledgeCateDelete({ cate_id: row.id }); |
|
}) |
|
.then(() => { |
|
this.getKnowledgeCateList(); |
|
this.$modal.msgSuccess("删除成功"); |
|
}) |
|
.catch(() => {}); |
|
}, |
|
// 查找菜单项 |
|
findMenuItem(key) { |
|
// 先在系统分类中查找 |
|
const item = this.cateList.find((item) => item.id === key); |
|
if (item) return item; |
|
return null; |
|
}, |
|
// 知识库右侧-列表 |
|
handleRightQuery() { |
|
this.queryRightParams.page = 1; |
|
this.getKnowledgeList(); |
|
}, |
|
// 知识库右侧-重置查询条件 |
|
resetRightQuery() { |
|
this.resetForm("queryRightForm"); |
|
this.queryRightParams.title = null; |
|
this.handleRightQuery(); |
|
}, |
|
// 知识库右侧-文件图标 |
|
getFileIconClass(fileName) { |
|
if (!fileName) return "el-icon-document"; |
|
const suffix = fileName.split(".").pop().toLowerCase(); |
|
const iconMap = { |
|
txt: "el-icon-tickets", |
|
pdf: "el-icon-document-copy", |
|
mp3: "el-icon-headset", |
|
wav: "el-icon-headset", |
|
avi: "el-icon-video-camera", |
|
mp4: "el-icon-video-camera", |
|
wmv: "el-icon-video-camera", |
|
mov: "el-icon-video-camera", |
|
zip: "el-icon-folder-opened", |
|
rar: "el-icon-folder-opened", |
|
ppt: "el-icon-data-board", |
|
pps: "el-icon-data-board", |
|
xls: "el-icon-s-grid", |
|
xlsx: "el-icon-s-grid", |
|
doc: "el-icon-document", |
|
docx: "el-icon-document", |
|
jpg: "el-icon-picture", |
|
jpeg: "el-icon-picture", |
|
jpe: "el-icon-picture", |
|
bmp: "el-icon-picture", |
|
gif: "el-icon-picture", |
|
png: "el-icon-picture", |
|
tif: "el-icon-picture", |
|
tiff: "el-icon-picture", |
|
}; |
|
return iconMap[suffix] || "el-icon-document"; |
|
}, |
|
// 知识库右侧-格式化文件大小 |
|
formatfile_size(bytes) { |
|
if (!bytes) return "0B"; |
|
const u = ["B", "KB", "MB", "GB"]; |
|
let s = bytes, |
|
i = 0; |
|
while (s >= 1024 && i < 3) { |
|
s /= 1024; |
|
i++; |
|
} |
|
return s.toFixed(2) + u[i]; |
|
}, |
|
// 知识库右侧-列表 |
|
getKnowledgeList() { |
|
this.list = []; |
|
postKnowledgeList(this.queryRightParams).then((res) => { |
|
this.list = res.data.list; |
|
this.queryRightParams.total = Number(res.data.total); |
|
this.queryRightParams.is_mine = res.data.is_mine; |
|
console.log("知识库右侧-列表", this.queryRightParams); |
|
}); |
|
}, |
|
// 知识库右侧-预览 |
|
handlePlay(row) { |
|
postKnowledgePlay({ knowledge_id: row.id }).then((res) => { |
|
window.open(this.getFullFilePath(row.file_path), "_blank"); |
|
}); |
|
}, |
|
handleUpdateFileName(row) { |
|
this.fileNameForm = { title: row.title, knowledge_id: row.id }; |
|
this.fileNameDialogVisible = true; |
|
}, |
|
submitFileNameForm() { |
|
this.$refs.fileNameFormRef.validate((valid) => { |
|
if (valid) { |
|
postKnowledgeEdit(this.fileNameForm).then(() => { |
|
this.$modal.msgSuccess("修改成功"); |
|
this.fileNameDialogVisible = false; |
|
this.getKnowledgeList(); |
|
}); |
|
} |
|
}); |
|
}, |
|
cancelFileNameForm() { |
|
this.fileNameDialogVisible = false; |
|
}, |
|
// 知识库右侧-删除 |
|
handleRightDelete(row) { |
|
this.$modal |
|
.confirm("确认要删除此资源?") |
|
.then(function () { |
|
return postKnowledgeDelete({ knowledge_id: row.id }); |
|
}) |
|
.then(() => { |
|
this.getKnowledgeList(); |
|
this.$modal.msgSuccess("删除成功"); |
|
}) |
|
.catch(() => {}); |
|
}, |
|
|
|
handleShare(row) { |
|
this.shareItem = row; |
|
this.shareDialogVisible = true; |
|
}, |
|
handleShareToContacts(selectedMembers) { |
|
if (selectedMembers && selectedMembers.length > 0) { |
|
const { id, netConfig } = this.$store.state.user; |
|
const currentUserId = id; |
|
const minioEndpoint = |
|
netConfig?.MINIO_ENDPOINT_HTTPS?.trim() || "http://47.92.6.51:9100/"; |
|
selectedMembers.forEach((shareTarget) => { |
|
const payloadContent = { |
|
file_name: this.shareItem?.title || "", |
|
file_path: this.shareItem?.file_path || "", |
|
file_size: this.shareItem?.file_size || "", |
|
file_time: this.shareItem?.create_time || "", |
|
knowledge_id: this.shareItem?.id || 0, |
|
thumbnail_path: this.shareItem?.thumbnail_path || "", |
|
}; |
|
const messageData = { |
|
client_id: `utalk-client-${currentUserId}`, |
|
message: { |
|
at_users: [], |
|
message_id: 0, |
|
payload: { |
|
content: JSON.stringify(payloadContent), |
|
file_duration: 0, |
|
file_ico: "", |
|
file_name: this.shareItem?.title || "", |
|
file_path: this.shareItem?.file_path || "", |
|
file_size: this.shareItem?.file_size || "", |
|
file_type: this.shareItem?.file_type || "", |
|
}, |
|
scene: 1, |
|
source_id: currentUserId, |
|
target_id: shareTarget.id, |
|
timestamp: 0, |
|
type: MessageType.KNOWLEDGE_SHAREs, |
|
}, |
|
target_id: shareTarget.id, |
|
topic: "/user/", |
|
}; |
|
postMessagePushToUser(messageData).then((res) => { |
|
this.$modal.msgSuccess(`已分享给 ${shareTarget.name}`); |
|
}); |
|
}); |
|
|
|
this.shareDialogVisible = false; |
|
this.shareItem = null; |
|
} |
|
}, |
|
// ====================== 上传核心逻辑 ====================== |
|
// 打开类型选择 |
|
openUploadTypeDialog() { |
|
this.uploadTypeDialogVisible = true; |
|
}, |
|
// 选择文件类型 |
|
selectFileType(type) { |
|
this.uploadTypeDialogVisible = false; |
|
this.currentFileType = type; |
|
this.uploadAccept = type === "video" ? "video/mp4" : "application/pdf"; |
|
// 触发文件选择 |
|
this.$nextTick(() => { |
|
this.$refs.fileInput.click(); |
|
}); |
|
}, |
|
// 文件选择处理 |
|
handleFileSelect(event) { |
|
const file = event.target.files[0]; |
|
if (!file) return; |
|
|
|
this.currentUploadFile = file; |
|
this.uploadProgressDialogVisible = true; |
|
this.uploadPercent = 0; |
|
|
|
// 执行 MinIO 上传 |
|
this.uploadToMinio(file); |
|
|
|
// 清空文件输入 |
|
event.target.value = ""; |
|
}, |
|
// 上传到 MinIO(支持 PDF 缩略图生成) |
|
async uploadToMinio(file) { |
|
try { |
|
// 确保 MinIO 客户端已初始化 |
|
await this.ensureMinioInitialized(); |
|
|
|
// 根据 is_mine 选择存储桶 |
|
const isMine = this.queryRightParams.is_mine == "0"; |
|
const bucket = this.config?.MINIO_BUCKET_KNOWLEDGE_PERSONAL; |
|
// 生成文件路径 |
|
const timestamp = Date.now(); |
|
const fileName = file.name; |
|
const objectName = `${this.currentFileType}/${this.queryRightParams.cate_id}/${timestamp}/${fileName}`; |
|
console.log(`Uploading to bucket: ${bucket}, object: ${objectName}`); |
|
|
|
// 使用统一的上传方法,自动判断文件类型并生成缩略图(PDF 和视频) |
|
const result = await uploadFileWithThumbnail( |
|
bucket, |
|
objectName, |
|
file, |
|
{}, |
|
(percent) => { |
|
this.uploadPercent = percent; |
|
} |
|
); |
|
|
|
console.log("MinIO 上传成功:", result); |
|
|
|
// 上传成功后调用业务接口入库(包含缩略图路径) |
|
await this.saveKnowledgeToDB( |
|
result.objectName, |
|
file, |
|
result.thumbnailPath, |
|
bucket |
|
); |
|
|
|
// 完成上传 |
|
this.uploadPercent = 100; |
|
setTimeout(() => { |
|
this.uploadProgressDialogVisible = false; |
|
this.$modal.msgSuccess("上传成功"); |
|
this.getKnowledgeList(); |
|
}, 500); |
|
} catch (error) { |
|
console.error("上传失败:", error); |
|
this.uploadProgressDialogVisible = false; |
|
this.$modal.msgError("上传失败: " + error.message); |
|
} |
|
}, |
|
// 确保 MinIO 客户端已初始化 |
|
async ensureMinioInitialized() { |
|
const config = this.$store.getters.config; |
|
if (!config || !config.MINIO_ENDPOINT) { |
|
console.log("MinIO config not loaded, fetching..."); |
|
await this.$store.dispatch("GetNetConfig"); |
|
} |
|
}, |
|
// 保存知识库记录到业务数据库 |
|
async saveKnowledgeToDB(filePath, file, thumbnailPath = "", bucket = "") { |
|
console.log("saveKnowledgeToDB:", filePath, file, thumbnailPath, bucket); |
|
|
|
// 构建完整的文件路径(bucket/object 格式) |
|
const fullFilePath = bucket ? `${bucket}/${filePath}` : filePath; |
|
|
|
// 构建缩略图路径 |
|
const fullThumbnailPath = thumbnailPath |
|
? `${bucket}/${thumbnailPath}` |
|
: ""; |
|
console.log("fullThumbnailPath:", fullThumbnailPath); |
|
const data = { |
|
cate_id: this.queryRightParams.cate_id, |
|
file_name: file.name, |
|
file_path: fullFilePath, |
|
file_size: file.size, |
|
file_type: this.currentFileType, |
|
kid: 21, |
|
thumbnail_path: |
|
fullThumbnailPath || |
|
"personal-test/video/688/1780036088/1633500241136.png", |
|
title: file.name.replace(/\.[^/.]+$/, ""), // 去除扩展名作为标题 |
|
true_file_size: file.size, |
|
}; |
|
await postKnowledgeCreate(data); |
|
console.log("知识库记录保存成功"); |
|
}, |
|
}, |
|
}; |
|
</script> |
|
|
|
<style lang="scss" scoped> |
|
// 自定义项 |
|
.custom-item { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
.left { |
|
display: flex; |
|
align-items: center; |
|
gap: 8px; |
|
} |
|
} |
|
// 资源缩略图 |
|
.resource-thumb { |
|
width: 230px; |
|
height: 80px; |
|
border-radius: 4px; |
|
cursor: pointer; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
font-size: 20px; |
|
} |
|
|
|
.hidden-upload-input { |
|
display: none; |
|
} |
|
|
|
.upload-type-box { |
|
display: flex; |
|
justify-content: space-around; |
|
padding: 20px 0; |
|
.type-item { |
|
width: 120px; |
|
height: 120px; |
|
border: 1px dashed #ccc; |
|
border-radius: 8px; |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
justify-content: center; |
|
cursor: pointer; |
|
i { |
|
font-size: 40px; |
|
color: #409eff; |
|
margin-bottom: 10px; |
|
} |
|
&:hover { |
|
border-color: #409eff; |
|
background: #f5f7fa; |
|
} |
|
} |
|
} |
|
|
|
.progress-box { |
|
padding: 20px; |
|
text-align: center; |
|
} |
|
</style> |