|
|
|
|
<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="!(cate_id == 0 && (is_mine == 0 || is_mine == 2))"
|
|
|
|
|
>
|
|
|
|
|
上传
|
|
|
|
|
</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
|
|
|
|
|
ref="createGroupDialogRef"
|
|
|
|
|
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", "userInfo"]),
|
|
|
|
|
},
|
|
|
|
|
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" },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
// 知识库右侧-查询参数
|
|
|
|
|
cate_id: null,
|
|
|
|
|
is_mine: null,
|
|
|
|
|
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.cate_id = res.data.cate_id;
|
|
|
|
|
this.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.$refs.createGroupDialogRef.show();
|
|
|
|
|
},
|
|
|
|
|
handleShareToContacts(selectedMembers) {
|
|
|
|
|
console.log("选中的成员:", selectedMembers, this.userInfo);
|
|
|
|
|
if (selectedMembers && selectedMembers.length > 0) {
|
|
|
|
|
postMessagePushToUser({
|
|
|
|
|
client_id: "utalk-client-" + this.userInfo.id,
|
|
|
|
|
message: {
|
|
|
|
|
at_users: [],
|
|
|
|
|
message_id: 0,
|
|
|
|
|
payload: {
|
|
|
|
|
content: JSON.stringify({
|
|
|
|
|
file_name: this.shareItem.file_name,
|
|
|
|
|
file_path: this.shareItem.file_path,
|
|
|
|
|
file_size: this.shareItem.file_size,
|
|
|
|
|
knowledge_id: this.shareItem.id,
|
|
|
|
|
thumbnail_path: this.shareItem.thumbnail_path,
|
|
|
|
|
file_time: this.shareItem.file_time,
|
|
|
|
|
}),
|
|
|
|
|
file_duration: 0,
|
|
|
|
|
file_ico: "",
|
|
|
|
|
file_name: this.shareItem.file_name,
|
|
|
|
|
file_path: this.shareItem.file_path,
|
|
|
|
|
file_size: this.shareItem.file_size,
|
|
|
|
|
file_type: this.shareItem.file_type,
|
|
|
|
|
},
|
|
|
|
|
scene: 1,
|
|
|
|
|
source_id: this.userInfo.id,
|
|
|
|
|
target_id: selectedMembers[0].id,
|
|
|
|
|
timestamp: 0,
|
|
|
|
|
type: "knowledge_share",
|
|
|
|
|
},
|
|
|
|
|
target_id: selectedMembers[0].id,
|
|
|
|
|
topic: "/user/¯",
|
|
|
|
|
}).then((res) => {
|
|
|
|
|
this.$modal.msgSuccess(`已分享给 ${selectedMembers[0].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;
|
|
|
|
|
|
|
|
|
|
// 文件名校验
|
|
|
|
|
const validateResult = this.validateFileName(file.name);
|
|
|
|
|
if (!validateResult.valid) {
|
|
|
|
|
this.$modal.msgError(validateResult.message);
|
|
|
|
|
event.target.value = "";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 文件大小校验(可选,根据需求启用)
|
|
|
|
|
// const sizeResult = this.validateFileSize(file.size);
|
|
|
|
|
// if (!sizeResult.valid) {
|
|
|
|
|
// this.$modal.msgError(sizeResult.message);
|
|
|
|
|
// event.target.value = "";
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
this.currentUploadFile = file;
|
|
|
|
|
this.uploadProgressDialogVisible = true;
|
|
|
|
|
this.uploadPercent = 0;
|
|
|
|
|
|
|
|
|
|
// 创建初始上传记录(无论成功失败都会保存)
|
|
|
|
|
const recordId = this.createTransferRecord(file);
|
|
|
|
|
|
|
|
|
|
// 执行 MinIO 上传
|
|
|
|
|
this.uploadToMinio(file, recordId);
|
|
|
|
|
|
|
|
|
|
// 清空文件输入
|
|
|
|
|
event.target.value = "";
|
|
|
|
|
},
|
|
|
|
|
// 文件名验证
|
|
|
|
|
validateFileName(fileName) {
|
|
|
|
|
// 1. 检查文件名是否为空
|
|
|
|
|
if (!fileName || fileName.trim() === "") {
|
|
|
|
|
return { valid: false, message: "文件名不能为空" };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. 检查文件名长度(不含扩展名最多20字符)
|
|
|
|
|
const nameWithoutExt = fileName.replace(/\.[^/.]+$/, "");
|
|
|
|
|
if (nameWithoutExt.length > 20) {
|
|
|
|
|
return { valid: false, message: "文件名(不含扩展名)不能超过20个字符" };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 检查是否以数字开头
|
|
|
|
|
if (/^\d/.test(nameWithoutExt)) {
|
|
|
|
|
return { valid: false, message: "文件名不能以数字开头" };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. 检查是否以特殊字符开头
|
|
|
|
|
const specialChars = /^[!@#$%^&*()_+\-=\[\]{}|;:,.<>?~`]/;
|
|
|
|
|
if (specialChars.test(nameWithoutExt)) {
|
|
|
|
|
return { valid: false, message: "文件名不能以特殊字符开头" };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. 检查是否包含特殊字符(Windows文件名限制)
|
|
|
|
|
const invalidChars = /[\\/:*?"<>|]/;
|
|
|
|
|
if (invalidChars.test(fileName)) {
|
|
|
|
|
return { valid: false, message: "文件名不能包含 \\ / : * ? \" < > | 等特殊字符" };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { valid: true, message: "" };
|
|
|
|
|
},
|
|
|
|
|
// 文件大小验证(根据需求启用)
|
|
|
|
|
validateFileSize(size) {
|
|
|
|
|
// 例如:限制最大100MB
|
|
|
|
|
const maxSize = 100 * 1024 * 1024;
|
|
|
|
|
if (size > maxSize) {
|
|
|
|
|
return { valid: false, message: `文件大小不能超过100MB,当前文件大小:${(size / (1024 * 1024)).toFixed(2)}MB` };
|
|
|
|
|
}
|
|
|
|
|
return { valid: true, message: "" };
|
|
|
|
|
},
|
|
|
|
|
// 上传到 MinIO(支持 PDF 缩略图生成)
|
|
|
|
|
async uploadToMinio(file, recordId) {
|
|
|
|
|
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,
|
|
|
|
|
recordId
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 完成上传
|
|
|
|
|
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);
|
|
|
|
|
// 更新上传记录为失败状态
|
|
|
|
|
this.updateTransferRecord(recordId, false, "");
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 确保 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 = "",
|
|
|
|
|
recordId = null
|
|
|
|
|
) {
|
|
|
|
|
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("知识库记录保存成功");
|
|
|
|
|
// 更新上传记录为成功状态,保存完整的可预览路径
|
|
|
|
|
if (recordId) {
|
|
|
|
|
const minioEndpoint =
|
|
|
|
|
this.$store.state.user.netConfig?.MINIO_ENDPOINT_HTTPS?.trim() ||
|
|
|
|
|
"http://47.92.6.51:9100/";
|
|
|
|
|
const fileUrl = minioEndpoint + fullFilePath;
|
|
|
|
|
this.updateTransferRecord(recordId, true, fileUrl);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 创建初始上传记录(文件选择时调用,无论成功失败都会保存)
|
|
|
|
|
createTransferRecord(file) {
|
|
|
|
|
const STORAGE_KEY = "file_transfer_records";
|
|
|
|
|
try {
|
|
|
|
|
// 获取现有记录
|
|
|
|
|
const existingData = localStorage.getItem(STORAGE_KEY);
|
|
|
|
|
const records = existingData ? JSON.parse(existingData) : [];
|
|
|
|
|
|
|
|
|
|
// 生成唯一记录 ID
|
|
|
|
|
const recordId =
|
|
|
|
|
Date.now() + "_" + Math.random().toString(36).substr(2, 9);
|
|
|
|
|
|
|
|
|
|
// 创建新记录(transfer_type: 3 表示上传)
|
|
|
|
|
const newRecord = {
|
|
|
|
|
id: recordId,
|
|
|
|
|
file_name: file.name,
|
|
|
|
|
file_size: file.size,
|
|
|
|
|
time: new Date().toISOString(),
|
|
|
|
|
transfer_type: 3, // 3 表示上传操作
|
|
|
|
|
is_success: false, // 初始状态为未完成
|
|
|
|
|
is_uploading: true, // 正在上传中
|
|
|
|
|
file_url: "",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 添加到记录数组
|
|
|
|
|
records.unshift(newRecord);
|
|
|
|
|
|
|
|
|
|
// 保存回 localStorage
|
|
|
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(records));
|
|
|
|
|
console.log("上传记录已创建:", newRecord);
|
|
|
|
|
return recordId;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error("创建上传记录失败", e);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 更新上传记录状态
|
|
|
|
|
updateTransferRecord(recordId, isSuccess, fileUrl = "") {
|
|
|
|
|
const STORAGE_KEY = "file_transfer_records";
|
|
|
|
|
try {
|
|
|
|
|
// 获取现有记录
|
|
|
|
|
const existingData = localStorage.getItem(STORAGE_KEY);
|
|
|
|
|
const records = existingData ? JSON.parse(existingData) : [];
|
|
|
|
|
|
|
|
|
|
// 查找并更新记录
|
|
|
|
|
const recordIndex = records.findIndex(
|
|
|
|
|
(record) => record.id === recordId
|
|
|
|
|
);
|
|
|
|
|
if (recordIndex !== -1) {
|
|
|
|
|
records[recordIndex].is_success = isSuccess;
|
|
|
|
|
records[recordIndex].is_uploading = false; // 上传完成,不再是上传中
|
|
|
|
|
if (fileUrl) {
|
|
|
|
|
records[recordIndex].file_url = fileUrl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 保存回 localStorage
|
|
|
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(records));
|
|
|
|
|
console.log("上传记录已更新:", records[recordIndex]);
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error("更新上传记录失败", e);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
</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>
|