gaoluyang
2026-06-29 27cd042df9aca0383a49f3514bc21958dd890912
src/views/im/home/components/group/group-create-dialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,144 @@
<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>