|
|
|
|
@ -1,30 +1,28 @@ |
|
|
|
|
<template> |
|
|
|
|
<div class="app-container addrbook-page"> |
|
|
|
|
<!-- 左列:组织架构树 --> |
|
|
|
|
<!-- 左侧树区域 --> |
|
|
|
|
<div class="addrbook-left"> |
|
|
|
|
<div |
|
|
|
|
v-for="(item, idx) in leftTabList" |
|
|
|
|
:key="idx" |
|
|
|
|
class="latest-contacts" |
|
|
|
|
:class="{ active: isLatestContacts }" |
|
|
|
|
@click="showLatestContacts" |
|
|
|
|
:class="{ active: item.flag && isLatestContacts }" |
|
|
|
|
@click="item.fn()" |
|
|
|
|
> |
|
|
|
|
<i class="el-icon-time" /> |
|
|
|
|
<span>常用联系人</span> |
|
|
|
|
</div> |
|
|
|
|
<div class="latest-contacts"> |
|
|
|
|
<i class="el-icon-office-building" /> |
|
|
|
|
<span>组织架构</span> |
|
|
|
|
<i :class="item.icon" /> |
|
|
|
|
<span>{{ item.label }}</span> |
|
|
|
|
</div> |
|
|
|
|
<el-tree |
|
|
|
|
ref="orgTree" |
|
|
|
|
:data="treeData" |
|
|
|
|
:props="treeProps" |
|
|
|
|
default-expand-all |
|
|
|
|
:highlight-current="true" |
|
|
|
|
highlight-current |
|
|
|
|
node-key="id" |
|
|
|
|
@node-click="handleNodeClick" |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
<!-- 中列:成员列表 --> |
|
|
|
|
<!-- 中间成员列表 --> |
|
|
|
|
<div class="addrbook-center"> |
|
|
|
|
<div class="search-box"> |
|
|
|
|
<el-input |
|
|
|
|
@ -46,84 +44,73 @@ |
|
|
|
|
</div> |
|
|
|
|
<div v-loading="loading" class="member-list"> |
|
|
|
|
<div |
|
|
|
|
v-for="member in displayMembers" |
|
|
|
|
v-for="member in memberList" |
|
|
|
|
:key="member.id" |
|
|
|
|
:class="[ |
|
|
|
|
'member-item', |
|
|
|
|
{ |
|
|
|
|
active: currentUser && currentUser.id === member.id, |
|
|
|
|
class="member-item" |
|
|
|
|
:class="{ |
|
|
|
|
active: currentUser.id === member.id, |
|
|
|
|
offline: !member.online, |
|
|
|
|
}, |
|
|
|
|
]" |
|
|
|
|
@click="handleMemberClick(member)" |
|
|
|
|
}" |
|
|
|
|
@click="setSelectUser(member)" |
|
|
|
|
> |
|
|
|
|
<el-avatar |
|
|
|
|
:size="32" |
|
|
|
|
:src="member.avatar | avatarFilter" |
|
|
|
|
:src="getAvatarUrl(member.avatar)" |
|
|
|
|
icon="el-icon-user-solid" |
|
|
|
|
/> |
|
|
|
|
<span class="member-name">{{ member.name }}</span> |
|
|
|
|
<span v-if="member.online" class="online-dot" /> |
|
|
|
|
</div> |
|
|
|
|
<el-empty |
|
|
|
|
v-if="!loading && displayMembers.length === 0" |
|
|
|
|
v-if="!loading && !memberList.length" |
|
|
|
|
description="暂无成员" |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
<!-- 右列:详情面板 --> |
|
|
|
|
|
|
|
|
|
<!-- 右侧详情 --> |
|
|
|
|
<div class="addrbook-right"> |
|
|
|
|
<div v-if="currentUser" class="detail-panel"> |
|
|
|
|
<div class="avatar-section"> |
|
|
|
|
<el-avatar |
|
|
|
|
:size="80" |
|
|
|
|
:src="currentUser.avatar | avatarFilter" |
|
|
|
|
:src="getAvatarUrl(currentUser.avatar)" |
|
|
|
|
icon="el-icon-user-solid" |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
<div class="info-section"> |
|
|
|
|
<div class="info-row"> |
|
|
|
|
<i class="el-icon-user" /> |
|
|
|
|
<span class="label">姓名</span> |
|
|
|
|
<el-tooltip :content="currentUser.name" placement="top"> |
|
|
|
|
<span class="value">{{ currentUser.name }}</span> |
|
|
|
|
</el-tooltip> |
|
|
|
|
</div> |
|
|
|
|
<div class="info-row"> |
|
|
|
|
<i class="el-icon-message" /> |
|
|
|
|
<span class="label">邮箱</span> |
|
|
|
|
<el-tooltip :content="currentUser.email || ''" placement="top"> |
|
|
|
|
<span class="value">{{ currentUser.email }}</span> |
|
|
|
|
</el-tooltip> |
|
|
|
|
</div> |
|
|
|
|
<div class="info-row"> |
|
|
|
|
<i class="el-icon-office-building" /> |
|
|
|
|
<span class="label">部门</span> |
|
|
|
|
<el-tooltip :content="currentUser.full_group || ''" placement="top"> |
|
|
|
|
<span class="value">{{ currentUser.full_group }}</span> |
|
|
|
|
</el-tooltip> |
|
|
|
|
</div> |
|
|
|
|
<div class="info-row"> |
|
|
|
|
<i class="el-icon-s-custom" /> |
|
|
|
|
<span class="label">职位</span> |
|
|
|
|
<el-tooltip :content="currentUser.role || ''" placement="top"> |
|
|
|
|
<span class="value">{{ currentUser.role }}</span> |
|
|
|
|
</el-tooltip> |
|
|
|
|
</div> |
|
|
|
|
<div class="info-row"> |
|
|
|
|
<i class="el-icon-phone" /> |
|
|
|
|
<span class="label">手机</span> |
|
|
|
|
<span class="value">{{ currentUser.phone }}</span> |
|
|
|
|
</div> |
|
|
|
|
<div class="info-row"> |
|
|
|
|
<i class="el-icon-info" /> |
|
|
|
|
<span class="label">状态</span> |
|
|
|
|
<div v-for="info in userInfoList" :key="info.key" class="info-row"> |
|
|
|
|
<i :class="info.icon" /> |
|
|
|
|
<span class="label">{{ info.label }}</span> |
|
|
|
|
<el-tooltip |
|
|
|
|
:content=" |
|
|
|
|
info.key === 'online' |
|
|
|
|
? currentUser.online |
|
|
|
|
? '正常' |
|
|
|
|
: '离线' |
|
|
|
|
: currentUser[info.key] |
|
|
|
|
" |
|
|
|
|
placement="top" |
|
|
|
|
> |
|
|
|
|
<span |
|
|
|
|
class="value" |
|
|
|
|
:class="currentUser.online ? 'online' : 'offline'" |
|
|
|
|
:class=" |
|
|
|
|
info.key === 'online' |
|
|
|
|
? currentUser.online |
|
|
|
|
? 'online' |
|
|
|
|
: 'offline' |
|
|
|
|
: '' |
|
|
|
|
" |
|
|
|
|
> |
|
|
|
|
{{ currentUser.online ? "正常" : "离线" }} |
|
|
|
|
{{ |
|
|
|
|
info.key === "online" |
|
|
|
|
? currentUser.online |
|
|
|
|
? "正常" |
|
|
|
|
: "离线" |
|
|
|
|
: currentUser[info.key] |
|
|
|
|
}} |
|
|
|
|
</span> |
|
|
|
|
</el-tooltip> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
<div class="action-section"> |
|
|
|
|
@ -144,7 +131,6 @@ |
|
|
|
|
</template> |
|
|
|
|
|
|
|
|
|
<script> |
|
|
|
|
import EventBus from "@/utils/eventBus"; |
|
|
|
|
import { |
|
|
|
|
getMessagesLatestContacts, |
|
|
|
|
getGroupsList, |
|
|
|
|
@ -153,121 +139,106 @@ import { |
|
|
|
|
} from "@/api/contacts/index.js"; |
|
|
|
|
export default { |
|
|
|
|
name: "Contacts", |
|
|
|
|
filters: { |
|
|
|
|
avatarFilter(avatar) { |
|
|
|
|
if (!avatar) return ""; |
|
|
|
|
if (avatar.startsWith("http")) return avatar; |
|
|
|
|
// 尝试拼接 MinIO 访问路径,实际项目中应根据 netConfig 动态生成 |
|
|
|
|
return avatar; |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
data() { |
|
|
|
|
return { |
|
|
|
|
treeData: [], |
|
|
|
|
treeProps: { |
|
|
|
|
label: "name", |
|
|
|
|
children: "children", |
|
|
|
|
}, |
|
|
|
|
expandedKeys: [], |
|
|
|
|
treeProps: { label: "name", children: "child" }, |
|
|
|
|
currentGroupId: null, |
|
|
|
|
currentStructureName: "", |
|
|
|
|
memberList: [], |
|
|
|
|
offlineMemberList: [], |
|
|
|
|
currentUser: null, |
|
|
|
|
searchText: "", |
|
|
|
|
loading: false, |
|
|
|
|
isLatestContacts: false, |
|
|
|
|
userGroupId: null, |
|
|
|
|
// 左侧顶部菜单配置 |
|
|
|
|
leftTabList: [ |
|
|
|
|
{ |
|
|
|
|
label: "常用联系人", |
|
|
|
|
icon: "el-icon-time", |
|
|
|
|
flag: true, |
|
|
|
|
fn: () => this.showLatestContacts(), |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
label: "组织架构", |
|
|
|
|
icon: "el-icon-office-building", |
|
|
|
|
flag: false, |
|
|
|
|
fn: () => this.fetchOrgTree(), |
|
|
|
|
}, |
|
|
|
|
], |
|
|
|
|
// 用户详情配置 |
|
|
|
|
userInfoList: [ |
|
|
|
|
{ label: "姓名", icon: "el-icon-user", key: "name" }, |
|
|
|
|
{ label: "邮箱", icon: "el-icon-message", key: "email" }, |
|
|
|
|
{ label: "部门", icon: "el-icon-office-building", key: "full_group" }, |
|
|
|
|
{ label: "职位", icon: "el-icon-s-custom", key: "role" }, |
|
|
|
|
{ label: "手机", icon: "el-icon-phone", key: "phone" }, |
|
|
|
|
{ label: "状态", icon: "el-icon-info", key: "online" }, |
|
|
|
|
], |
|
|
|
|
}; |
|
|
|
|
}, |
|
|
|
|
computed: { |
|
|
|
|
displayMembers() { |
|
|
|
|
// 在线在前,离线在后,与 Qt 代码逻辑一致 |
|
|
|
|
const online = this.memberList.filter((m) => m.online); |
|
|
|
|
const offline = this.memberList.filter((m) => !m.online); |
|
|
|
|
return [...online, ...offline]; |
|
|
|
|
}, |
|
|
|
|
currentUserInfo() { |
|
|
|
|
return this.$store.state.user.userInfo; |
|
|
|
|
MINIO_BASE() { |
|
|
|
|
return this.$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS || ""; |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
mounted() { |
|
|
|
|
// 默认显示常用联系人 |
|
|
|
|
this.showLatestContacts(); |
|
|
|
|
this.fetchOrgTree(); |
|
|
|
|
this.showLatestContacts(); |
|
|
|
|
}, |
|
|
|
|
methods: { |
|
|
|
|
// 获取组织架构树,与 Qt Cmd_addr_book_addr 对应,POST 空 body |
|
|
|
|
// 统一获取头像地址 |
|
|
|
|
getAvatarUrl(avatar) { |
|
|
|
|
if (!avatar) return ""; |
|
|
|
|
return avatar.startsWith("http") ? avatar : `${this.MINIO_BASE}${avatar}`; |
|
|
|
|
}, |
|
|
|
|
// 选中联系人公共方法 |
|
|
|
|
setSelectUser(user) { |
|
|
|
|
this.currentUser = { ...user }; |
|
|
|
|
}, |
|
|
|
|
// 树形结构相关 |
|
|
|
|
async fetchOrgTree() { |
|
|
|
|
try { |
|
|
|
|
const res = await getGroupsList(); |
|
|
|
|
const list = res.data?.list || []; |
|
|
|
|
this.treeData = this.buildTree(list); |
|
|
|
|
// 自动展开当前用户所在组 |
|
|
|
|
if (this.userGroupId) { |
|
|
|
|
this.expandedKeys = this.findParentKeys( |
|
|
|
|
this.treeData, |
|
|
|
|
this.userGroupId |
|
|
|
|
); |
|
|
|
|
const { data } = await getGroupsList(); |
|
|
|
|
this.treeData =data.list |
|
|
|
|
if (!this.userGroupId) return; |
|
|
|
|
const parentKeys = this.findParentKeys(this.treeData, this.userGroupId); |
|
|
|
|
this.$nextTick(() => { |
|
|
|
|
this.$refs.orgTree.setCurrentKey(this.userGroupId); |
|
|
|
|
if (this.userGroupId) { |
|
|
|
|
this.fetchMembers(this.userGroupId); |
|
|
|
|
const node = this.findNodeById(this.treeData, this.userGroupId); |
|
|
|
|
if (node) { |
|
|
|
|
this.currentStructureName = node.name; |
|
|
|
|
} |
|
|
|
|
this.fetchMembers(this.userGroupId); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
} catch (err) { |
|
|
|
|
console.error("获取组织架构失败", err); |
|
|
|
|
} |
|
|
|
|
} catch (e) { |
|
|
|
|
console.error("获取组织架构失败", e); |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// 构建树形数据 |
|
|
|
|
buildTree(list) { |
|
|
|
|
return list.map((item) => ({ |
|
|
|
|
id: item.id, |
|
|
|
|
name: item.name, |
|
|
|
|
level: item.level, |
|
|
|
|
children: |
|
|
|
|
item.child && item.child.length > 0 |
|
|
|
|
? this.buildTree(item.child) |
|
|
|
|
: undefined, |
|
|
|
|
})); |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// 查找父节点路径 |
|
|
|
|
findParentKeys(tree, targetId, parents = []) { |
|
|
|
|
for (const node of tree) { |
|
|
|
|
if (node.id === targetId) { |
|
|
|
|
return [...parents, node.id]; |
|
|
|
|
} |
|
|
|
|
if (node.children && node.children.length > 0) { |
|
|
|
|
const found = this.findParentKeys(node.children, targetId, [ |
|
|
|
|
if (node.id === targetId) return [...parents, node.id]; |
|
|
|
|
if (node.children.length) { |
|
|
|
|
const res = this.findParentKeys(node.children, targetId, [ |
|
|
|
|
...parents, |
|
|
|
|
node.id, |
|
|
|
|
]); |
|
|
|
|
if (found.length > 0) return found; |
|
|
|
|
if (res.length) return res; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return []; |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// 根据ID查找节点 |
|
|
|
|
findNodeById(tree, id) { |
|
|
|
|
for (const node of tree) { |
|
|
|
|
if (node.id === id) return node; |
|
|
|
|
if (node.children) { |
|
|
|
|
const found = this.findNodeById(node.children, id); |
|
|
|
|
if (found) return found; |
|
|
|
|
const res = this.findNodeById(node.children, id); |
|
|
|
|
if (res) return res; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return null; |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// 点击树节点,与 Qt OnListWidgetTreeMenuSwitch 对应 |
|
|
|
|
// 树点击 |
|
|
|
|
handleNodeClick(data) { |
|
|
|
|
this.isLatestContacts = false; |
|
|
|
|
this.currentStructureName = data.name; |
|
|
|
|
@ -275,157 +246,86 @@ export default { |
|
|
|
|
this.currentUser = null; |
|
|
|
|
this.fetchMembers(data.id); |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// 获取组成员,与 Qt GetAddrBookDetailInfo 对应,POST { group_id } |
|
|
|
|
// 获取部门成员 |
|
|
|
|
async fetchMembers(groupId) { |
|
|
|
|
this.loading = true; |
|
|
|
|
try { |
|
|
|
|
const res = await getGroupsListUser({ group_id: groupId }); |
|
|
|
|
const list = res.data?.list || []; |
|
|
|
|
// 与 Qt 代码一致:区分在线离线,但这里统一存入 memberList,由 computed 排序 |
|
|
|
|
this.memberList = list.map((item) => ({ |
|
|
|
|
id: item.id, |
|
|
|
|
name: item.name, |
|
|
|
|
role: item.role, |
|
|
|
|
phone: item.phone, |
|
|
|
|
status: item.status, |
|
|
|
|
email: item.email, |
|
|
|
|
group: item.group, |
|
|
|
|
full_group: item.full_group, |
|
|
|
|
avatar: item.avatar, |
|
|
|
|
online: item.online === 1 || item.online === true, |
|
|
|
|
})); |
|
|
|
|
// 默认选择第一个联系人 |
|
|
|
|
if (this.memberList.length > 0) { |
|
|
|
|
this.handleMemberClick(this.memberList[0]); |
|
|
|
|
} |
|
|
|
|
console.log(this.memberList, "组成员"); |
|
|
|
|
} catch (e) { |
|
|
|
|
console.error("获取成员列表失败", e); |
|
|
|
|
const { data } = await getGroupsListUser({ group_id: groupId }); |
|
|
|
|
this.memberList = data.list || []; |
|
|
|
|
this.memberList.length && this.setSelectUser(this.memberList[0]); |
|
|
|
|
} catch (err) { |
|
|
|
|
console.error("获取成员失败", err); |
|
|
|
|
this.memberList = []; |
|
|
|
|
} finally { |
|
|
|
|
this.loading = false; |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// 显示最近联系人,与 Qt SendLatestContactsSlot 对应 |
|
|
|
|
showLatestContacts() { |
|
|
|
|
// 常用联系人 |
|
|
|
|
async showLatestContacts() { |
|
|
|
|
this.isLatestContacts = true; |
|
|
|
|
this.currentStructureName = "最近联系人"; |
|
|
|
|
this.currentGroupId = null; |
|
|
|
|
this.currentUser = null; |
|
|
|
|
// 取消树节点选中 |
|
|
|
|
this.$refs.orgTree.setCurrentKey(null); |
|
|
|
|
this.fetchLatestContacts(); |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// 获取最近联系人,与 Qt GetLatestContacts 对应,POST { scene: 1, page: 1, size: 65535 } |
|
|
|
|
async fetchLatestContacts() { |
|
|
|
|
this.loading = true; |
|
|
|
|
try { |
|
|
|
|
const res = await getMessagesLatestContacts({ |
|
|
|
|
const { data } = await getMessagesLatestContacts({ |
|
|
|
|
scene: 1, |
|
|
|
|
page: 1, |
|
|
|
|
size: 65535, |
|
|
|
|
}); |
|
|
|
|
const list = res.data?.list || []; |
|
|
|
|
this.memberList = list.map((item) => ({ |
|
|
|
|
id: item.id, |
|
|
|
|
name: item.name, |
|
|
|
|
role: item.role, |
|
|
|
|
phone: item.phone, |
|
|
|
|
status: item.status, |
|
|
|
|
email: item.email, |
|
|
|
|
group: item.group, |
|
|
|
|
full_group: item.full_group, |
|
|
|
|
avatar: item.avatar, |
|
|
|
|
online: item.online === 1 || item.online === true, |
|
|
|
|
})); |
|
|
|
|
// 默认选择第一个联系人 |
|
|
|
|
if (this.memberList.length > 0) { |
|
|
|
|
this.handleMemberClick(this.memberList[0]); |
|
|
|
|
} |
|
|
|
|
} catch (e) { |
|
|
|
|
console.error("获取最近联系人失败", e); |
|
|
|
|
this.memberList = data.list || []; |
|
|
|
|
this.memberList.length && this.setSelectUser(this.memberList[0]); |
|
|
|
|
} catch (err) { |
|
|
|
|
console.error("常用联系人异常", err); |
|
|
|
|
this.memberList = []; |
|
|
|
|
} finally { |
|
|
|
|
this.loading = false; |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// 点击成员,与 Qt OnSecListMenuSwitch 对应 |
|
|
|
|
handleMemberClick(member) { |
|
|
|
|
this.currentUser = { ...member }; |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// 搜索联系人,与 Qt searchContactsClicked 对应 |
|
|
|
|
// 搜索 |
|
|
|
|
async handleSearch() { |
|
|
|
|
const text = this.searchText.trim(); |
|
|
|
|
if (!text) { |
|
|
|
|
if (this.isLatestContacts) { |
|
|
|
|
this.fetchLatestContacts(); |
|
|
|
|
} else if (this.currentGroupId) { |
|
|
|
|
this.fetchMembers(this.currentGroupId); |
|
|
|
|
} |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
const keyword = this.searchText.trim(); |
|
|
|
|
if (!keyword) return this.handleClearSearch(); |
|
|
|
|
this.loading = true; |
|
|
|
|
try { |
|
|
|
|
const res = await searchUsers({ |
|
|
|
|
search_key: text, |
|
|
|
|
const { data } = await searchUsers({ |
|
|
|
|
search_key: keyword, |
|
|
|
|
user_ids: [], |
|
|
|
|
page: 1, |
|
|
|
|
size: 10000, |
|
|
|
|
}); |
|
|
|
|
const list = res.data?.list || []; |
|
|
|
|
this.isLatestContacts = true; |
|
|
|
|
this.currentStructureName = "搜索结果"; |
|
|
|
|
this.currentGroupId = null; |
|
|
|
|
this.$refs.orgTree.setCurrentKey(null); |
|
|
|
|
this.memberList = list.map((item) => ({ |
|
|
|
|
id: item.id, |
|
|
|
|
name: item.name, |
|
|
|
|
role: item.role, |
|
|
|
|
phone: item.phone, |
|
|
|
|
status: item.status, |
|
|
|
|
email: item.email, |
|
|
|
|
group: item.group, |
|
|
|
|
full_group: item.full_group, |
|
|
|
|
avatar: item.avatar, |
|
|
|
|
online: item.online === 1 || item.online === true, |
|
|
|
|
this.memberList = (data.list || []).map((item) => ({ |
|
|
|
|
...item, |
|
|
|
|
online: [1, true].includes(item.online), |
|
|
|
|
})); |
|
|
|
|
} catch (e) { |
|
|
|
|
console.error("搜索联系人失败", e); |
|
|
|
|
this.memberList.length && this.setSelectUser(this.memberList[0]); |
|
|
|
|
} catch (err) { |
|
|
|
|
console.error("搜索失败", err); |
|
|
|
|
} finally { |
|
|
|
|
this.loading = false; |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// 清除搜索 |
|
|
|
|
// 清空搜索 |
|
|
|
|
handleClearSearch() { |
|
|
|
|
this.searchText = ""; |
|
|
|
|
if (this.isLatestContacts) { |
|
|
|
|
this.fetchLatestContacts(); |
|
|
|
|
} else if (this.currentGroupId) { |
|
|
|
|
this.fetchMembers(this.currentGroupId); |
|
|
|
|
} else if (this.userGroupId) { |
|
|
|
|
this.fetchMembers(this.userGroupId); |
|
|
|
|
} |
|
|
|
|
if (this.isLatestContacts) this.showLatestContacts(); |
|
|
|
|
else if (this.currentGroupId) this.fetchMembers(this.currentGroupId); |
|
|
|
|
else if (this.userGroupId) this.fetchMembers(this.userGroupId); |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// 刷新 |
|
|
|
|
handleRefresh() { |
|
|
|
|
if (this.isLatestContacts) { |
|
|
|
|
this.fetchLatestContacts(); |
|
|
|
|
} else if (this.currentGroupId) { |
|
|
|
|
this.fetchMembers(this.currentGroupId); |
|
|
|
|
} |
|
|
|
|
this.isLatestContacts |
|
|
|
|
? this.showLatestContacts() |
|
|
|
|
: this.fetchMembers(this.currentGroupId); |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// 发消息,与 Qt SendMessag / SignalSendSelectedContactInfo 对应 |
|
|
|
|
// 跳转发消息 |
|
|
|
|
sendMessage() { |
|
|
|
|
if (!this.currentUser) return; |
|
|
|
|
// 跳转到消息页面并传递联系人信息 |
|
|
|
|
this.$router.push({ |
|
|
|
|
path: "/message", |
|
|
|
|
query: { |
|
|
|
|
@ -444,12 +344,11 @@ export default { |
|
|
|
|
display: flex; |
|
|
|
|
height: 100%; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 左侧 |
|
|
|
|
.addrbook-left { |
|
|
|
|
width: 240px; |
|
|
|
|
border-right: 1px solid #ebeef5; |
|
|
|
|
overflow-y: auto; |
|
|
|
|
|
|
|
|
|
.latest-contacts { |
|
|
|
|
display: flex; |
|
|
|
|
align-items: center; |
|
|
|
|
@ -461,78 +360,61 @@ export default { |
|
|
|
|
font-size: 14px; |
|
|
|
|
color: #606266; |
|
|
|
|
transition: all 0.2s; |
|
|
|
|
|
|
|
|
|
i { |
|
|
|
|
font-size: 16px; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
&:hover { |
|
|
|
|
background: #e5f1fb; |
|
|
|
|
color: #409eff; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
&:hover, |
|
|
|
|
&.active { |
|
|
|
|
background: #e5f1fb; |
|
|
|
|
color: #409eff; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
::v-deep .el-tree { |
|
|
|
|
background: transparent; |
|
|
|
|
|
|
|
|
|
.el-tree-node__content { |
|
|
|
|
height: 40px; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.el-tree-node:focus > .el-tree-node__content { |
|
|
|
|
background-color: #e5f1fb; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.el-tree-node__content:hover { |
|
|
|
|
background-color: #f0f7ff; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 中间列表 |
|
|
|
|
.addrbook-center { |
|
|
|
|
width: 280px; |
|
|
|
|
border-right: 1px solid #ebeef5; |
|
|
|
|
display: flex; |
|
|
|
|
flex-direction: column; |
|
|
|
|
|
|
|
|
|
.search-box, |
|
|
|
|
.center-header { |
|
|
|
|
display: flex; |
|
|
|
|
align-items: center; |
|
|
|
|
justify-content: space-between; |
|
|
|
|
padding: 12px 16px; |
|
|
|
|
border-bottom: 1px solid #ebeef5; |
|
|
|
|
height: 61px; |
|
|
|
|
} |
|
|
|
|
.center-header { |
|
|
|
|
display: flex; |
|
|
|
|
align-items: center; |
|
|
|
|
justify-content: space-between; |
|
|
|
|
.structure-name { |
|
|
|
|
font-size: 14px; |
|
|
|
|
font-weight: 500; |
|
|
|
|
color: #303133; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.refresh-btn { |
|
|
|
|
font-size: 16px; |
|
|
|
|
color: #909399; |
|
|
|
|
cursor: pointer; |
|
|
|
|
|
|
|
|
|
&:hover { |
|
|
|
|
color: #409eff; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.search-box { |
|
|
|
|
padding: 12px 16px; |
|
|
|
|
border-bottom: 1px solid #ebeef5; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.member-list { |
|
|
|
|
flex: 1; |
|
|
|
|
overflow-y: auto; |
|
|
|
|
|
|
|
|
|
.member-item { |
|
|
|
|
display: flex; |
|
|
|
|
align-items: center; |
|
|
|
|
@ -540,31 +422,23 @@ export default { |
|
|
|
|
padding: 8px 16px; |
|
|
|
|
cursor: pointer; |
|
|
|
|
transition: background 0.2s; |
|
|
|
|
position: relative; |
|
|
|
|
|
|
|
|
|
&:hover { |
|
|
|
|
background: #f5f7fa; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
&.active { |
|
|
|
|
background: #e5f1fb; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
&.offline { |
|
|
|
|
.member-name { |
|
|
|
|
&.offline .member-name { |
|
|
|
|
color: #909399; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.member-name { |
|
|
|
|
font-size: 14px; |
|
|
|
|
color: #303133; |
|
|
|
|
flex: 1; |
|
|
|
|
overflow: hidden; |
|
|
|
|
text-overflow: ellipsis; |
|
|
|
|
white-space: nowrap; |
|
|
|
|
font-size: 14px; |
|
|
|
|
color: #303133; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.online-dot { |
|
|
|
|
width: 8px; |
|
|
|
|
height: 8px; |
|
|
|
|
@ -575,26 +449,22 @@ export default { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 右侧详情 |
|
|
|
|
.addrbook-right { |
|
|
|
|
flex: 1; |
|
|
|
|
background: #fff; |
|
|
|
|
display: flex; |
|
|
|
|
flex-direction: column; |
|
|
|
|
align-items: center; |
|
|
|
|
justify-content: center; |
|
|
|
|
|
|
|
|
|
.detail-panel { |
|
|
|
|
width: 100%; |
|
|
|
|
max-width: 400px; |
|
|
|
|
padding: 40px 24px; |
|
|
|
|
|
|
|
|
|
.avatar-section { |
|
|
|
|
display: flex; |
|
|
|
|
justify-content: center; |
|
|
|
|
margin-bottom: 32px; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.info-section { |
|
|
|
|
.info-row { |
|
|
|
|
display: flex; |
|
|
|
|
@ -609,14 +479,12 @@ export default { |
|
|
|
|
width: 20px; |
|
|
|
|
text-align: center; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.label { |
|
|
|
|
width: 70px; |
|
|
|
|
font-size: 14px; |
|
|
|
|
color: #909399; |
|
|
|
|
flex-shrink: 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.value { |
|
|
|
|
flex: 1; |
|
|
|
|
font-size: 14px; |
|
|
|
|
@ -625,30 +493,25 @@ export default { |
|
|
|
|
text-overflow: ellipsis; |
|
|
|
|
white-space: nowrap; |
|
|
|
|
text-align: right; |
|
|
|
|
|
|
|
|
|
&.online { |
|
|
|
|
color: #67c23a; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
&.offline { |
|
|
|
|
color: #909399; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.action-section { |
|
|
|
|
display: flex; |
|
|
|
|
gap: 16px; |
|
|
|
|
justify-content: center; |
|
|
|
|
margin-top: 32px; |
|
|
|
|
|
|
|
|
|
.el-button { |
|
|
|
|
min-width: 120px; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.empty-detail { |
|
|
|
|
width: 100%; |
|
|
|
|
height: 100%; |
|
|
|
|
|