From 787ccc59ba89bacc075562a161ecf02bc76ebadc Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 24 七月 2026 17:15:53 +0800
Subject: [PATCH] 银川 1.采购申请添加入库状态查询条件
---
src/views/im/home/components/user/recommend-card-dialog.vue | 322 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 322 insertions(+), 0 deletions(-)
diff --git a/src/views/im/home/components/user/recommend-card-dialog.vue b/src/views/im/home/components/user/recommend-card-dialog.vue
new file mode 100644
index 0000000..8c74cb1
--- /dev/null
+++ b/src/views/im/home/components/user/recommend-card-dialog.vue
@@ -0,0 +1,322 @@
+<script lang="ts" setup>
+import type { Conversation, FriendLite } from '../../types'
+
+import { computed, ref } from 'vue'
+
+import { IconifyIcon as Icon } from '#/packages/icons/src'
+
+import { Button, Input, message, Modal } from 'ant-design-vue'
+
+import { createGroup } from '#/api/im/group'
+import { CardBubble } from '#/views/im/home/components/card'
+
+import { ImContentType, ImConversationType, isGroupConversation } from '../../../utils/constants'
+import { getConversationKey } from '../../../utils/conversation'
+import { buildDefaultGroupName } from '../../../utils/group'
+import { type CardTarget, serializeMessage } from '../../../utils/message'
+import { getGroupDisplayName, isGroupQuit } from '../../../utils/user'
+import { useMessageSender } from '../../composables/useMessageSender'
+import { FacePicker } from '../../pages/conversation/components/input'
+import { useConversationStore } from '../../store/conversationStore'
+import { useFriendStore } from '../../store/friendStore'
+import { useGroupStore } from '../../store/groupStore'
+import { ConversationPickerPanel } from '../picker'
+import { FriendPickerPanel } from '../picker'
+
+defineOptions({ name: 'ImRecommendCardDialog' })
+
+const conversationStore = useConversationStore()
+const friendStore = useFriendStore()
+const groupStore = useGroupStore()
+const { sendRaw, send } = useMessageSender()
+
+const visible = ref(false)
+const target = ref<CardTarget | null>(null)
+const view = ref<'contact' | 'conversation'>('conversation') // 褰撳墠瑙嗗浘锛氶粯璁や細璇濋�夋嫨锛屻�屽垱寤鸿亰澶┿�嶅叆鍙e垏鍒板ソ鍙嬮�夋嫨
+const selectedKeys = ref<string[]>([])
+const selectedFriendIds = ref<number[]>([])
+const leaveMessage = ref('')
+const sending = ref(false)
+const emojiVisible = ref(false) // 琛ㄦ儏闈㈡澘鏄鹃殣锛氬彸渚� smile icon 鍒囨崲
+
+defineExpose({
+ /** 鎵撳紑鎺ㄨ崘寮圭獥锛歳eset 鈫� 鐏屽弬 鈫� visible=true */
+ open(opts: { target: CardTarget }) {
+ target.value = opts.target
+ view.value = 'conversation'
+ selectedKeys.value = []
+ selectedFriendIds.value = []
+ leaveMessage.value = ''
+ emojiVisible.value = false
+ sending.value = false
+ visible.value = true
+ }
+})
+
+/** 寮圭獥鏍囬锛氫細璇濊鍥炬寜 target 绫诲瀷鍒嗘枃妗堬紱濂藉弸瑙嗗浘鍥哄畾涓恒�岄�夋嫨濂藉弸銆� */
+const headerTitle = computed(() => {
+ if (view.value === 'contact') {
+ return '閫夋嫨濂藉弸'
+ }
+ return isGroupConversation(target.value?.targetType) ? '鎶婅繖涓兢鎺ㄨ崘缁欐湅鍙�' : '鎶婁粬鎺ㄨ崘缁欐湅鍙�'
+})
+
+/** 鍊欓�変細璇濓細浠� store 鎷挎帓搴忓悗鐨勫垪琛紙hide 鐢� Panel 鎺� hideKeys 杩囨护锛夛紱鍘嗗彶閫�缇ょ兢涓嶅彲琚帹鑽愰�変腑锛堥�変簡鍚庣涔熶細鎷掞級 */
+const candidateConversations = computed<Conversation[]>(() =>
+ conversationStore.getSortedConversationList.filter(
+ (conversation) =>
+ !(
+ conversation.type === ImConversationType.GROUP &&
+ isGroupQuit(groupStore.getGroup(conversation.targetId))
+ )
+ )
+)
+
+/** 闅愯棌 key锛氫笉鑳芥妸鍚嶇墖鎺ㄥ洖鍚嶇墖鏈韩鐨勪細璇濓紙鐢ㄦ埛鍚嶇墖閬垮厤鑷帹銆佺兢鍚嶇墖閬垮厤鎺ㄥ洖璇ョ兢锛� */
+const hideKeys = computed<string[]>(() => {
+ const t = target.value
+ if (!t) {
+ return []
+ }
+ return [getConversationKey({ type: t.targetType, targetId: t.targetId })]
+})
+
+/** 濂藉弸瑙嗗浘鍊欓�夊垪琛細鐩存帴澶嶇敤 friendStore Lite 瑙嗗浘 */
+const friends = computed<FriendLite[]>(() => friendStore.getActiveFriendLiteList)
+
+/** 鎶婇�変腑鐨� emoji 鎷煎埌鐣欒█鏈熬锛汧acePicker 鑷韩璐熻矗鍏抽棴闈㈡澘 */
+function handleEmojiSelect(emoji: string) {
+ leaveMessage.value = `${leaveMessage.value}${emoji}`
+}
+
+/** 鍒囧埌濂藉弸瑙嗗浘锛氭竻鎺変箣鍓嶅湪浼氳瘽瑙嗗浘杈撳叆鐨勭暀瑷�锛岄伩鍏嶅湪涓嶅彲瑙佽緭鍏ユ閲屾妸鐣欒█闈欓粯鍙戝埌鏂扮兢 */
+function handleSwitchToContact() {
+ view.value = 'contact'
+ leaveMessage.value = ''
+ emojiVisible.value = false
+}
+
+/**
+ * 纭鍙戦�侊紙浼氳瘽瑙嗗浘锛夛細姣忎釜閫変腑浼氳瘽鍏堝彂 CARD锛孋ARD 鎴愬姛鍚庢墠鍙戠暀瑷�锛堜繚璇併�屽厛鐪嬪埌鍚嶇墖銆嶇殑椤哄簭鎰忓浘锛�
+ *
+ * 鏂囨鑱氬悎锛氬叏閮ㄦ垚鍔熴�屽凡杞彂銆嶃�佸叏閮ㄥけ璐ャ�岃浆鍙戝け璐ワ細A銆丅銆嶃�侀儴鍒嗗け璐ャ�屽凡杞彂锛屼絾 X銆乊 澶辫触銆嶏紱
+ * 澶辫触鐨勬秷鎭互 FAILED 鐘舵�佺暀鍦ㄥ搴斾細璇濇皵娉¢噷锛屽彲鍙抽敭閲嶈瘯
+ */
+async function handleSend() {
+ const card = target.value
+ if (!card?.targetId || selectedKeys.value.length === 0) {
+ return
+ }
+ const byKey = new Map(candidateConversations.value.map((c) => [getConversationKey(c), c]))
+ const targets = selectedKeys.value
+ .map((key) => byKey.get(key))
+ .filter((c): c is Conversation => c != null)
+ if (targets.length === 0) {
+ return
+ }
+ const cardContent = serializeMessage({ ...card })
+ const leaveText = leaveMessage.value.trim()
+ sending.value = true
+ try {
+ const tasks = targets.map(async (conversation) => {
+ const cardOk = await sendRaw(ImContentType.CARD, cardContent, { conversation })
+ if (!cardOk) {
+ return { conversation, ok: false }
+ }
+ const ok = leaveText ? await send(leaveText, { conversation }) : true
+ return { conversation, ok }
+ })
+ const results = await Promise.all(tasks)
+ const failedNames = results.filter((r) => !r.ok).map((r) => r.conversation.name || '鏈懡鍚嶄細璇�')
+ // 鎶婂懡涓殑鐩爣鎺ㄥ埌鏈�杩戣浆鍙戝垪琛紙閮ㄥ垎澶辫触涔熸帹锛氱敤鎴风殑"鎰忓浘"宸茶〃杈撅級
+ conversationStore.pushRecentForwardConversationKeyList(targets.map((c) => getConversationKey(c)))
+ if (failedNames.length === 0) {
+ message.success('宸茶浆鍙�')
+ } else if (failedNames.length === targets.length) {
+ message.error(`杞彂澶辫触锛�${failedNames.join('銆�')}`)
+ } else {
+ message.warning(`宸茶浆鍙戯紝浣� ${failedNames.join('銆�')} 澶辫触`)
+ }
+ visible.value = false
+ } finally {
+ sending.value = false
+ }
+}
+
+/**
+ * 濂藉弸瑙嗗浘鍙戦�侊細鍏堝缓缇わ紙鍚屾椂閭�璇锋墍閫夊ソ鍙嬶級鈫� 缁欐柊缇ゅ彂鍚嶇墖 鈫� 鍙戠暀瑷� 鈫� 鍏冲脊绐�
+ *
+ * 璺熶細璇濊鍥剧殑宸埆锛氬厛瑕� createGroup 鎷垮埌 groupId锛屼箣鍚庢瀯閫犱竴涓� GROUP 绫诲瀷鐨� conversation 瀵硅薄缁� sendRaw 鐢�
+ * 锛坰endRaw 鍐呴儴浼氳嚜鍔� insertMessage 鎶婃柊缇ょ櫥璁拌繘 store锛屾渶杩戣浆鍙戝垪琛ㄤ篃鑳芥甯告帹锛�
+ */
+async function handleCreateGroupAndSend() {
+ const card = target.value
+ if (!card?.targetId || selectedFriendIds.value.length === 0) {
+ return
+ }
+ const byId = new Map(friends.value.map((f) => [f.id, f]))
+ const members = selectedFriendIds.value
+ .map((id) => byId.get(id))
+ .filter((f): f is FriendLite => f != null)
+ if (members.length === 0) {
+ return
+ }
+ sending.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
+ groupStore.upsertGroup({
+ id: group.id,
+ name: group.name,
+ avatar: group.avatar,
+ notice: group.notice,
+ ownerUserId: group.ownerUserId
+ })
+ // 缁欐柊缇ゆ瀯閫犱竴涓复鏃� conversation 瀵硅薄缁� sendRaw 鐢紱sendRaw 鍐呴儴浼氳嚜鍔� insertMessage 鐧昏
+ const newConversation: Conversation = {
+ type: ImConversationType.GROUP,
+ targetId: group.id,
+ name: getGroupDisplayName(group) || name,
+ avatar: group.avatar || '',
+ unreadCount: 0,
+ lastContent: '',
+ lastSendTime: 0
+ }
+ const cardOk = await sendRaw(ImContentType.CARD, serializeMessage({ ...card }), {
+ conversation: newConversation
+ })
+ if (!cardOk) {
+ message.warning('缇ゅ凡鍒涘缓锛屼絾鍚嶇墖鍙戦�佸け璐ワ紝璇风◢鍚庡湪缇ら噷閲嶈瘯')
+ visible.value = false
+ return
+ }
+ const leaveText = leaveMessage.value.trim()
+ if (leaveText) {
+ await send(leaveText, { conversation: newConversation })
+ }
+ conversationStore.pushRecentForwardConversationKeyList([getConversationKey(newConversation)])
+ message.success('宸插垱寤虹兢鑱婂苟鍙戦��')
+ visible.value = false
+ } finally {
+ sending.value = false
+ }
+}
+</script>
+
+<template>
+ <!--
+ 鎶婂悕鐗囨帹鑽愮粰鏈嬪弸锛堢敤鎴� / 缇ら�氱敤锛�
+ - dialog 澹崇敱鏈粍浠舵寔鏈夛紱閫夋嫨 UI 濮旀墭 ConversationPickerPanel / FriendPickerPanel
+ - view='conversation'锛氶�夊凡鏈変細璇濆彂鍚嶇墖锛堥粯璁よ鍥撅級
+ - view='contact'锛氫粠銆屽垱寤鸿亰澶┿�嶅叆鍙h繘鍏ワ紝閫夊ソ鍙嬪缓缇ゅ啀鍙戝悕鐗囷紝涓氬姟澹冲眰鍒囪鍥撅紝涓や釜 Panel 浜掍笉鐭ラ亾瀵规柟
+ - 閫� 1 璧般�屽彂閫併�嶃�佸涓蛋銆屽垎鍒彂閫�(n)銆嶆枃妗堬紝涓庡井淇′竴鑷�
+ - 澶辫触鐨勬秷鎭互 FAILED 鐘舵�佺暀鍦ㄥ搴斾細璇濇皵娉¢噷锛屼緵鍙抽敭閲嶈瘯
+ - 瀵瑰鎺ュ彛锛歳ef + open({ target })锛屼笉鍐嶈蛋 v-model
+ -->
+ <Modal
+ v-model:open="visible"
+ width="720px"
+ :mask-closable="false"
+ :footer="view === 'conversation' ? null : undefined"
+ class="im-picker-dialog im-recommend-dialog"
+ >
+ <template #header>
+ <div class="flex gap-2 items-center">
+ <Icon
+ v-if="view === 'contact'"
+ icon="ant-design:arrow-left-outlined"
+ :size="16"
+ class="cursor-pointer text-[var(--ant-color-text-secondary)] transition-colors duration-150 hover:text-[var(--ant-color-primary)]"
+ @click="view = 'conversation'"
+ />
+ <span class="text-base text-[var(--ant-color-text)]">
+ {{ headerTitle }}
+ </span>
+ </div>
+ </template>
+
+ <div class="h-[480px]">
+ <!-- 浼氳瘽瑙嗗浘锛氶�夊凡鏈変細璇濆彂閫� -->
+ <ConversationPickerPanel
+ v-if="view === 'conversation'"
+ v-model:selected-keys="selectedKeys"
+ :conversations="candidateConversations"
+ :recent-forward-conversation-keys="conversationStore.recentForwardConversationKeys"
+ :hide-keys="hideKeys"
+ :show-create-chat="true"
+ @create-chat="handleSwitchToContact"
+ @remove-recent="conversationStore.removeRecentForwardConversationKey"
+ >
+ <template #footer>
+ <div class="flex flex-col gap-3 px-4 py-3">
+ <!-- 鍚嶇墖棰勮鍗� -->
+ <CardBubble v-if="target" :card="target" />
+
+ <!-- 鐣欒█锛堝崟琛岋級锛氬彸渚ц〃鎯呮寜閽Е鍙� FacePicker锛涢�変腑 emoji 鎷煎埌鏈熬 -->
+ <div class="relative">
+ <Input v-model:value="leaveMessage" :maxlength="100" placeholder="缁欐湅鍙嬬暀瑷�">
+ <template #suffix>
+ <Icon
+ icon="ant-design:smile-outlined"
+ :size="18"
+ class="cursor-pointer text-[var(--ant-color-text-secondary)] hover:text-[var(--ant-color-primary)]"
+ @click.stop="emojiVisible = !emojiVisible"
+ />
+ </template>
+ </Input>
+ <FacePicker
+ v-model:visible="emojiVisible"
+ mode="emoji"
+ class="bottom-full right-0 mb-2"
+ @select-emoji="handleEmojiSelect"
+ />
+ </div>
+
+ <!-- 鎿嶄綔鎸夐挳锛氶�� 0/1 鏄剧ず銆屽彂閫併�嶃�佸涓樉绀恒�屽垎鍒彂閫�(n)銆� -->
+ <div class="flex gap-2 justify-end">
+ <Button @click="visible = false">鍙栨秷</Button>
+ <Button
+ type="primary"
+ :loading="sending"
+ :disabled="selectedKeys.length === 0"
+ @click="handleSend"
+ >
+ {{ selectedKeys.length > 1 ? `鍒嗗埆鍙戦�侊紙${selectedKeys.length}锛塦 : '鍙戦��' }}
+ </Button>
+ </div>
+ </div>
+ </template>
+ </ConversationPickerPanel>
+
+ <!-- 濂藉弸瑙嗗浘锛氶�夊ソ鍙嬪缓缇ゅ悗鍙戦�� -->
+ <FriendPickerPanel v-else v-model:selected-ids="selectedFriendIds" :friends="friends" />
+ </div>
+
+ <!-- 濂藉弸瑙嗗浘鐨� dialog footer锛氬缓缇ゅ苟鍙戦�� -->
+ <template v-if="view === 'contact'" #footer>
+ <Button @click="visible = false">鍙栨秷</Button>
+ <Button
+ type="primary"
+ :loading="sending"
+ :disabled="selectedFriendIds.length === 0"
+ @click="handleCreateGroupAndSend"
+ >
+ 鍒涘缓缇よ亰骞跺彂閫�
+ </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>
--
Gitblit v1.9.3