gaoluyang
2026-06-29 27cd042df9aca0383a49f3514bc21958dd890912
src/views/im/home/pages/conversation/components/input/message-multi-select-bar.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,153 @@
<script lang="ts" setup>
import type { Message } from '#/views/im/home/types'
import { computed, inject } from 'vue'
import { confirm } from '#/packages/effects/common-ui/src'
import { IconifyIcon as Icon } from '#/packages/icons/src'
import { useMessageMultiSelect } from '#/views/im/home/composables/useMessageMultiSelect'
import { useConversationStore } from '#/views/im/home/store/conversationStore'
import { useMessageStore } from '#/views/im/home/store/messageStore'
import { ImForwardMode, isNormalMessage } from '#/views/im/utils/constants'
import { getClientConversationId } from '#/views/im/utils/db'
import { IM_FORWARD_DIALOG_KEY } from '../message/forward/keys'
defineOptions({ name: 'ImMessageMultiSelectBar' })
const conversationStore = useConversationStore()
const messageStore = useMessageStore()
const openForwardDialog = inject(IM_FORWARD_DIALOG_KEY)
const multiSelect = useMessageMultiSelect()
/** é€‰ä¸­æ¡æ•° */
const selectedCount = computed(() => multiSelect.state.selectedClientMessageIds.length)
/** å½“前会话内已选消息 */
function getSelectedMessages(): Message[] {
  const conversation = conversationStore.activeConversation
  if (!conversation) {
    return []
  }
  const ids = multiSelect.selectedIdSet.value
  return messageStore
    .getMessages(getClientConversationId(conversation.type, conversation.targetId))
    .filter((message) => ids.has(message.clientMessageId) && isNormalMessage(message.type))
}
/** é€æ¡è½¬å‘:开 ForwardDialog å•条模式 */
function handleForwardOneByOne() {
  const conversation = conversationStore.activeConversation
  if (!conversation) {
    return
  }
  const messages = getSelectedMessages()
  if (messages.length === 0) {
    return
  }
  openForwardDialog?.({
    mode: ImForwardMode.SINGLE,
    messages,
    sourceConversation: conversation
  })
}
/** åˆå¹¶è½¬å‘:开 ForwardDialog åˆå¹¶æ¨¡å¼ */
function handleForwardMerged() {
  const conversation = conversationStore.activeConversation
  if (!conversation) {
    return
  }
  const messages = getSelectedMessages()
  if (messages.length === 0) {
    return
  }
  openForwardDialog?.({
    mode: ImForwardMode.MERGE,
    messages,
    sourceConversation: conversation
  })
}
/** åˆ é™¤ï¼šå¼¹ç¡®è®¤æ¡† â†’ æœ¬åœ°æ‰¹é‡ç§»é™¤ï¼ˆä¸åŒæ­¥åŽç«¯ï¼Œå¯¹é½å¾®ä¿¡ã€Œåˆ é™¤ã€è¯­ä¹‰ï¼‰ */
async function handleDelete() {
  const conversation = conversationStore.activeConversation
  if (!conversation) {
    return
  }
  const messages = getSelectedMessages()
  if (messages.length === 0) {
    return
  }
  try {
    await confirm(`确认删除选中的 ${messages.length} æ¡æ¶ˆæ¯ï¼Ÿ`, {
      cancelText: '取消',
      confirmText: '确定',
      icon: 'warning',
      title: '删除确认'
    })
  } catch {
    return
  }
  for (const m of messages) {
    messageStore.removeMessage(conversation.type, conversation.targetId, {
      id: m.id,
      clientMessageId: m.clientMessageId
    })
  }
  multiSelect.exit()
}
/** å–消多选 */
function handleCancel() {
  multiSelect.exit()
}
</script>
<template>
  <div
    v-if="multiSelect.state.active"
    class="flex items-center justify-center gap-12 px-5 w-full h-full border-t border-t-solid bg-[var(--ant-color-bg-container)] border-[var(--im-border-color-lighter)]"
  >
    <span
      class="absolute left-5 top-1/2 -translate-y-1/2 text-12px text-[var(--ant-color-text-secondary)]"
    >
      å·²é€‰ {{ selectedCount }} æ¡
    </span>
    <button
      class="inline-flex flex-col items-center gap-1 px-3 py-1 text-12px rounded-md border-0 bg-transparent cursor-pointer transition-colors text-[var(--ant-color-text)] hover:text-[var(--ant-color-primary)] hover:bg-[var(--ant-color-fill)] disabled:text-[var(--ant-color-text-disabled)] disabled:cursor-not-allowed disabled:bg-transparent"
      :disabled="selectedCount === 0"
      @click="handleForwardOneByOne"
    >
      <Icon icon="ant-design:share-alt-outlined" :size="22" />
      <span>逐条转发</span>
    </button>
    <button
      class="inline-flex flex-col items-center gap-1 px-3 py-1 text-12px rounded-md border-0 bg-transparent cursor-pointer transition-colors text-[var(--ant-color-text)] hover:text-[var(--ant-color-primary)] hover:bg-[var(--ant-color-fill)] disabled:text-[var(--ant-color-text-disabled)] disabled:cursor-not-allowed disabled:bg-transparent"
      :disabled="selectedCount === 0"
      @click="handleForwardMerged"
    >
      <Icon icon="tabler:arrow-merge" :size="22" />
      <span>合并转发</span>
    </button>
    <button
      class="inline-flex flex-col items-center gap-1 px-3 py-1 text-12px rounded-md border-0 bg-transparent cursor-pointer transition-colors text-[var(--ant-color-text)] hover:bg-[var(--ant-color-fill)] hover:text-[var(--ant-color-error)] disabled:text-[var(--ant-color-text-disabled)] disabled:cursor-not-allowed disabled:bg-transparent"
      :disabled="selectedCount === 0"
      @click="handleDelete"
    >
      <Icon icon="ant-design:delete-outlined" :size="22" />
      <span>删除</span>
    </button>
    <button
      class="absolute right-5 top-1/2 -translate-y-1/2 inline-flex items-center justify-center w-7 h-7 rounded-full border-0 bg-transparent cursor-pointer transition-colors text-[var(--ant-color-text-secondary)] hover:text-[var(--ant-color-text)] hover:bg-[var(--ant-color-fill)]"
      @click="handleCancel"
    >
      <Icon icon="ant-design:close-outlined" :size="20" />
    </button>
  </div>
</template>