145 lines
4.2 KiB
Vue
145 lines
4.2 KiB
Vue
<script lang="ts" setup>
|
||
import type { FriendLite } from '../../types'
|
||
|
||
import { computed, ref } from 'vue'
|
||
|
||
import { Button, message, Modal } from 'ant-design-vue'
|
||
|
||
import { createGroup } from '#/api/im/group'
|
||
|
||
import { buildDefaultGroupName } from '../../../utils/group'
|
||
import { useFriendStore } from '../../store/friendStore'
|
||
import { useGroupStore } from '../../store/groupStore'
|
||
import { FriendPickerPanel } from '../picker'
|
||
|
||
defineOptions({ name: 'ImGroupCreateDialog' })
|
||
|
||
const emit = defineEmits<{
|
||
/** 创建成功,携带新群编号;父侧通常用来跳转到新群会话 */
|
||
created: [groupId: number]
|
||
}>()
|
||
|
||
const friendStore = useFriendStore()
|
||
const groupStore = useGroupStore()
|
||
|
||
const visible = ref(false)
|
||
const submitting = ref(false)
|
||
const lockedIds = ref<number[]>([])
|
||
const selectedIds = ref<number[]>([])
|
||
|
||
defineExpose({
|
||
/** 打开发起群聊弹窗:reset → 灌参 → visible=true */
|
||
open(opts?: { lockedIds?: number[] }) {
|
||
lockedIds.value = opts?.lockedIds ? [...opts.lockedIds] : []
|
||
selectedIds.value = []
|
||
submitting.value = false
|
||
visible.value = true
|
||
}
|
||
})
|
||
|
||
/** 全量好友:直接复用 friendStore Lite 视图(带拼音字段供分桶用) */
|
||
const friends = computed<FriendLite[]>(() => friendStore.getActiveFriendLiteList)
|
||
|
||
/** 完成按钮可点:至少有 1 个非 locked 勾选(locked 是入口锁定项,不算"用户主动选择") */
|
||
const canSubmit = computed(() => selectedIds.value.length > 0)
|
||
|
||
/** 拿到所有要进群的好友(locked + selected);建群默认群名按这批人生成 */
|
||
function resolveMembersToInvite(): FriendLite[] {
|
||
const seen = new Set<number>()
|
||
const result: FriendLite[] = []
|
||
const byId = new Map(friends.value.map((f) => [f.id, f]))
|
||
for (const id of lockedIds.value) {
|
||
if (seen.has(id)) {
|
||
continue
|
||
}
|
||
const friend = byId.get(id)
|
||
if (friend) {
|
||
seen.add(id)
|
||
result.push(friend)
|
||
}
|
||
}
|
||
for (const id of selectedIds.value) {
|
||
if (seen.has(id)) {
|
||
continue
|
||
}
|
||
const friend = byId.get(id)
|
||
if (friend) {
|
||
seen.add(id)
|
||
result.push(friend)
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
/** 创建群聊:建群(同时邀请初始成员)→ upsert groupStore → emit created 让父页跳转新会话 */
|
||
async function handleOk() {
|
||
const members = resolveMembersToInvite()
|
||
if (members.length === 0) {
|
||
return
|
||
}
|
||
submitting.value = true
|
||
try {
|
||
const memberUserIds = members.map((m) => m.id)
|
||
const name = buildDefaultGroupName(members)
|
||
const group = await createGroup({ name, memberUserIds, joinApproval: false })
|
||
if (!group?.id) {
|
||
throw new Error('创建群失败:未返回群编号')
|
||
}
|
||
// 直接 upsert 进 groupStore,省一次 fetchGroupList —— 服务端返回 VO 已经够建会话了
|
||
groupStore.upsertGroup({
|
||
id: group.id,
|
||
name: group.name,
|
||
avatar: group.avatar,
|
||
notice: group.notice,
|
||
ownerUserId: group.ownerUserId
|
||
})
|
||
message.success('群聊创建成功')
|
||
emit('created', group.id)
|
||
visible.value = false
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<!--
|
||
发起群聊:选好友 → 默认按所选成员名生成群名 → createGroup
|
||
- dialog 壳本组件持有;选择 UI 委托 FriendPickerPanel
|
||
- lockedIds 由调用方通过 open({ lockedIds }) 传入;私聊侧 +建群锁定对方
|
||
- 不再要求先输入群名 / 不再展示「进群审批」开关,对齐微信 PC
|
||
- 对外接口:ref + open({ lockedIds }) + emit created(groupId)
|
||
-->
|
||
<Modal
|
||
v-model:open="visible"
|
||
title="发起群聊"
|
||
width="720px"
|
||
:mask-closable="false"
|
||
class="im-picker-dialog"
|
||
>
|
||
<div class="h-[480px]">
|
||
<FriendPickerPanel
|
||
v-model:selected-ids="selectedIds"
|
||
:friends="friends"
|
||
:locked-ids="lockedIds"
|
||
/>
|
||
</div>
|
||
|
||
<template #footer>
|
||
<Button @click="visible = false">取消</Button>
|
||
<Button type="primary" :loading="submitting" :disabled="!canSubmit" @click="handleOk">
|
||
完成
|
||
</Button>
|
||
</template>
|
||
</Modal>
|
||
</template>
|
||
|
||
<style scoped lang="scss">
|
||
@use '../picker/picker-dialog' as picker;
|
||
|
||
/* :deep 穿透 el-dialog 内部类;复用 picker 公共 mixin */
|
||
.im-picker-dialog {
|
||
@include picker.styles;
|
||
}
|
||
</style>
|