知识库-分享-功能联调

main
ysn 3 days ago
parent 2b2f02be72
commit def8b10005
  1. 83
      src/views/knowledge/index.vue
  2. 199
      src/views/message/components/CreateGroupDialog.vue

@ -276,7 +276,7 @@
<!-- 分享弹窗 - 选择联系人 --> <!-- 分享弹窗 - 选择联系人 -->
<CreateGroupDialog <CreateGroupDialog
:visible.sync="shareDialogVisible" ref="createGroupDialogRef"
title="选择人员" title="选择人员"
:min-select-count="1" :min-select-count="1"
@confirm="handleShareToContacts" @confirm="handleShareToContacts"
@ -313,7 +313,7 @@ export default {
CreateGroupDialog, CreateGroupDialog,
}, },
computed: { computed: {
...mapGetters(["config"]), ...mapGetters(["config", "userInfo"]),
}, },
data() { data() {
return { return {
@ -598,54 +598,45 @@ export default {
}) })
.catch(() => {}); .catch(() => {});
}, },
handleShare(row) { handleShare(row) {
this.shareItem = row; this.shareItem = row;
this.shareDialogVisible = true; this.$refs.createGroupDialogRef.show();
}, },
handleShareToContacts(selectedMembers) { handleShareToContacts(selectedMembers) {
console.log("选中的成员:", selectedMembers, this.userInfo);
if (selectedMembers && selectedMembers.length > 0) { if (selectedMembers && selectedMembers.length > 0) {
const { id, netConfig } = this.$store.state.user; postMessagePushToUser({
const currentUserId = id; client_id: "utalk-client-" + this.userInfo.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: { message: {
at_users: [], at_users: [],
message_id: 0, message_id: 0,
payload: { payload: {
content: JSON.stringify(payloadContent), 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_duration: 0,
file_ico: "", file_ico: "",
file_name: this.shareItem?.title || "", file_name: this.shareItem.file_name,
file_path: this.shareItem?.file_path || "", file_path: this.shareItem.file_path,
file_size: this.shareItem?.file_size || "", file_size: this.shareItem.file_size,
file_type: this.shareItem?.file_type || "", file_type: this.shareItem.file_type,
}, },
scene: 1, scene: 1,
source_id: currentUserId, source_id: this.userInfo.id,
target_id: shareTarget.id, target_id: selectedMembers[0].id,
timestamp: 0, timestamp: 0,
type: MessageType.KNOWLEDGE_SHAREs, type: "knowledge_share",
}, },
target_id: shareTarget.id, target_id: selectedMembers[0].id,
topic: "/user/", topic: "/user/¯",
}; }).then((res) => {
postMessagePushToUser(messageData).then((res) => { this.$modal.msgSuccess(`已分享给 ${selectedMembers[0].name}`);
this.$modal.msgSuccess(`已分享给 ${shareTarget.name}`);
}); });
});
this.shareDialogVisible = false; this.shareDialogVisible = false;
this.shareItem = null; this.shareItem = null;
} }
@ -744,7 +735,13 @@ export default {
} }
}, },
// //
async saveKnowledgeToDB(filePath, file, thumbnailPath = "", bucket = "", recordId = null) { async saveKnowledgeToDB(
filePath,
file,
thumbnailPath = "",
bucket = "",
recordId = null
) {
console.log("saveKnowledgeToDB:", filePath, file, thumbnailPath, bucket); console.log("saveKnowledgeToDB:", filePath, file, thumbnailPath, bucket);
// bucket/object // bucket/object
@ -772,7 +769,9 @@ export default {
console.log("知识库记录保存成功"); console.log("知识库记录保存成功");
// //
if (recordId) { if (recordId) {
const minioEndpoint = this.$store.state.user.netConfig?.MINIO_ENDPOINT_HTTPS?.trim() || "http://47.92.6.51:9100/"; const minioEndpoint =
this.$store.state.user.netConfig?.MINIO_ENDPOINT_HTTPS?.trim() ||
"http://47.92.6.51:9100/";
const fileUrl = minioEndpoint + fullFilePath; const fileUrl = minioEndpoint + fullFilePath;
this.updateTransferRecord(recordId, true, fileUrl); this.updateTransferRecord(recordId, true, fileUrl);
} }
@ -786,7 +785,8 @@ export default {
const records = existingData ? JSON.parse(existingData) : []; const records = existingData ? JSON.parse(existingData) : [];
// ID // ID
const recordId = Date.now() + "_" + Math.random().toString(36).substr(2, 9); const recordId =
Date.now() + "_" + Math.random().toString(36).substr(2, 9);
// transfer_type: 3 // transfer_type: 3
const newRecord = { const newRecord = {
@ -821,7 +821,9 @@ export default {
const records = existingData ? JSON.parse(existingData) : []; const records = existingData ? JSON.parse(existingData) : [];
// //
const recordIndex = records.findIndex(record => record.id === recordId); const recordIndex = records.findIndex(
(record) => record.id === recordId
);
if (recordIndex !== -1) { if (recordIndex !== -1) {
records[recordIndex].is_success = isSuccess; records[recordIndex].is_success = isSuccess;
records[recordIndex].is_uploading = false; // records[recordIndex].is_uploading = false; //
@ -847,12 +849,14 @@ export default {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
.left { .left {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
} }
} }
// //
.resource-thumb { .resource-thumb {
width: 230px; width: 230px;
@ -873,6 +877,7 @@ export default {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
padding: 20px 0; padding: 20px 0;
.type-item { .type-item {
width: 120px; width: 120px;
height: 120px; height: 120px;
@ -883,11 +888,13 @@ export default {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
i { i {
font-size: 40px; font-size: 40px;
color: #409eff; color: #409eff;
margin-bottom: 10px; margin-bottom: 10px;
} }
&:hover { &:hover {
border-color: #409eff; border-color: #409eff;
background: #f5f7fa; background: #f5f7fa;

@ -1,62 +1,35 @@
<template> <template>
<div> <div>
<el-dialog <el-dialog :title="title" :visible.sync="visible" width="38%" append-to-body :before-close="handleClose">
:title="title"
:visible.sync="visible"
width="38%"
append-to-body
:before-close="handleClose"
>
<el-row :gutter="20" class="body"> <el-row :gutter="20" class="body">
<!-- 左侧联系人选择区域 --> <!-- 左侧联系人选择区域 -->
<el-col :span="12"> <el-col :span="12">
<!-- 搜索框 --> <!-- 搜索框 -->
<el-input <el-input v-model="searchText" placeholder="搜索联系人" suffix-icon="el-icon-search" clearable
v-model="searchText" @keyup.enter.native="handleSearch" @clear="handleClearSearch" style="margin-bottom: 10px" />
placeholder="搜索联系人"
suffix-icon="el-icon-search"
clearable
@keyup.enter.native="handleSearch"
@clear="handleClearSearch"
style="margin-bottom: 10px"
/>
<!-- 标签页 --> <!-- 标签页 -->
<el-tabs v-model="activeName" @tab-click="handleClick"> <el-tabs v-model="activeName" @tab-click="handleClick">
<!-- 最近联系人 --> <!-- 最近联系人 -->
<el-tab-pane label="最近联系人" name="recentContacts"> <el-tab-pane label="最近联系人" name="recentContacts">
<div v-loading="loading" class="contact-list"> <div v-loading="loading" class="contact-list">
<div <div v-for="member in displayMembers" :key="member.id" :class="[
v-for="member in displayMembers"
:key="member.id"
:class="[
'member-item', 'member-item',
{ {
active: isSelected(member.id), active: isSelected(member.id),
offline: !member.online, offline: !member.online,
}, },
]" ]" @click="toggleSelect(member)">
@click="toggleSelect(member)" <el-checkbox v-model="member.selected" :disabled="isSelectDisabled && !member.selected"
> @change="toggleSelect(member)">
<el-checkbox
v-model="member.selected"
:disabled="isSelectDisabled && !member.selected"
@change="toggleSelect(member)"
>
<div class="member-item-label"> <div class="member-item-label">
<el-avatar <el-avatar :src="member.avatar" icon="el-icon-user-solid" />
:src="member.avatar"
icon="el-icon-user-solid"
/>
<span class="member-name">{{ member.name }}</span> <span class="member-name">{{ member.name }}</span>
<span v-if="member.online" class="online-dot" /> <span v-if="member.online" class="online-dot" />
</div> </div>
</el-checkbox> </el-checkbox>
</div> </div>
<el-empty <el-empty v-if="!loading && displayMembers.length === 0" description="暂无联系人" />
v-if="!loading && displayMembers.length === 0"
description="暂无联系人"
/>
</div> </div>
</el-tab-pane> </el-tab-pane>
@ -65,11 +38,7 @@
<div v-loading="loading" class="contact-list"> <div v-loading="loading" class="contact-list">
<div v-for="group in recentGroups" :key="group.id"> <div v-for="group in recentGroups" :key="group.id">
<!-- 群组标题 --> <!-- 群组标题 -->
<div <div class="group-header" :class="{ expanded: group.expanded }" @click="toggleGroupExpand(group)">
class="group-header"
:class="{ expanded: group.expanded }"
@click="toggleGroupExpand(group)"
>
<el-avatar :size="32" :src="group.avatar" /> <el-avatar :size="32" :src="group.avatar" />
<span class="group-name">{{ group.name }}</span> <span class="group-name">{{ group.name }}</span>
<i class="el-icon-arrow-right expand-icon" /> <i class="el-icon-arrow-right expand-icon" />
@ -77,35 +46,21 @@
<!-- 群成员列表 --> <!-- 群成员列表 -->
<div v-if="group.expanded"> <div v-if="group.expanded">
<!-- 全选 --> <!-- 全选 -->
<div <div class="member-item" :class="{ selected: group.allSelected }"
class="member-item" @click="toggleGroupAllSelect(group)">
:class="{ selected: group.allSelected }" <el-checkbox v-model="group.allSelected" @change="toggleGroupAllSelect(group)" />
@click="toggleGroupAllSelect(group)"
>
<el-checkbox
v-model="group.allSelected"
@change="toggleGroupAllSelect(group)"
/>
<span class="member-name">全选</span> <span class="member-name">全选</span>
</div> </div>
<!-- 成员列表 --> <!-- 成员列表 -->
<div <div v-for="member in group.members" :key="member.id" :class="[
v-for="member in group.members"
:key="member.id"
:class="[
'member-item', 'member-item',
{ {
active: isSelected(member.id), active: isSelected(member.id),
offline: !member.online, offline: !member.online,
}, },
]" ]" @click="toggleSelect(member)">
@click="toggleSelect(member)" <el-checkbox v-model="member.selected" :disabled="isSelectDisabled && !member.selected"
> @change="toggleSelect(member)">
<el-checkbox
v-model="member.selected"
:disabled="isSelectDisabled && !member.selected"
@change="toggleSelect(member)"
>
<div class="member-item-label"> <div class="member-item-label">
<el-avatar :size="32" :src="member.avatar" /> <el-avatar :size="32" :src="member.avatar" />
<span class="member-name">{{ member.name }}</span> <span class="member-name">{{ member.name }}</span>
@ -114,25 +69,16 @@
</div> </div>
</div> </div>
</div> </div>
<el-empty <el-empty v-if="!loading && recentGroups.length === 0" description="暂无群组" />
v-if="!loading && recentGroups.length === 0"
description="暂无群组"
/>
</div> </div>
</el-tab-pane> </el-tab-pane>
<!-- 组织架构 --> <!-- 组织架构 -->
<el-tab-pane label="组织架构" name="organization"> <el-tab-pane label="组织架构" name="organization">
<div v-loading="loading" class="contact-list"> <div v-loading="loading" class="contact-list">
<el-tree <el-tree ref="orgTree" :data="treeData" :props="{ label: 'name', children: 'children' }"
ref="orgTree" :highlight-current="true" node-key="id" :expand-on-click-node="false"
:data="treeData" @node-click="handleOrgNodeClick">
:props="{ label: 'name', children: 'children' }"
:highlight-current="true"
node-key="id"
:expand-on-click-node="false"
@node-click="handleOrgNodeClick"
>
<template #default="{ node, data }"> <template #default="{ node, data }">
<div class="custom-tree-node"> <div class="custom-tree-node">
<!-- 组织节点 --> <!-- 组织节点 -->
@ -142,30 +88,19 @@
</template> </template>
<!-- 全选节点 --> <!-- 全选节点 -->
<template v-else-if="data.type === 'all'"> <template v-else-if="data.type === 'all'">
<el-checkbox <el-checkbox :indeterminate="data.indeterminate" v-model="data.checkAll"
:indeterminate="data.indeterminate" @change="handleOrgCheckAllChange(data)">
v-model="data.checkAll"
@change="handleOrgCheckAllChange(data)"
>
全选 全选
</el-checkbox> </el-checkbox>
</template> </template>
<!-- 用户节点 --> <!-- 用户节点 -->
<div v-else-if="data.type === 'user'" class="userline"> <div v-else-if="data.type === 'user'" class="userline">
<el-checkbox <el-checkbox :value="isSelected(data.id)" @change="toggleSelect(data)"
:value="isSelected(data.id)" :disabled="isSelectDisabled && !isSelected(data.id)">
@change="toggleSelect(data)"
:disabled="isSelectDisabled && !isSelected(data.id)"
>
<div class="member-item-label"> <div class="member-item-label">
<el-avatar <el-avatar :size="32" :src="$store.state.user.netConfig
:size="32"
:src="
$store.state.user.netConfig
.MINIO_ENDPOINT_HTTPS + data.avatar .MINIO_ENDPOINT_HTTPS + data.avatar
" " icon="el-icon-user-solid" />
icon="el-icon-user-solid"
/>
<span class="member-name">{{ data.name }}</span> <span class="member-name">{{ data.name }}</span>
<span v-if="data.online" class="online-dot" /> <span v-if="data.online" class="online-dot" />
</div> </div>
@ -184,59 +119,36 @@
</el-col> </el-col>
<!-- 右侧已选人员 --> <!-- 右侧已选人员 -->
<el-col <el-col :span="12" style="height: 100%; display: flex; flex-direction: column">
:span="12"
style="height: 100%; display: flex; flex-direction: column"
>
<div class="selected-header"> <div class="selected-header">
已选人员({{ selectedMembers.length }} 已选人员({{ selectedMembers.length }}
<template v-if="maxSelectCount > 0">/{{ maxSelectCount }}</template <template v-if="maxSelectCount > 0">/{{ maxSelectCount }}</template>)
>) <span v-if="selectedMembers.length > 0" class="clear-btn" @click="clearAllSelected">
<span
v-if="selectedMembers.length > 0"
class="clear-btn"
@click="clearAllSelected"
>
<i class="el-icon-close" /> <i class="el-icon-close" />
</span> </span>
</div> </div>
<div class="selected-list"> <div class="selected-list">
<div <div v-for="member in selectedMembers" :key="member.id" class="selected-item">
v-for="member in selectedMembers"
:key="member.id"
class="selected-item"
>
<el-avatar :size="40" :src="member.avatar" /> <el-avatar :size="40" :src="member.avatar" />
<span class="selected-name">{{ member.name }}</span> <span class="selected-name">{{ member.name }}</span>
<span class="remove-btn" @click="removeSelected(member)"> <span class="remove-btn" @click="removeSelected(member)">
<i class="el-icon-close" /> <i class="el-icon-close" />
</span> </span>
</div> </div>
<el-empty <el-empty v-if="selectedMembers.length === 0" description="暂无选中人员" class="empty-tip" />
v-if="selectedMembers.length === 0"
description="暂无选中人员"
class="empty-tip"
/>
</div> </div>
<div <div v-if="
v-if="
maxSelectCount > 0 && selectedMembers.length >= maxSelectCount maxSelectCount > 0 && selectedMembers.length >= maxSelectCount
" " class="max-tip">
class="max-tip"
>
已达到最大选择数量({{ maxSelectCount }}) 已达到最大选择数量({{ maxSelectCount }})
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button> <el-button @click="handleClose"> </el-button>
<el-button <el-button type="primary" :loading="confirmLoading" :disabled="selectedMembers.length < minSelectCount"
type="primary" @click="confirmCreateGroup">
:loading="confirmLoading" 确认
:disabled="selectedMembers.length < minSelectCount"
@click="confirmCreateGroup"
>
{{ confirmText }}
</el-button> </el-button>
</span> </span>
</el-dialog> </el-dialog>
@ -258,21 +170,13 @@ export default {
type: String, type: String,
default: "发起群聊", default: "发起群聊",
}, },
visible: {
type: Boolean,
default: false,
},
minSelectCount: { minSelectCount: {
type: Number, type: Number,
default: 2, default: 1,
}, },
maxSelectCount: { maxSelectCount: {
type: Number, type: Number,
default: 0, default: 1,
},
confirmText: {
type: String,
default: "创建",
}, },
confirmCallback: { confirmCallback: {
type: Function, type: Function,
@ -281,6 +185,7 @@ export default {
}, },
data() { data() {
return { return {
visible: false,
activeName: "recentContacts", activeName: "recentContacts",
searchText: "", searchText: "",
loading: false, loading: false,
@ -302,14 +207,11 @@ export default {
return [...online, ...offline]; return [...online, ...offline];
}, },
}, },
watch: { methods: {
visible(val) { show() {
if (val) { this.visible = true;
this.loadData(); this.loadData();
}
},
}, },
methods: {
async loadData() { async loadData() {
await Promise.all([ await Promise.all([
this.fetchRecentContacts(), this.fetchRecentContacts(),
@ -748,13 +650,8 @@ export default {
// //
handleClose(done) { handleClose(done) {
this.$confirm("确认关闭?")
.then((_) => {
this.clearAllSelected(); this.clearAllSelected();
this.searchText = ""; this.searchText = "";
done();
})
.catch((_) => {});
}, },
// //
@ -765,14 +662,16 @@ export default {
} }
this.confirmLoading = true; this.confirmLoading = true;
try { try {
// 1. props
if (typeof this.confirmCallback === "function") { if (typeof this.confirmCallback === "function") {
await this.confirmCallback(this.selectedMembers); await this.confirmCallback(this.selectedMembers);
} }
// 2. confirm
this.$emit("confirm", this.selectedMembers); this.$emit("confirm", this.selectedMembers);
this.$emit("create-success", this.selectedMembers); // 3.
this.clearAllSelected(); this.clearAllSelected();
this.searchText = ""; this.searchText = "";
this.$emit("update:visible", false); this.visible = false;
} catch (e) { } catch (e) {
this.$message.error("操作失败"); this.$message.error("操作失败");
} finally { } finally {
@ -786,6 +685,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.body { .body {
height: 450px; height: 450px;
.contact-list { .contact-list {
height: 400px; height: 400px;
overflow-y: auto; overflow-y: auto;
@ -812,10 +712,12 @@ export default {
color: #909399; color: #909399;
} }
} }
.member-item-label { .member-item-label {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
.member-name { .member-name {
font-size: 14px; font-size: 14px;
color: #303133; color: #303133;
@ -970,6 +872,7 @@ export default {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.userline { .userline {
display: flex; display: flex;
align-items: center; align-items: center;

Loading…
Cancel
Save