feat(im): 优化群聊的功能界面

im
YunaiV 2026-04-30 16:59:56 +08:00
parent 368b385267
commit 4b4c4fab11
8 changed files with 305 additions and 176 deletions

View File

@ -11,6 +11,9 @@
@click="$emit('click', friend)"
@contextmenu.prevent="handleContextMenu"
>
<!-- prefix slot放在头像前给选择类弹窗的 checkbox / 圆点用不传则不渲染 -->
<slot name="prefix"></slot>
<!-- 头像 -->
<UserAvatar
:id="friend.id"
:url="friend.avatar"
@ -18,8 +21,8 @@
:size="42"
:clickable="false"
/>
<!-- 单行展示 displayName 优先昵称仅在好友详情面板展示列表里不重复 -->
<div class="flex flex-1 min-w-0">
<!-- 单行展示 displayName 优先昵称仅在好友详情面板展示列表里不重复 -->
<div class="overflow-hidden text-sm truncate text-[var(--el-text-color-primary)]">
{{ friend.displayName || friend.nickname }}
</div>
@ -55,12 +58,13 @@ const emit = defineEmits<{
const uiStore = useImUiStore()
function handleContextMenu(e: MouseEvent) {
/** 右键菜单:发送消息 / 删除好友 */
function handleContextMenu(event: MouseEvent) {
if (!props.menu) {
return
}
uiStore.openContextMenu(
{ x: e.clientX, y: e.clientY },
{ x: event.clientX, y: event.clientY },
[
{ key: 'chat', name: '发送消息' },
{ key: 'delete', name: '删除好友' }

View File

@ -2,9 +2,10 @@
<!--
新建群聊对话框
- 顶部群名称输入
- 好友列表checkbox
- 已勾选预览
- 好友列表checkbox 前置
- 已勾选预览每行可 x 移除locked 不渲染 x
- 提交 createGroup inviteGroupMember最后让父页 reload
- lockedIds锁定不可取消的好友 id私聊侧 "+创建群" 入口用来锁定对方
-->
<el-dialog v-model="visible" title="新建群聊" width="620px" :close-on-click-modal="false">
<div class="flex flex-col gap-3">
@ -14,25 +15,31 @@
<div
class="flex flex-col flex-1 overflow-hidden rounded border border-[var(--el-border-color)]"
>
<el-input v-model="searchText" placeholder="搜索好友" size="small" clearable>
<template #suffix>
<Icon icon="ant-design:search-outlined" />
<el-input v-model="searchText" placeholder="搜索好友" clearable>
<template #prefix>
<Icon
icon="ant-design:search-outlined"
class="text-[var(--el-text-color-placeholder)]"
/>
</template>
</el-input>
<el-scrollbar class="h-[400px]">
<FriendItem
v-for="f in shownFriends"
:key="f.id"
:friend="f"
v-for="friend in shownFriends"
:key="friend.id"
:friend="friend"
:menu="false"
:active="false"
@click="handleToggleCheck(f)"
@click="handleToggleCheck(friend)"
>
<el-checkbox
:model-value="f.isCheck"
@click.stop
@change="(v) => (f.isCheck = !!v)"
/>
<template #prefix>
<el-checkbox
:model-value="friend.checked"
:disabled="friend.disabled"
@click.stop
@change="(value) => handleCheckChange(friend, !!value)"
/>
</template>
</FriendItem>
</el-scrollbar>
</div>
@ -44,27 +51,38 @@
<div
class="flex flex-col flex-1 overflow-hidden rounded border border-[var(--el-border-color)]"
>
<!-- 标题高度对齐左侧 el-input default32px保证两侧第一项起点在同一水平 -->
<div
class="h-10 pl-2.5 leading-10 text-13px text-[var(--el-text-color-secondary)] border-b border-[var(--el-border-color-lighter)]"
class="h-8 pl-2.5 leading-8 text-13px text-[var(--el-text-color-secondary)] border-b border-[var(--el-border-color-lighter)]"
>
已勾选 {{ checkedFriends.length }} 位好友
</div>
<el-scrollbar class="h-[400px]">
<FriendItem
v-for="f in checkedFriends"
:key="f.id"
:friend="f"
v-for="friend in checkedFriends"
:key="friend.id"
:friend="friend"
:menu="false"
:active="false"
/>
>
<!-- locked 的好友不渲染 x避免误以为可移除 -->
<Icon
v-if="!friend.disabled"
icon="ant-design:close-outlined"
class="im-group-create-dialog__remove"
@click.stop="handleUncheck(friend)"
/>
</FriendItem>
</el-scrollbar>
</div>
</div>
</div>
<template #footer>
<el-button @click="visible = false"> </el-button>
<el-button type="primary" :loading="submitting" @click="handleOk"> </el-button>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" :loading="submitting" :disabled="!canSubmit" @click="handleOk">
完成
</el-button>
</template>
</el-dialog>
</template>
@ -83,16 +101,19 @@ import type { FriendLite } from '../../types'
defineOptions({ name: 'ImGroupCreateDialog' })
interface FriendCheckable extends FriendLite {
isCheck?: boolean
checked?: boolean
disabled?: boolean // locked 的好友:勾选态由 lockedIds 强制为 trueUI 上 checkbox / x 都不响应
}
const props = withDefaults(
defineProps<{
modelValue: boolean
friends?: FriendLite[] // friendStore
lockedIds?: number[] // id + "+"
}>(),
{
friends: () => []
friends: () => [],
lockedIds: () => []
}
)
@ -113,47 +134,73 @@ const visible = computed({
const groupName = ref('')
const searchText = ref('')
const submitting = ref(false)
// TODO @AIchecked
const workingFriends = ref<FriendCheckable[]>([]) // isCheck prop
const workingFriends = ref<FriendCheckable[]>([]) // 工作副本(带 checked / disabled 标记 prop 隔离
watch(
visible,
(v) => {
if (!v) {
(open) => {
if (!open) {
return
}
groupName.value = ''
searchText.value = ''
workingFriends.value = props.friends
.filter((f) => !f.deleted)
.map((f) => ({ ...f, isCheck: false }))
.filter((friend) => !friend.deleted)
.map((friend) => {
const locked = props.lockedIds.some((id) => id === friend.id)
return { ...friend, checked: locked, disabled: locked }
})
},
{ immediate: true }
)
/** 左侧展示的好友:按搜索关键字过滤 workingFriends */
const shownFriends = computed(() =>
workingFriends.value.filter((f) => f.nickname.includes(searchText.value))
workingFriends.value.filter((friend) => friend.nickname.includes(searchText.value))
)
/** 已勾选的好友:右侧预览 + 提交时取 memberUserIds */
const checkedFriends = computed(() => workingFriends.value.filter((f) => f.isCheck))
const checkedFriends = computed(() => workingFriends.value.filter((friend) => friend.checked))
/** 行点击:切换勾选态,让点击整行与点 checkbox 等价 */
function handleToggleCheck(f: FriendCheckable) {
f.isCheck = !f.isCheck
/**
* 完成按钮可点群名非空 + 至少有 1 个非 locked 勾选
*
* locked 是入口侧自动选的如私聊对方不算"用户主动选择"否则用户什么都没勾就能建 2 人群体验上等于私聊
*/
const canSubmit = computed(() => {
if (!groupName.value.trim()) {
return false
}
return checkedFriends.value.some((friend) => !friend.disabled)
})
/** 行点击切换勾选态locked 的不响应 */
function handleToggleCheck(friend: FriendCheckable) {
if (friend.disabled) {
return
}
friend.checked = !friend.checked
}
/** checkbox change直接落 valuelocked 已由 :disabled 拦截,这里再守一层) */
function handleCheckChange(friend: FriendCheckable, value: boolean) {
if (friend.disabled) {
return
}
friend.checked = value
}
/** 右侧 x 点击取消勾选locked 不渲染 x到这里说明非 locked */
function handleUncheck(friend: FriendCheckable) {
friend.checked = false
}
/** 创建群聊:建群 → 拉人 → upsert groupStore最后 emit('created') 让父页跳转新会话 */
async function handleOk() {
const name = groupName.value.trim()
if (!name) {
message.warning('请输入群名称')
return
}
const memberUserIds = checkedFriends.value.map((f) => f.id)
if (memberUserIds.length === 0) {
message.warning('请至少选择一位好友')
const memberUserIds = checkedFriends.value.map((friend) => friend.id)
// canSubmit disabled
if (!name || memberUserIds.length === 0) {
return
}
submitting.value = true
@ -165,6 +212,7 @@ async function handleOk() {
}
// 1.2
await inviteGroupMember({ groupId: group.id, memberUserIds })
// 2.1 upsert groupStore fetchGroups VO
groupStore.upsertGroup({
id: group.id,
@ -182,3 +230,16 @@ async function handleOk() {
}
}
</script>
<style scoped>
/* 右侧已选行的 x默认浅灰hover 转危险色,提示"点了就移除" */
.im-group-create-dialog__remove {
font-size: 14px;
color: var(--el-text-color-placeholder);
cursor: pointer;
transition: color 0.15s;
}
.im-group-create-dialog__remove:hover {
color: var(--el-color-danger);
}
</style>

View File

@ -1,38 +1,40 @@
<template>
<!--
邀请好友入群对话框
- 好友列表 checkbox
- 已勾选预览
- 已在群内的好友标记为 disabled
- TODO 接入 /im/group/inviteTODO 这个是不是已经接入了
- 好友列表checkbox 前置
- 已勾选预览每行可 x 移除
- 已在群内的好友 disabled复用 GroupCreateDialog 的视觉
-->
<el-dialog v-model="visible" title="邀请好友" width="620px" :close-on-click-modal="false">
<div class="flex gap-2.5">
<div
class="flex flex-col flex-1 overflow-hidden rounded border border-[var(--el-border-color)]"
>
<el-input v-model="searchText" placeholder="搜索好友" size="small" clearable>
<template #suffix>
<Icon icon="ant-design:search-outlined" />
<el-input v-model="searchText" placeholder="搜索好友" clearable>
<template #prefix>
<Icon
icon="ant-design:search-outlined"
class="text-[var(--el-text-color-placeholder)]"
/>
</template>
</el-input>
<el-scrollbar class="h-[400px]">
<!-- TODO @ai: friend? -->
<FriendItem
v-for="f in shownFriends"
:key="f.id"
:friend="f"
v-for="friend in shownFriends"
:key="friend.id"
:friend="friend"
:menu="false"
:active="false"
@click="handleToggleCheck(f)"
@click="handleToggleCheck(friend)"
>
<!-- TODO @AIchecked -->
<el-checkbox
:model-value="f.isCheck"
:disabled="f.disabled"
@click.stop
@change="(v) => (f.isCheck = !!v)"
/>
<template #prefix>
<el-checkbox
:model-value="friend.checked"
:disabled="friend.disabled"
@click.stop
@change="(value) => handleCheckChange(friend, !!value)"
/>
</template>
</FriendItem>
</el-scrollbar>
</div>
@ -44,26 +46,35 @@
<div
class="flex flex-col flex-1 overflow-hidden rounded border border-[var(--el-border-color)]"
>
<!-- 标题高度对齐左侧 el-input default32px保证两侧第一项起点在同一水平 -->
<div
class="h-10 pl-2.5 leading-10 text-13px text-[var(--el-text-color-secondary)] border-b border-[var(--el-border-color-lighter)]"
class="h-8 pl-2.5 leading-8 text-13px text-[var(--el-text-color-secondary)] border-b border-[var(--el-border-color-lighter)]"
>
已勾选 {{ checkCount }} 位好友
已勾选 {{ checkedFriends.length }} 位好友
</div>
<el-scrollbar class="h-[400px]">
<FriendItem
v-for="f in checkedFriends"
:key="f.id"
:friend="f"
v-for="friend in checkedFriends"
:key="friend.id"
:friend="friend"
:menu="false"
:active="false"
/>
>
<Icon
icon="ant-design:close-outlined"
class="im-group-member-add-dialog__remove"
@click.stop="handleUncheck(friend)"
/>
</FriendItem>
</el-scrollbar>
</div>
</div>
<template #footer>
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="handleOk"> </el-button>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" :loading="submitting" :disabled="!canSubmit" @click="handleOk">
完成
</el-button>
</template>
</el-dialog>
</template>
@ -74,6 +85,7 @@ import Icon from '@/components/Icon/src/Icon.vue'
import { useMessage } from '@/hooks/web/useMessage'
import { CommonStatusEnum } from '@/utils/constants'
import { inviteGroupMember } from '@/api/im/group/member'
import FriendItem from '../friend/FriendItem.vue'
import type { FriendLite } from '../../types'
import type { GroupMemberLite } from './GroupMember.vue'
@ -81,8 +93,8 @@ import type { GroupMemberLite } from './GroupMember.vue'
defineOptions({ name: 'ImGroupMemberAddDialog' })
interface FriendCheckable extends FriendLite {
isCheck?: boolean
disabled?: boolean
checked?: boolean
disabled?: boolean // checkbox +
}
const props = withDefaults(
@ -112,15 +124,16 @@ const visible = computed({
})
const searchText = ref('')
/** 本地工作副本(带 isCheck / disabled 标记);和 props.friends 区分以避免直接改 prop */
const workingFriends = ref<FriendCheckable[]>([])
const submitting = ref(false)
const workingFriends = ref<FriendCheckable[]>([]) // 工作副本(带 checked / disabled 标记 prop 隔离
watch(
visible,
(v) => {
if (!v) {
(open) => {
if (!open) {
return
}
searchText.value = ''
workingFriends.value = props.friends
.filter((friend) => !friend.deleted)
.map((friend) => {
@ -129,8 +142,8 @@ watch(
)
return {
...friend,
disabled: inGroup,
isCheck: inGroup
checked: inGroup,
disabled: inGroup
}
})
},
@ -139,36 +152,68 @@ watch(
/** 左侧展示的好友:按搜索关键字过滤 workingFriends */
const shownFriends = computed(() =>
workingFriends.value.filter((f) => f.nickname.includes(searchText.value))
workingFriends.value.filter((friend) => friend.nickname.includes(searchText.value))
)
/** 本次将被邀请的好友:勾选 + 非已在群成员disabled 不计入) */
const checkedFriends = computed(() => workingFriends.value.filter((f) => f.isCheck && !f.disabled))
const checkedFriends = computed(() =>
workingFriends.value.filter((friend) => friend.checked && !friend.disabled)
)
/** 已勾选数量:右侧标题展示 */
const checkCount = computed(() => checkedFriends.value.length)
/** 完成按钮可点:至少有 1 个新邀请的好友 */
const canSubmit = computed(() => checkedFriends.value.length > 0)
/** 行点击切换勾选态已在群disabled的不响应 */
function handleToggleCheck(f: FriendCheckable) {
if (!f.disabled) {
f.isCheck = !f.isCheck
}
}
// TODO @AI
/**
* 邀请入群占位实现 reload 让父侧关弹窗 / 刷新
*
* TODO 接入 /im/group/invite
*/
async function handleOk() {
const ids = checkedFriends.value.map((f) => f.id)
if (ids.length === 0) {
message.warning('请选择至少一个好友')
function handleToggleCheck(friend: FriendCheckable) {
if (friend.disabled) {
return
}
message.info('邀请入群接口待接入,当前为占位实现')
emit('reload', ids)
visible.value = false
friend.checked = !friend.checked
}
/** checkbox change直接落 valuedisabled 已由属性拦截,这里再守一层) */
function handleCheckChange(friend: FriendCheckable, value: boolean) {
if (friend.disabled) {
return
}
friend.checked = value
}
/** 右侧 x 点击取消勾选disabled 不会进右侧列表,到这里说明非 disabled */
function handleUncheck(friend: FriendCheckable) {
friend.checked = false
}
/** 邀请入群:调 /im/group/invite成功后 emit reload 让父侧刷新群成员 */
async function handleOk() {
if (!props.groupId) {
return
}
const memberUserIds = checkedFriends.value.map((friend) => friend.id)
if (memberUserIds.length === 0) {
return
}
submitting.value = true
try {
await inviteGroupMember({ groupId: props.groupId, memberUserIds })
message.success('邀请成功')
emit('reload', memberUserIds)
visible.value = false
} finally {
submitting.value = false
}
}
</script>
<style scoped>
/* 右侧已选行的 x默认浅灰hover 转危险色,提示"点了就移除" */
.im-group-member-add-dialog__remove {
font-size: 14px;
color: var(--el-text-color-placeholder);
cursor: pointer;
transition: color 0.15s;
}
.im-group-member-add-dialog__remove:hover {
color: var(--el-color-danger);
}
</style>

View File

@ -1,8 +1,9 @@
<template>
<!--
私聊侧边抽屉
- 整体结构对齐 ConversationGroupSide宫格 + 信息行 + 开关
- "添加 / 清空聊天记录" 按钮在 WeChat 里有但目前后端没建群-from-私聊 / 清空消息能力先不加避免做半吊子
- 整体结构对齐 ConversationGroupSide宫格 + 信息行 + 开关
- 顶部好友宫格 + "+" tile + 调起 GroupCreateDialog 并锁定对方对齐微信"基于私聊发起群聊"
- "清空聊天记录"按钮在 WeChat 里有但目前后端没建消息清空能力先不加避免做半吊子
-->
<el-drawer
v-model="visible"
@ -14,7 +15,7 @@
>
<div v-if="friend" class="im-conversation-private-side flex flex-col h-full">
<div class="im-conversation-private-side__scroll flex-1 overflow-y-auto">
<!-- 好友宫格单个头像 tile对齐 GroupSide 视觉让两种抽屉看起来是一家的 -->
<!-- 好友宫格tile + "+" tile对齐 GroupSide 视觉让两种抽屉看起来是一家的 -->
<div class="im-conversation-private-side__section im-conversation-private-side__friend">
<div class="im-conversation-private-side__tile-wrap">
<UserAvatar
@ -28,6 +29,18 @@
{{ displayName }}
</div>
</div>
<!-- + tile点击调起 GroupCreateDialog把对方 id 作为 lockedIds 传入 -->
<div
class="im-conversation-private-side__tile-wrap im-conversation-private-side__tile-wrap--clickable"
title="发起群聊"
@click="createGroupVisible = true"
>
<div class="im-conversation-private-side__icon-tile">
<Icon icon="ant-design:plus-outlined" />
</div>
<div class="im-conversation-private-side__tile-label">添加</div>
</div>
</div>
<div class="im-conversation-private-side__spacer"></div>
@ -105,6 +118,14 @@
</div>
</div>
</div>
<!-- 子对话框发起群聊锁定对方为已选 -->
<GroupCreateDialog
v-model="createGroupVisible"
:friends="friends"
:locked-ids="lockedIds"
@created="handleGroupCreated"
/>
</el-drawer>
</template>
@ -112,13 +133,15 @@
import { computed, ref, watch } from 'vue'
import Icon from '@/components/Icon/src/Icon.vue'
import UserAvatar from '../../../../components/user/UserAvatar.vue'
import GroupCreateDialog from '../../../../components/group/GroupCreateDialog.vue'
import { useMessage } from '@/hooks/web/useMessage'
import { useConversationStore } from '@/views/im/home/store/conversationStore'
import { useFriendStore } from '@/views/im/home/store/friendStore'
import { useGroupStore } from '@/views/im/home/store/groupStore'
import { getFriendDisplayName } from '@/views/im/utils/user'
import { ImConversationType } from '@/views/im/utils/constants'
import type { Conversation, Friend } from '../../../../types'
import type { Conversation, Friend, FriendLite } from '../../../../types'
defineOptions({ name: 'ImConversationPrivateSide' })
@ -127,9 +150,11 @@ const props = withDefaults(
modelValue?: boolean // v-model
conversation?: Conversation | null // 当前会话(取置顶 / 免打扰态
friend?: Friend // 对方好友信息(取头像 / 昵称
friends?: FriendLite[] // "+" GroupCreateDialog
}>(),
{
modelValue: false
modelValue: false,
friends: () => []
}
)
@ -145,13 +170,20 @@ const visible = computed({
const conversationStore = useConversationStore()
const friendStore = useFriendStore()
const groupStore = useGroupStore()
const message = useMessage()
/** tile 标签 / 后续聊天界面用的展示名:备注优先 */
const displayName = computed(() => (props.friend ? getFriendDisplayName(props.friend) : ''))
/** GroupCreateDialog 锁定 id把对方默认勾上且不可取消对应微信"基于私聊发起群聊" */
const lockedIds = computed<number[]>(() =>
props.friend ? [props.friend.friendUserId] : []
)
const displayNamePopoverVisible = ref(false)
const editDisplayName = ref('')
const createGroupVisible = ref(false)
// popover
watch(displayNamePopoverVisible, (open) => {
@ -190,8 +222,8 @@ function handleMutedChange(value: boolean | string | number) {
if (type !== ImConversationType.PRIVATE) {
return
}
friendStore.setMuted(targetId, next).catch((e) => {
console.error('[IM ConversationPrivateSide] 切换免打扰失败', { targetId }, e)
friendStore.setMuted(targetId, next).catch((error) => {
console.error('[IM ConversationPrivateSide] 切换免打扰失败', { targetId }, error)
message.error('切换免打扰失败')
conversationStore.setMuted(type, targetId, !next)
})
@ -204,6 +236,22 @@ function handleTopChange(value: boolean | string | number) {
}
conversationStore.setTop(props.conversation.type, props.conversation.targetId, !!value)
}
/** 群创建成功:跳到新群会话 + 关掉本侧抽屉,让用户专注新群 */
function handleGroupCreated(groupId: number) {
const group = groupStore.getGroup(groupId)
if (!group) {
return
}
conversationStore.openConversation(
groupId,
ImConversationType.GROUP,
group.name,
group.avatar || '',
{ muted: !!group.muted }
)
visible.value = false
}
</script>
<style scoped>
@ -220,8 +268,11 @@ function handleTopChange(value: boolean | string | number) {
background-color: var(--el-bg-color);
}
/* 好友宫格区:留白和 GroupSide__members 对齐,单 tile 居左展示 */
/* 好友宫格区:留白和 GroupSide__members 对齐,friend tile + "+" tile 横排 */
.im-conversation-private-side__friend {
display: flex;
flex-wrap: wrap;
gap: 4px;
padding: 16px 16px 14px;
}
@ -231,6 +282,9 @@ function handleTopChange(value: boolean | string | number) {
align-items: center;
width: 66px;
}
.im-conversation-private-side__tile-wrap--clickable {
cursor: pointer;
}
.im-conversation-private-side__tile-label {
width: 100%;
@ -244,6 +298,33 @@ function handleTopChange(value: boolean | string | number) {
white-space: nowrap;
}
/* "+" 加号 tile浅底 + 虚线边hover 走主色让交互可读,与 GroupSide 一致 */
.im-conversation-private-side__icon-tile {
display: flex;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
font-size: 20px;
color: var(--el-text-color-regular);
background-color: var(--el-fill-color-lighter);
border: 1px dashed var(--el-border-color);
border-radius: 6px;
transition:
color 0.18s,
border-color 0.18s,
background-color 0.18s;
}
.im-conversation-private-side__tile-wrap--clickable:hover .im-conversation-private-side__icon-tile {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9);
}
/* el-icon 全局 color 在暗色模式下被主题盖过;:deep(svg) 锁 fill 到当前色 */
.im-conversation-private-side__icon-tile :deep(svg) {
fill: currentColor !important;
}
/* section 间隔条 */
.im-conversation-private-side__spacer {
flex-shrink: 0;

View File

@ -84,7 +84,7 @@
:group="groupInfo"
:conversation="conversationStore.activeConversation"
:members="groupMembers"
:friends="groupFriends"
:friends="friends"
@reload="reloadGroupData"
@open-history="historyVisible = true"
/>
@ -93,6 +93,7 @@
v-model="sideVisible"
:conversation="conversationStore.activeConversation"
:friend="privateFriend"
:friends="friends"
@open-history="historyVisible = true"
/>
@ -201,8 +202,8 @@ const groupMembers = computed<GroupMemberLite[]>(() => {
})
})
/** 好友列表(用于"邀请入群"对话框):把 friendStore 的全量好友 map 成 FriendLite 窄接口 */
const groupFriends = computed<FriendLite[]>(() =>
/** 好友列表:群侧用于"邀请入群",私聊侧用于"+创建群",统一从 friendStore 映射成 FriendLite 窄接口 */
const friends = computed<FriendLite[]>(() =>
friendStore.getActiveFriends.map((friend) => ({
id: friend.friendUserId,
nickname: friend.nickname,

View File

@ -1,21 +0,0 @@
<template>
<ContentWrap>
<!-- TODO @芋艿这个只是临时的 -->
<div class="im-manager-placeholder">我是好友管理测试界面</div>
</ContentWrap>
</template>
<script lang="ts" setup>
defineOptions({ name: 'ImManagerFriend' })
</script>
<style scoped>
.im-manager-placeholder {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
font-size: 18px;
color: #606266;
}
</style>

View File

@ -1,21 +0,0 @@
<template>
<ContentWrap>
<!-- TODO @芋艿这个只是临时的 -->
<div class="im-manager-placeholder">我是群管理测试界面</div>
</ContentWrap>
</template>
<script lang="ts" setup>
defineOptions({ name: 'ImManagerGroup' })
</script>
<style scoped>
.im-manager-placeholder {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
font-size: 18px;
color: #606266;
}
</style>

View File

@ -1,21 +0,0 @@
<template>
<ContentWrap>
<!-- TODO @芋艿这个只是临时的 -->
<div class="im-manager-placeholder">我是消息管理测试界面</div>
</ContentWrap>
</template>
<script lang="ts" setup>
defineOptions({ name: 'ImManagerMessage' })
</script>
<style scoped>
.im-manager-placeholder {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
font-size: 18px;
color: #606266;
}
</style>