Skip to content

Commit

Permalink
feat: 新增添加管理员功能 (#106)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kkuil authored Oct 26, 2023
1 parent b594aa7 commit 21aa075
Show file tree
Hide file tree
Showing 13 changed files with 270 additions and 9 deletions.
3 changes: 1 addition & 2 deletions src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ declare module '@vue/runtime-core' {
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBadge: typeof import('element-plus/es')['ElBadge']
ElButton: typeof import('element-plus/es')['ElButton']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
ElInput: typeof import('element-plus/es')['ElInput']
Expand Down
2 changes: 2 additions & 0 deletions src/constant/group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// 最大管理员数量
export const MAX_ADMIN_COUNT = 3
6 changes: 6 additions & 0 deletions src/services/apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,10 @@ export default {
/** 会话详情(联系人列表发消息用) */
sessionDetailWithFriends: (params: { uid: number }) =>
getRequest<SessionItem>(urls.sessionDetailWithFriends, { params }),
/** 添加群管理 */
addAdmin: ({ roomId, uidList }: { roomId: number; uidList: number[] }) =>
putRequest<Boolean>(urls.addAdmin, {
roomId,
uidList,
}),
}
1 change: 1 addition & 0 deletions src/services/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default {
createGroup: `${prefix}/capi/room/group`, // 新增群组
getGroupUserList: `${prefix}/capi/room/public/group/member/page`,
inviteGroupMember: `${prefix}/capi/room/group/member`, // 邀请群成员
addAdmin: `${prefix}/capi/room/group/admin`, // 添加管理员
groupDetail: `${prefix}/capi/room/public/group`, // 群组详情
sessionDetail: `${prefix}/capi/chat/public/contact/detail`, // 会话详情
sessionDetailWithFriends: `${prefix}/capi/chat/public/contact/detail/friend`, // 会话详情(联系人列表发消息用)
Expand Down
11 changes: 10 additions & 1 deletion src/stores/cached.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useGlobalStore } from '@/stores/global'
import type { CacheUserItem, CacheBadgeItem } from '@/services/types'
import { isDiffNow10Min } from '@/utils/computedTime'

type BaseUserItem = Pick<CacheUserItem, 'uid' | 'avatar' | 'name'>
export type BaseUserItem = Pick<CacheUserItem, 'uid' | 'avatar' | 'name'>

export const useCachedStore = defineStore(
'cached',
Expand Down Expand Up @@ -110,6 +110,14 @@ export const useCachedStore = defineStore(
return currentAtUsersList.value?.filter((item) => item.name?.startsWith(searchKey))
}

/**
* 通过用户ID列表获取用户基本信息
* @param uidList
*/
const filterUsersByUidList = (uidList: number[]) => {
return currentAtUsersList.value.filter((user) => uidList.includes(user.uid))
}

return {
userCachedList,
badgeCachedList,
Expand All @@ -119,6 +127,7 @@ export const useCachedStore = defineStore(
filterUsers,
getGroupAtUserBaseInfo,
currentAtUsersList,
filterUsersByUidList,
}
},
{ persist: true },
Expand Down
9 changes: 9 additions & 0 deletions src/stores/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ export const useChatStore = defineStore('chat', () => {
replyMapping.set(currentRoomId.value, val as Map<number, number[]>)
},
})
const isGroup = computed(() => currentRoomType.value === RoomTypeEnum.Group)
/**
* 获取当前会话信息
*/
const currentSessionInfo = computed(() =>
sessionList.find((session) => session.roomId === globalStore.currentSession.roomId),
)

const chatListToBottomAction = ref<() => void>() // 外部提供消息列表滚动到底部事件

Expand Down Expand Up @@ -445,5 +452,7 @@ export const useChatStore = defineStore('chat', () => {
updateSession,
updateSessionLastActiveTime,
markSessionRead,
isGroup,
currentSessionInfo,
}
})
71 changes: 67 additions & 4 deletions src/stores/group.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { ref, reactive, computed } from 'vue'
import { computed, reactive, ref } from 'vue'
import apis from '@/services/apis'
import { defineStore } from 'pinia'
import { useCachedStore } from '@/stores/cached'
import { useGlobalStore } from '@/stores/global'
import type { UserItem, GroupDetailReq } from '@/services/types'
import type { GroupDetailReq, UserItem } from '@/services/types'
import { pageSize } from './chat'
import cloneDeep from 'lodash/cloneDeep'
import { OnlineEnum } from '@/enums'
import { OnlineEnum, RoleEnum } from '@/enums'
import { uniqueUserList } from '@/utils/unique'
import { useCachedStore } from '@/stores/cached'

const sorAction = (pre: UserItem, next: UserItem) => {
if (pre.activeStatus === OnlineEnum.ONLINE && next.activeStatus === OnlineEnum.ONLINE) {
Expand All @@ -30,6 +30,50 @@ export const useGroupStore = defineStore('group', () => {
const userList = ref<UserItem[]>([])
const userListOptions = reactive({ isLast: false, loading: true, cursor: '' })
const currentRoomId = computed(() => globalStore.currentSession.roomId)
/**
* 获取当前群主ID
*/
const currentLordId = computed(() => {
const list = userList.value.filter((member) => member.roleId === RoleEnum.LORD)
if (list.length) {
return list[0]?.uid
}
return -99
})
/**
* 获取当前管理员ID列表
*/
const adminUidList = computed(() => {
return userList.value
.filter((member) => member.roleId === RoleEnum.ADMIN)
.map((member) => member.uid)
})
/**
* 获取管理员基本信息列表
*/
const adminList = computed(() => {
return cachedStore.filterUsersByUidList(adminUidList.value)
})
/**
* 获取管理员基本信息列表
*/
const memberList = computed(() => {
const memberInfoList = cachedStore.filterUsersByUidList(userList.value.map((item) => item.uid))
return memberInfoList.map((member) => {
if (adminUidList.value.includes(member.uid)) {
return {
...member,
roleId: RoleEnum.ADMIN,
}
} else if (member.uid === currentLordId.value) {
return {
...member,
roleId: RoleEnum.LORD,
}
}
return member
})
})
const countInfo = ref<GroupDetailReq>({
avatar: '',
groupName: '',
Expand Down Expand Up @@ -99,15 +143,34 @@ export const useGroupStore = defineStore('group', () => {
userList.value = userList.value.filter((item) => item.uid !== uid)
}

/**
* 添加管理员
* @param uidList
*/
const addAdmin = async (uidList: number[]) => {
await apis.addAdmin({ roomId: currentRoomId.value, uidList }).send()

// 更新群成员列表
userList.value.forEach((user) => {
if (uidList.includes(user.uid)) {
user.roleId = RoleEnum.ADMIN
}
})
}

return {
userList,
userListOptions,
loadMore,
getGroupUserList,
getCountStatistic,
currentLordId,
countInfo,
batchUpdateUserStatus,
showGroupList,
filterUser,
adminList,
memberList,
addAdmin,
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<script setup lang="ts">
import { MAX_ADMIN_COUNT } from '@/constant/group'
import { computed, ref } from 'vue'
import { useGroupStore } from '@/stores/group'
import type { TransferKey } from 'element-plus'
import { ElMessage } from 'element-plus'
import type { BaseUserItem } from '@/stores/cached'
const groupStore = useGroupStore()
const isShowSetAdmin = ref<boolean>(false)
const selectedUidList = ref<number[]>([])
const computedMemberList = computed(() => {
return (groupStore.memberList as (BaseUserItem & { roleId?: number })[]).map((member) => ({
...member,
disabled: !!member.roleId,
}))
})
const selectMember = (checkedUidList: TransferKey[]) => {
if (checkedUidList.length + groupStore.adminList.length > MAX_ADMIN_COUNT) {
ElMessage.error('管理员数量不能超过' + MAX_ADMIN_COUNT + '')
}
}
/**
* 添加管理员
*/
const addAdmin = async () => {
await groupStore.addAdmin(selectedUidList.value)
isShowSetAdmin.value = false
ElMessage.success('添加管理员成功')
}
</script>

<template>
<div class="setting-box">
<el-divider content-position="left">高级</el-divider>
<div class="advanced">
<div class="set-admin">
<h5>
<span>设置管理员</span>
<span class="admin-count">
({{ groupStore.adminList.length }}/{{ MAX_ADMIN_COUNT }})
</span>
</h5>
<div class="flex-center">
<el-button
type="primary"
size="small"
@click="isShowSetAdmin = true"
:disabled="groupStore.adminList.length >= MAX_ADMIN_COUNT"
>
设置管理员
</el-button>
<el-avatar
v-for="admin in groupStore.adminList"
:key="admin.uid"
:src="admin.avatar"
size="small"
class="admin-avatar"
>
<el-icon :size="10">
<Avatar />
</el-icon>
</el-avatar>
<el-dialog v-model="isShowSetAdmin">
<el-transfer
v-model="selectedUidList"
:data="computedMemberList"
:props="{
key: 'uid',
label: 'name',
disabled: 'disabled',
}"
class="add-admin-transfer flex-center"
:titles="['未设置', '已设置']"
@left-check-change="selectMember"
/>
<div class="add-admin-btn flex-center">
<el-button type="primary" @click="addAdmin">添加</el-button>
</div>
</el-dialog>
</div>
</div>
</div>
</div>
</template>

<style scoped lang="scss" src="./styles.scss"></style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.setting-box {
.advanced {
width: 100%;
padding: 10px;
border: 1px solid #777;
border-radius: 5px;

.set-admin {
display: flex;
align-items: center;
justify-content: space-between;

.admin-avatar {
margin: 0 3px;
}

.add-admin-transfer {
.add-admin-btn {
margin-top: 5px;
}
}
}
}
}
35 changes: 35 additions & 0 deletions src/views/Home/Chat/components/ChatList/RoomName/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script setup lang="ts">
import { useChatStore } from '@/stores/chat'
import { ref } from 'vue'
import SettingBox from '@/views/Home/Chat/components/ChatList/RoomName/components/SettingBox/SettingBox.vue'
const chatStore = useChatStore()
const isShowSetting = ref<boolean>(false)
</script>

<template>
<div class="room-name">
<span class="name">{{ chatStore.currentSessionInfo?.name }}</span>
<span
class="setting"
@click="isShowSetting = true"
v-if="chatStore.isGroup && !chatStore.currentSessionInfo?.hot_Flag"
>
设置
</span>
</div>
<el-drawer v-model="isShowSetting" direction="rtl" append-to-body>
<template #header>
<h4 class="text-[#f5f5f5]">设置</h4>
</template>
<template #default>
<SettingBox />
</template>
</el-drawer>
</template>

<style lang="scss" src="./styles.scss">
.el-drawer__header {
margin-bottom: 0 !important;
}
</style>
18 changes: 18 additions & 0 deletions src/views/Home/Chat/components/ChatList/RoomName/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.room-name {
display: flex;
align-items: center;
justify-content: space-between;
height: 50px;
padding: 0 10px;
background: var(--background-mask);
border-radius: 8px 8px 0 0;

.setting {
font-size: 13px;
cursor: pointer;

&:hover {
color: var(--hover-primary);
}
}
}
7 changes: 5 additions & 2 deletions src/views/Home/Chat/components/ChatList/index.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<script setup lang="ts">
import { ref, onMounted, nextTick, provide, computed } from 'vue'
import { computed, nextTick, onMounted, provide, ref } from 'vue'
import throttle from 'lodash/throttle'
import { useChatStore } from '@/stores/chat'
import type { MessageType } from '@/services/types'
import VirtualList from '@/components/VirtualList'
import MsgItem from './MsgItem/index.vue'
import RoomName from './RoomName/index.vue'
const chatStore = useChatStore()
const virtualListRef = ref()
Expand Down Expand Up @@ -69,8 +70,10 @@ const getKey = (item: MessageType) => item.message.id

<template>
<div class="chat-msg-list" @contextmenu.prevent>
<RoomName />
<el-icon v-if="messageOptions?.isLoading" :size="14" class="loading">
<IEpLoading />消息加载中
<IEpLoading />
消息加载中
</el-icon>
<VirtualList
v-if="chatMessageList?.length"
Expand Down
1 change: 1 addition & 0 deletions src/views/Home/Chat/components/ChatList/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

.loading {
position: absolute;
top: 50px;
z-index: 20;
gap: 4px;
width: 100%;
Expand Down

0 comments on commit 21aa075

Please sign in to comment.