✨ feat(im): 增加好友申请的逻辑(v1.2:增加相关枚举、字典,减少硬编码)
parent
5b9acb4813
commit
89ee5d51ea
|
|
@ -333,6 +333,8 @@ export enum DICT_TYPE {
|
||||||
IM_GROUP_MESSAGE_STATUS = 'im_group_message_status', // IM 群聊消息状态:0=正常 / 2=已撤回
|
IM_GROUP_MESSAGE_STATUS = 'im_group_message_status', // IM 群聊消息状态:0=正常 / 2=已撤回
|
||||||
IM_GROUP_MESSAGE_RECEIPT_STATUS = 'im_group_message_receipt_status', // IM 群消息回执状态
|
IM_GROUP_MESSAGE_RECEIPT_STATUS = 'im_group_message_receipt_status', // IM 群消息回执状态
|
||||||
IM_FRIEND_STATUS = 'im_friend_status', // IM 好友状态
|
IM_FRIEND_STATUS = 'im_friend_status', // IM 好友状态
|
||||||
|
IM_FRIEND_ADD_SOURCE = 'im_friend_add_source', // IM 好友添加来源
|
||||||
|
IM_FRIEND_REQUEST_HANDLE_RESULT = 'im_friend_request_handle_result', // IM 好友申请处理结果
|
||||||
IM_GROUP_STATUS = 'im_group_status', // IM 群状态
|
IM_GROUP_STATUS = 'im_group_status', // IM 群状态
|
||||||
IM_GROUP_MEMBER_ROLE = 'im_group_member_role' // IM 群成员角色
|
IM_GROUP_MEMBER_ROLE = 'im_group_member_role' // IM 群成员角色
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,14 @@
|
||||||
>
|
>
|
||||||
<Icon :icon="item.icon" :size="22" />
|
<Icon :icon="item.icon" :size="22" />
|
||||||
</el-badge>
|
</el-badge>
|
||||||
|
<el-badge
|
||||||
|
v-else-if="item.name === 'ImHomeContact' && unhandledRequestCount > 0"
|
||||||
|
:value="unhandledRequestCount"
|
||||||
|
:max="99"
|
||||||
|
class="tool-bar__badge"
|
||||||
|
>
|
||||||
|
<Icon :icon="item.icon" :size="22" />
|
||||||
|
</el-badge>
|
||||||
<Icon v-else :icon="item.icon" :size="22" />
|
<Icon v-else :icon="item.icon" :size="22" />
|
||||||
</div>
|
</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
@ -55,6 +63,7 @@ import { useRoute, useRouter } from 'vue-router'
|
||||||
import Icon from '@/components/Icon/src/Icon.vue'
|
import Icon from '@/components/Icon/src/Icon.vue'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
import { useConversationStore } from '../store/conversationStore'
|
import { useConversationStore } from '../store/conversationStore'
|
||||||
|
import { useFriendStore } from '../store/friendStore'
|
||||||
import UserAvatar from './user/UserAvatar.vue'
|
import UserAvatar from './user/UserAvatar.vue'
|
||||||
|
|
||||||
defineOptions({ name: 'ImToolBar' })
|
defineOptions({ name: 'ImToolBar' })
|
||||||
|
|
@ -63,15 +72,15 @@ const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const conversationStore = useConversationStore()
|
const conversationStore = useConversationStore()
|
||||||
|
const friendStore = useFriendStore()
|
||||||
|
|
||||||
/** 消息 Tab 的红点:所有非免打扰会话的未读总和 */
|
const totalUnread = computed(() => conversationStore.getTotalUnread) // 消息 Tab 的红点:所有非免打扰会话的未读总和
|
||||||
const totalUnread = computed(() => conversationStore.getTotalUnread)
|
const unhandledRequestCount = computed(() => friendStore.getUnhandledRequestCount) // 通讯录 Tab 的红点:未处理好友申请数(接收方=我)
|
||||||
|
|
||||||
/** 两个主 Tab;用路由 name 而非 path,避免前缀 / 嵌套调整后失效 */
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ name: 'ImHomeConversation', label: '消息', icon: 'ep:chat-round' },
|
{ name: 'ImHomeConversation', label: '消息', icon: 'ep:chat-round' },
|
||||||
{ name: 'ImHomeContact', label: '通讯录', icon: 'mingcute:contacts-line' }
|
{ name: 'ImHomeContact', label: '通讯录', icon: 'mingcute:contacts-line' }
|
||||||
]
|
] // 两个主 Tab;用路由 name 而非 path,避免前缀 / 嵌套调整后失效
|
||||||
|
|
||||||
/** 当前路由是否命中 Tab:直接比对 route.name */
|
/** 当前路由是否命中 Tab:直接比对 route.name */
|
||||||
const isActive = (name: string) => route.name === name
|
const isActive = (name: string) => route.name === name
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,7 @@ import { useUserStore } from '@/store/modules/user'
|
||||||
import UserAvatar from '../user/UserAvatar.vue'
|
import UserAvatar from '../user/UserAvatar.vue'
|
||||||
import { useFriendStore } from '../../store/friendStore'
|
import { useFriendStore } from '../../store/friendStore'
|
||||||
import { getCurrentUserId } from '../../../utils/storage'
|
import { getCurrentUserId } from '../../../utils/storage'
|
||||||
|
import { ImFriendAddSource } from '../../../utils/constants'
|
||||||
import { getGenderColor, getGenderIcon } from '../../../utils/user'
|
import { getGenderColor, getGenderIcon } from '../../../utils/user'
|
||||||
import { getSimpleUserListByNickname, type UserVO } from '@/api/system/user'
|
import { getSimpleUserListByNickname, type UserVO } from '@/api/system/user'
|
||||||
|
|
||||||
|
|
@ -153,7 +154,7 @@ const props = withDefaults(
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
presetUser: null,
|
presetUser: null,
|
||||||
addSource: 1
|
addSource: ImFriendAddSource.SEARCH
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -226,8 +227,7 @@ function buildPresetApplyContent(): string {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
// 群聊场景拼带群名的话术;其它场景默认「我是 YY」
|
// 群聊场景拼带群名的话术;其它场景默认「我是 YY」
|
||||||
// TODO @AI:使用 addSource 的枚举;
|
const groupExtra = props.addSource === ImFriendAddSource.GROUP ? props.addSourceExtra : ''
|
||||||
const groupExtra = props.addSource === 2 ? props.addSourceExtra : ''
|
|
||||||
return groupExtra ? `我是"${groupExtra}"的${myNickname}` : `我是${myNickname}`
|
return groupExtra ? `我是"${groupExtra}"的${myNickname}` : `我是${myNickname}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
:url="member.avatar"
|
:url="member.avatar"
|
||||||
:clickable="clickable"
|
:clickable="clickable"
|
||||||
:id="member.userId"
|
:id="member.userId"
|
||||||
:add-source="2"
|
:add-source="ImFriendAddSource.GROUP"
|
||||||
:add-source-extra="groupName"
|
:add-source-extra="groupName"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import UserAvatar from '../user/UserAvatar.vue'
|
import UserAvatar from '../user/UserAvatar.vue'
|
||||||
|
import { ImFriendAddSource } from '../../../utils/constants'
|
||||||
|
|
||||||
defineOptions({ name: 'ImGroupMember' })
|
defineOptions({ name: 'ImGroupMember' })
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,13 @@
|
||||||
class="relative flex flex-col items-center px-0.5 py-1"
|
class="relative flex flex-col items-center px-0.5 py-1"
|
||||||
:style="{ width: `${size! + 16}px` }"
|
:style="{ width: `${size! + 16}px` }"
|
||||||
>
|
>
|
||||||
<!-- TODO @AI:add source 增加枚举; -->
|
|
||||||
<UserAvatar
|
<UserAvatar
|
||||||
:id="member.userId"
|
:id="member.userId"
|
||||||
:url="member.avatar"
|
:url="member.avatar"
|
||||||
:name="member.nickname"
|
:name="member.nickname"
|
||||||
:size="size"
|
:size="size"
|
||||||
:clickable="clickable"
|
:clickable="clickable"
|
||||||
:add-source="2"
|
:add-source="ImFriendAddSource.GROUP"
|
||||||
:add-source-extra="groupName"
|
:add-source-extra="groupName"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
|
@ -29,6 +28,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import UserAvatar from '../user/UserAvatar.vue'
|
import UserAvatar from '../user/UserAvatar.vue'
|
||||||
|
import { ImFriendAddSource } from '../../../utils/constants'
|
||||||
import type { GroupMemberLite } from './GroupMember.vue'
|
import type { GroupMemberLite } from './GroupMember.vue'
|
||||||
|
|
||||||
defineOptions({ name: 'ImGroupMemberGrid' })
|
defineOptions({ name: 'ImGroupMemberGrid' })
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import { useImUiStore } from '../../store/uiStore'
|
import { useImUiStore } from '../../store/uiStore'
|
||||||
|
import { ImFriendAddSource } from '../../../utils/constants'
|
||||||
import type { User } from '../../types'
|
import type { User } from '../../types'
|
||||||
|
|
||||||
defineOptions({ name: 'ImUserAvatar', inheritAttrs: false })
|
defineOptions({ name: 'ImUserAvatar', inheritAttrs: false })
|
||||||
|
|
@ -69,7 +70,7 @@ const props = withDefaults(
|
||||||
clickable: true,
|
clickable: true,
|
||||||
previewable: false,
|
previewable: false,
|
||||||
previewZIndex: 2000,
|
previewZIndex: 2000,
|
||||||
addSource: 1 // @AI:是不是枚举下;
|
addSource: ImFriendAddSource.SEARCH
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -295,7 +295,12 @@ function handleChat() {
|
||||||
emit('chat', props.user)
|
emit('chat', props.user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO @AI:添加好友、删除好友,作为一个 ==== 栏目,这样好理解点;
|
/** 占位提示:语音 / 视频聊天能力尚未接入,先以"开发中"友好提示 */
|
||||||
|
function handleComingSoon(featureName: string) {
|
||||||
|
message.info(`${featureName} 功能开发中`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 添加好友 / 删除好友 ====================
|
||||||
|
|
||||||
// 加好友弹窗显隐 + 预填用户(点「加为好友」时把 props.user 传给 FriendAddDialog 跳过搜索)
|
// 加好友弹窗显隐 + 预填用户(点「加为好友」时把 props.user 传给 FriendAddDialog 跳过搜索)
|
||||||
const addFriendVisible = ref(false)
|
const addFriendVisible = ref(false)
|
||||||
|
|
@ -330,11 +335,6 @@ async function handleDeleteFriend() {
|
||||||
message.success('已删除好友')
|
message.success('已删除好友')
|
||||||
emit('deleted', target)
|
emit('deleted', target)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 占位提示:语音 / 视频聊天能力尚未接入,先以"开发中"友好提示 */
|
|
||||||
function handleComingSoon(featureName: string) {
|
|
||||||
message.info(`${featureName} 功能开发中`)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
||||||
|
|
@ -34,39 +34,47 @@
|
||||||
<span class="w-12 flex-shrink-0">来源</span>
|
<span class="w-12 flex-shrink-0">来源</span>
|
||||||
<span class="text-[var(--el-text-color-primary)]">{{ addSourceLabel }}</span>
|
<span class="text-[var(--el-text-color-primary)]">{{ addSourceLabel }}</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- 拒绝理由(拒绝状态展示) -->
|
<!-- 拒绝理由(拒绝状态展示):长文本走 break-words 自动折行,避免横向溢出 -->
|
||||||
<!-- TODO @AI:会折行,拒绝理由; -->
|
<!-- TODO @AI:会折行,拒绝理由; -->
|
||||||
|
<!-- TODO @AI:我指的是: -->
|
||||||
|
<!-- TODO @AI:尽量对齐下微信的样式 /Users/yunai/Downloads/iShot_2026-05-04_10.42.58.png
|
||||||
|
/Users/yunai/Downloads/iShot_2026-05-04_10.42.46.png -->
|
||||||
<div
|
<div
|
||||||
v-if="request.handleResult === 2 && request.handleContent"
|
v-if="request.handleResult === ImFriendRequestHandleResult.REFUSED && request.handleContent"
|
||||||
class="w-full max-w-[420px] mt-3 flex items-start text-13px text-[var(--el-text-color-secondary)]"
|
class="w-full max-w-[420px] mt-3 flex items-start text-13px text-[var(--el-text-color-secondary)]"
|
||||||
>
|
>
|
||||||
<span class="w-12 flex-shrink-0">拒绝理由</span>
|
<span class="w-12 flex-shrink-0">拒绝理由</span>
|
||||||
<span class="text-[var(--el-text-color-primary)]">{{ request.handleContent }}</span>
|
<span class="flex-1 min-w-0 break-words text-[var(--el-text-color-primary)]">
|
||||||
|
{{ request.handleContent }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
<div class="w-full max-w-[420px] mt-8 flex justify-center">
|
<div class="w-full max-w-[420px] mt-8 flex justify-center">
|
||||||
<!-- 我发起 + 等待中:禁用「等待对方验证」 -->
|
<!-- 我发起 + 等待中:禁用「等待对方验证」 -->
|
||||||
<el-button v-if="iSentIt && request.handleResult === 0" disabled>
|
<el-button
|
||||||
|
v-if="iSentIt && request.handleResult === ImFriendRequestHandleResult.UNHANDLED"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
等待对方验证
|
等待对方验证
|
||||||
</el-button>
|
</el-button>
|
||||||
<!-- 别人加我 + 等待中:同意 / 拒绝 -->
|
<!-- 别人加我 + 等待中:同意 / 拒绝 -->
|
||||||
<template v-if="!iSentIt && request.handleResult === 0">
|
<template v-if="!iSentIt && request.handleResult === ImFriendRequestHandleResult.UNHANDLED">
|
||||||
<el-button @click="handleRefuse" :loading="refusing">拒绝</el-button>
|
<el-button @click="handleRefuse" :loading="refusing">拒绝</el-button>
|
||||||
<el-button type="primary" @click="handleAgree" :loading="agreeing">
|
<el-button type="primary" @click="handleAgree" :loading="agreeing"> 同意 </el-button>
|
||||||
同意
|
|
||||||
</el-button>
|
|
||||||
</template>
|
</template>
|
||||||
<!-- 已同意:发消息 -->
|
<!-- 已同意:发消息 -->
|
||||||
<el-button
|
<el-button
|
||||||
v-if="request.handleResult === 1"
|
v-if="request.handleResult === ImFriendRequestHandleResult.AGREED"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="emit('chat', peerUserId)"
|
@click="emit('chat', peerUserId)"
|
||||||
>
|
>
|
||||||
发消息
|
发消息
|
||||||
</el-button>
|
</el-button>
|
||||||
<!-- 已拒绝:占位禁用按钮 -->
|
<!-- 已拒绝:占位禁用按钮 -->
|
||||||
<el-button v-if="request.handleResult === 2" disabled>已拒绝</el-button>
|
<el-button v-if="request.handleResult === ImFriendRequestHandleResult.REFUSED" disabled>
|
||||||
|
已拒绝
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -79,6 +87,8 @@ import { ElMessageBox } from 'element-plus'
|
||||||
import UserAvatar from '../../components/user/UserAvatar.vue'
|
import UserAvatar from '../../components/user/UserAvatar.vue'
|
||||||
import { useFriendStore } from '../../store/friendStore'
|
import { useFriendStore } from '../../store/friendStore'
|
||||||
import { getCurrentUserId } from '../../../utils/storage'
|
import { getCurrentUserId } from '../../../utils/storage'
|
||||||
|
import { ImFriendRequestHandleResult } from '../../../utils/constants'
|
||||||
|
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
||||||
import type { FriendRequest } from '../../types'
|
import type { FriendRequest } from '../../types'
|
||||||
|
|
||||||
defineOptions({ name: 'ImContactFriendRequestDetail' })
|
defineOptions({ name: 'ImContactFriendRequestDetail' })
|
||||||
|
|
@ -112,22 +122,13 @@ const peerAvatar = computed(() =>
|
||||||
iSentIt.value ? props.request.toAvatar : props.request.fromAvatar
|
iSentIt.value ? props.request.toAvatar : props.request.fromAvatar
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 添加来源文案;对齐后端 ImFriendAddSourceEnum:1 搜索 / 2 群聊 / 3 扫码 / 4 名片 */
|
/** 添加来源文案:走字典,对齐后端 ImFriendAddSourceEnum */
|
||||||
// TODO @AI:增加对应字段
|
// TODO @AI:通过 html 里处理掉。不用抽个方法;
|
||||||
const addSourceLabel = computed(() => {
|
const addSourceLabel = computed(() =>
|
||||||
switch (props.request.addSource) {
|
props.request.addSource
|
||||||
case 1:
|
? getDictLabel(DICT_TYPE.IM_FRIEND_ADD_SOURCE, props.request.addSource)
|
||||||
return '通过搜索添加'
|
: ''
|
||||||
case 2:
|
)
|
||||||
return '通过群聊添加'
|
|
||||||
case 3:
|
|
||||||
return '通过扫码添加'
|
|
||||||
case 4:
|
|
||||||
return '通过名片添加'
|
|
||||||
default:
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const agreeing = ref(false)
|
const agreeing = ref(false)
|
||||||
const refusing = ref(false)
|
const refusing = ref(false)
|
||||||
|
|
@ -143,9 +144,9 @@ async function handleAgree() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 拒绝申请 */
|
/** 拒绝申请:弹 prompt 收集可选拒绝理由(点取消则中止),随后调 store 落库 + 提示 */
|
||||||
async function handleRefuse() {
|
async function handleRefuse() {
|
||||||
// TODO @AI:增加注释,参考 system user index.vue 的风格;
|
// 1. 弹 prompt 收集拒绝理由(最多 255 字);用户点「取消」会 reject,中止后续流程
|
||||||
let handleContent: string | undefined
|
let handleContent: string | undefined
|
||||||
try {
|
try {
|
||||||
const result = await ElMessageBox.prompt('可填写拒绝理由(选填)', '拒绝好友申请', {
|
const result = await ElMessageBox.prompt('可填写拒绝理由(选填)', '拒绝好友申请', {
|
||||||
|
|
@ -160,6 +161,7 @@ async function handleRefuse() {
|
||||||
} catch {
|
} catch {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 2. 调 store 拒绝申请;按钮 loading 期间不允许重复点击
|
||||||
refusing.value = true
|
refusing.value = true
|
||||||
try {
|
try {
|
||||||
await friendStore.refuseFriendRequest(props.request.id, handleContent)
|
await friendStore.refuseFriendRequest(props.request.id, handleContent)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
-->
|
-->
|
||||||
<div>
|
<div>
|
||||||
<!-- 折叠分组头 -->
|
<!-- 折叠分组头 -->
|
||||||
<!-- TODO @AI:tool 那,应该因为通讯录(新的朋友)有个计数;未处理数量; -->
|
|
||||||
<div
|
<div
|
||||||
class="flex gap-2 items-center px-3.5 py-2.5 text-15px text-[var(--el-text-color-primary)] cursor-pointer select-none hover:bg-[var(--el-fill-color-light)]"
|
class="flex gap-2 items-center px-3.5 py-2.5 text-15px text-[var(--el-text-color-primary)] cursor-pointer select-none hover:bg-[var(--el-fill-color-light)]"
|
||||||
@click="expanded = !expanded"
|
@click="expanded = !expanded"
|
||||||
|
|
@ -67,6 +66,8 @@ import { computed, ref } from 'vue'
|
||||||
import Icon from '@/components/Icon/src/Icon.vue'
|
import Icon from '@/components/Icon/src/Icon.vue'
|
||||||
import UserAvatar from '../../components/user/UserAvatar.vue'
|
import UserAvatar from '../../components/user/UserAvatar.vue'
|
||||||
import { getCurrentUserId } from '../../../utils/storage'
|
import { getCurrentUserId } from '../../../utils/storage'
|
||||||
|
import { ImFriendRequestHandleResult } from '../../../utils/constants'
|
||||||
|
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
||||||
import type { FriendRequest } from '../../types'
|
import type { FriendRequest } from '../../types'
|
||||||
|
|
||||||
defineOptions({ name: 'ImContactFriendRequestList' })
|
defineOptions({ name: 'ImContactFriendRequestList' })
|
||||||
|
|
@ -81,12 +82,13 @@ const emit = defineEmits<{
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const expanded = ref(true)
|
const expanded = ref(true)
|
||||||
// TODO @AI:是不是不用 Number 处理;
|
const currentUserId = getCurrentUserId()
|
||||||
const currentUserId = Number(getCurrentUserId() || 0)
|
|
||||||
|
|
||||||
/** 未处理 + 别人加我的(接收方=我)才进红点;我发起的不进 */
|
/** 未处理 + 别人加我的(接收方=我)才进红点;我发起的不进 */
|
||||||
const unhandledCount = computed(
|
const unhandledCount = computed(
|
||||||
() => props.requests.filter((r) => r.handleResult === 0 && r.toUserId === currentUserId).length
|
() => props.requests.filter(
|
||||||
|
(r) => r.handleResult === ImFriendRequestHandleResult.UNHANDLED && r.toUserId === currentUserId
|
||||||
|
).length
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 列表项展示对端:fromUserId == 我 → 对端 = toUser;否则对端 = fromUser */
|
/** 列表项展示对端:fromUserId == 我 → 对端 = toUser;否则对端 = fromUser */
|
||||||
|
|
@ -94,27 +96,21 @@ function getPeerUserId(request: FriendRequest): number {
|
||||||
return request.fromUserId === currentUserId ? request.toUserId : request.fromUserId
|
return request.fromUserId === currentUserId ? request.toUserId : request.fromUserId
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO @AI:注释
|
/** 列表项展示对端的昵称(fromUserId == 我 → toUser 昵称;否则 fromUser 昵称;缺则用 id 兜底) */
|
||||||
function getPeerNickname(request: FriendRequest): string {
|
function getPeerNickname(request: FriendRequest): string {
|
||||||
return request.fromUserId === currentUserId
|
return request.fromUserId === currentUserId
|
||||||
? request.toNickname || String(request.toUserId)
|
? request.toNickname || String(request.toUserId)
|
||||||
: request.fromNickname || String(request.fromUserId)
|
: request.fromNickname || String(request.fromUserId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO @AI:注释
|
/** 列表项展示对端的头像(fromUserId == 我 → toUser 头像;否则 fromUser 头像) */
|
||||||
function getPeerAvatar(request: FriendRequest): string | undefined {
|
function getPeerAvatar(request: FriendRequest): string | undefined {
|
||||||
return request.fromUserId === currentUserId ? request.toAvatar : request.fromAvatar
|
return request.fromUserId === currentUserId ? request.toAvatar : request.fromAvatar
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 状态文案:未处理 / 同意 / 拒绝 */
|
/** 状态文案:走字典,对齐后端 ImFriendRequestHandleResultEnum */
|
||||||
// TODO @AI:字典添加下;这里简化掉;
|
// TODO @AI:直接在 html 里,vue 渲染掉;
|
||||||
function statusLabel(request: FriendRequest): string {
|
function statusLabel(request: FriendRequest): string {
|
||||||
if (request.handleResult === 1) {
|
return getDictLabel(DICT_TYPE.IM_FRIEND_REQUEST_HANDLE_RESULT, request.handleResult)
|
||||||
return '已添加'
|
|
||||||
}
|
|
||||||
if (request.handleResult === 2) {
|
|
||||||
return '已拒绝'
|
|
||||||
}
|
|
||||||
return '等待验证'
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,12 @@
|
||||||
<div class="text-13px text-[var(--el-text-color-secondary)]"> {{ memberCount }} 位成员 </div>
|
<div class="text-13px text-[var(--el-text-color-secondary)]"> {{ memberCount }} 位成员 </div>
|
||||||
<!-- 成员宫格:纯展示,宽度跟着 320 容器自动换行;不带"邀请 +"瓦片 -->
|
<!-- 成员宫格:纯展示,宽度跟着 320 容器自动换行;不带"邀请 +"瓦片 -->
|
||||||
<div class="flex flex-wrap gap-2 justify-center w-full pt-2">
|
<div class="flex flex-wrap gap-2 justify-center w-full pt-2">
|
||||||
<!-- TODO @AI:是不是传入 groupname 更合适?不应该给别人看到,我自己自定义的。 -->
|
<!-- 注意!!!加好友话术里的群名一律用 group.name(原始名);showGroupName 是我自定义的群备注,不能带给对端 -->
|
||||||
<GroupMemberGrid
|
<GroupMemberGrid
|
||||||
v-for="member in members"
|
v-for="member in members"
|
||||||
:key="member.userId"
|
:key="member.userId"
|
||||||
:member="member"
|
:member="member"
|
||||||
:group-name="group.showGroupName || group.name"
|
:group-name="group.name"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { defineStore, acceptHMRUpdate } from 'pinia'
|
import { defineStore, acceptHMRUpdate } from 'pinia'
|
||||||
import { reactive } from 'vue'
|
import { reactive } from 'vue'
|
||||||
|
|
||||||
|
import { ImFriendAddSource } from '../../utils/constants'
|
||||||
import type { User } from '../types'
|
import type { User } from '../types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -18,8 +19,8 @@ export const useImUiStore = defineStore('imUiStore', () => {
|
||||||
show: false,
|
show: false,
|
||||||
user: null as User | null,
|
user: null as User | null,
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
// TODO @AI:1 要走枚举
|
// addSource / addSourceExtra 跟随触发点带入「加好友」来源(群成员入口 = GROUP + 群名;其余默认搜索)
|
||||||
addSource: 1 as number, // addSource / addSourceExtra 跟随触发点带入「加好友」来源(群成员入口 = GROUP + 群名;其余默认搜索)
|
addSource: ImFriendAddSource.SEARCH as number,
|
||||||
addSourceExtra: '' as string
|
addSourceExtra: '' as string
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -27,8 +28,7 @@ export const useImUiStore = defineStore('imUiStore', () => {
|
||||||
function openUserInfoCard(
|
function openUserInfoCard(
|
||||||
user: User,
|
user: User,
|
||||||
position: { x: number; y: number },
|
position: { x: number; y: number },
|
||||||
// TODO @AI:1 要走枚举
|
addSource: number = ImFriendAddSource.SEARCH,
|
||||||
addSource: number = 1,
|
|
||||||
addSourceExtra: string = ''
|
addSourceExtra: string = ''
|
||||||
) {
|
) {
|
||||||
const viewportWidth = document.documentElement.clientWidth
|
const viewportWidth = document.documentElement.clientWidth
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,21 @@ export const ImGroupMemberRole = {
|
||||||
NORMAL: 3 // 普通成员
|
NORMAL: 3 // 普通成员
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
/** 好友添加来源(对齐后端 ImFriendAddSourceEnum) */
|
||||||
|
export const ImFriendAddSource = {
|
||||||
|
SEARCH: 1, // 搜索
|
||||||
|
GROUP: 2, // 群聊
|
||||||
|
QR_CODE: 3, // 扫码
|
||||||
|
CARD: 4 // 名片
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/** 好友申请处理结果(对齐后端 ImFriendRequestHandleResultEnum) */
|
||||||
|
export const ImFriendRequestHandleResult = {
|
||||||
|
UNHANDLED: 0, // 未处理
|
||||||
|
AGREED: 1, // 同意
|
||||||
|
REFUSED: 2 // 拒绝
|
||||||
|
} as const
|
||||||
|
|
||||||
/** 群管理员人数上限(对齐后端 GROUP_ADMIN_MAX_COUNT) */
|
/** 群管理员人数上限(对齐后端 GROUP_ADMIN_MAX_COUNT) */
|
||||||
export const GROUP_ADMIN_MAX_COUNT = 3
|
export const GROUP_ADMIN_MAX_COUNT = 3
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue