diff --git a/src/views/im/home/components/friend/FriendItem.vue b/src/views/im/home/components/friend/FriendItem.vue index 96e7f00f6..7fdd85b76 100644 --- a/src/views/im/home/components/friend/FriendItem.vue +++ b/src/views/im/home/components/friend/FriendItem.vue @@ -11,6 +11,9 @@ @click="$emit('click', friend)" @contextmenu.prevent="handleContextMenu" > + + + +
-
{{ friend.displayName || friend.nickname }}
@@ -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: '删除好友' } diff --git a/src/views/im/home/components/group/GroupCreateDialog.vue b/src/views/im/home/components/group/GroupCreateDialog.vue index f084a835c..89eb47920 100644 --- a/src/views/im/home/components/group/GroupCreateDialog.vue +++ b/src/views/im/home/components/group/GroupCreateDialog.vue @@ -2,9 +2,10 @@
@@ -14,25 +15,31 @@
- - @@ -83,16 +101,19 @@ import type { FriendLite } from '../../types' defineOptions({ name: 'ImGroupCreateDialog' }) interface FriendCheckable extends FriendLite { - isCheck?: boolean + checked?: boolean + disabled?: boolean // locked 的好友:勾选态由 lockedIds 强制为 true,UI 上 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 @AI:checked 改成这个变量; -const workingFriends = ref([]) // 工作副本(带 isCheck 标记),与 prop 隔离 +const workingFriends = ref([]) // 工作副本(带 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:直接落 value(locked 已由 :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() { } } + + diff --git a/src/views/im/home/components/group/GroupMemberAddDialog.vue b/src/views/im/home/components/group/GroupMemberAddDialog.vue index b092f5d6f..59cf1bdbd 100644 --- a/src/views/im/home/components/group/GroupMemberAddDialog.vue +++ b/src/views/im/home/components/group/GroupMemberAddDialog.vue @@ -1,38 +1,40 @@