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