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/index.vue | 292 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 292 insertions(+), 0 deletions(-)
diff --git a/src/views/im/home/index.vue b/src/views/im/home/index.vue
new file mode 100644
index 0000000..0689956
--- /dev/null
+++ b/src/views/im/home/index.vue
@@ -0,0 +1,292 @@
+<script lang="ts" setup>
+import type { Conversation } from './types'
+
+import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
+import { useRoute } from 'vue-router'
+
+import { preferences } from '#/packages/preferences/src'
+
+import { ImConversationType } from '../utils/constants'
+import { initDb, stopRequests, StorageKeys } from '../utils/db'
+import { ContextMenu, ToolBar } from './components'
+import { GroupInfoCard } from './components/group'
+import { RtcCallContainer } from './components/rtc'
+import { UserInfoCard } from './components/user'
+import { useMessagePuller } from './composables/useMessagePuller'
+import { useMessageSender } from './composables/useMessageSender'
+import { useVoicePlayer } from './composables/useVoicePlayer'
+import { useChannelStore } from './store/channelStore'
+import { useConversationStore } from './store/conversationStore'
+import { useFaceStore } from './store/faceStore'
+import { useFriendStore } from './store/friendStore'
+import { useGroupRequestStore } from './store/groupRequestStore'
+import { useGroupStore } from './store/groupStore'
+import { useMessageStore } from './store/messageStore'
+import { useImWebSocketStore } from './store/websocketStore'
+
+defineOptions({ name: 'ImIndex' })
+
+const route = useRoute()
+const conversationStore = useConversationStore()
+const messageStore = useMessageStore()
+const webSocketStore = useImWebSocketStore()
+const friendStore = useFriendStore()
+const groupStore = useGroupStore()
+const groupRequestStore = useGroupRequestStore()
+const faceStore = useFaceStore()
+const channelStore = useChannelStore()
+const { pullOnce, cancelPull } = useMessagePuller()
+const { readActive, syncPrivateReadStatus } = useMessageSender()
+const voicePlayer = useVoicePlayer()
+const childRouteReady = ref(false) // 瀛愯矾鐢辨槸鍚﹀彲鎸傝浇
+
+/** 鍒濆鍖栵細鍏堝悆鏈湴缂撳瓨璁╅灞忕珛鍗虫覆鏌擄紝鍐嶈繙绔埛鏂版渶鏂版暟鎹紝鏈�鍚庡缓瀹炴椂閫氫俊鎷夌绾挎秷鎭� */
+onMounted(async () => {
+ // 0.1 绯荤粺琛ㄦ儏鍖呭悗鍙伴鎷夛細鐙珛閾捐矾涓庨灞� IDB / 杩滅鎷夊彇骞跺彂锛屾秷闄よ〃鎯呴潰鏉块娆″睍寮�鐧藉睆锛涘け璐ヤ粎璁版棩蹇楋紝涓嶉樆濉炰富娴佺▼
+ void faceStore.ensureFacePackList().catch((error) => console.warn('[IM] 鍚庡彴棰勬媺琛ㄦ儏鍖呭け璐�', error))
+ // 1.1 鏁存 loading=true 闃绘柇浼氳瘽鍒楄〃鎶栧姩鍐欑洏 + WebSocket 鏅�氭秷鎭繘缂撳啿锛岄伩鍏� connect 鍒� pullOnce 涔嬮棿鏀跺埌鐨勫疄鏃舵秷鎭帹杩� maxId 瀵艰嚧 pull 璺宠繃鏂嚎绉帇娑堟伅
+ conversationStore.loading = true
+ try {
+ // 1.2 鎵撳紑褰撳墠鐢ㄦ埛 IM DB
+ await initDb()
+ // 1.3 澶氫釜 store 骞跺彂浠� IDB 璇诲彇鏈湴缂撳瓨
+ const cacheResults = await Promise.all([
+ conversationStore.loadConversationList(),
+ messageStore.loadMessageCursorList(),
+ friendStore.loadFriendData(),
+ groupStore.loadGroupList(),
+ channelStore.loadChannelList(),
+ groupRequestStore.loadGroupRequestList()
+ ])
+ const hasFriendRows = cacheResults[2]
+ const hasGroupRows = cacheResults[3]
+ const hasChannelRows = cacheResults[4]
+ groupStore.markAllGroupActiveCallsExpired()
+ groupStore.markAllGroupMembersExpired()
+ childRouteReady.value = true
+ // 1.4 鎴戠鐞嗙殑缇や笅鏈鐞嗗姞缇ょ敵璇风孩鐐癸細棣栫櫥鐢� unhandled-list锛堟湇鍔$鐩存帴杩囨护鏈鐞嗭紝璇箟绮惧噯銆佸惎鍔ㄨ交锛夛紱
+ // pullGroupRequests 鍙湪閲嶈繛 / 鍚庣画琛ュ伩鏃惰窇锛堣 useMessagePuller.pullStateEvents锛夛紝涓嶈繘棣栫櫥涓婚摼璺�
+ void groupRequestStore
+ .fetchUnhandledGroupRequestList()
+ .catch((error) => console.warn('[IM] 鎷夊彇鏈鐞嗗姞缇ょ敵璇峰け璐�', error))
+
+ // 2. 濂藉弸涓绘暟鎹仮澶嶈蛋 pull锛涚兢鍒楄〃鐢ㄥ揩鐓у埛鏂帮紝瑕嗙洊绂荤嚎鏈熼棿鑷繁鐨勫叆缇� / 閫�缇ょ姸鎬佸彉鍖�
+ // 2.1 鏈夌紦瀛橈細寮傛鑳屾櫙澧為噺鍒锋柊锛屽け璐ヤ粎璁版棩蹇楋紙IDB 鏁版嵁宸茬粡澶熸拺棣栧睆锛宲ullOnce 涔熻兘姝e父鍏ュ簱锛�
+ // 2.2 鏃犵紦瀛橈紙棣栫櫥 / 鍒囪处鍙峰洖鍒囷級锛氬繀椤� await + 澶辫触鎶涘嚭涓柇鏈疆 onMounted锛�
+ // 鍚﹀垯 pullOnce 浼氱敤 senderId 鏁板瓧缁欎細璇濊捣鍚嶈惤鍒� IDB 鍚庣画鍩烘湰鏃犳硶鑷剤锛涙棤缂撳瓨鍒嗘敮骞跺彂 Promise.all 鐪佷竴涓� RTT
+ const requiredFetches: Promise<unknown>[] = []
+ if (hasFriendRows) {
+ void friendStore.pullFriends().catch((error) => console.warn('[IM] 鍚庡彴澧為噺鎷夊ソ鍙嬪け璐�', error))
+ } else {
+ requiredFetches.push(friendStore.pullFriends())
+ }
+ if (hasGroupRows) {
+ void groupStore.fetchGroupList(true).catch((error) => console.warn('[IM] 鍚庡彴鍒锋柊缇ゅ垪琛ㄥけ璐�', error))
+ } else {
+ requiredFetches.push(groupStore.fetchGroupList(true))
+ }
+ // 2.3 棰戦亾鏃犲閲� pull 鎺ュ彛锛岀户缁蛋 list
+ if (hasChannelRows) {
+ void channelStore.fetchChannelList().catch((error) => console.warn('[IM] 鍚庡彴鍒烽閬撳垪琛ㄥけ璐�', error))
+ } else {
+ requiredFetches.push(channelStore.fetchChannelList())
+ }
+ // 2.4 鎵ц鍔犺浇
+ if (requiredFetches.length > 0) {
+ await Promise.all(requiredFetches)
+ }
+
+ // 2.5 濂藉弸鐢宠澧為噺琛ュ伩锛氶鐧讳篃瑕佽窇锛岀绾挎湡闂村ソ鍙嬬敵璇峰彉鏇翠笉浼氬奖鍝嶅ソ鍙嬩富琛�
+ void friendStore
+ .pullFriendRequests()
+ .catch((error) => console.warn('[IM] 鍚庡彴澧為噺鎷夊ソ鍙嬬敵璇峰け璐�', error))
+
+ // 3. 浼氳瘽璇讳綅缃厛琛ュ伩锛屾秷鎭叆搴撴椂鍙洿鎺ヨ繃婊ゅ凡璇诲巻鍙叉秷鎭�
+ await conversationStore
+ .pullConversationReads()
+ .catch((error) => console.warn('[IM] 鎷夊彇浼氳瘽璇讳綅缃け璐�', error))
+
+ // 4. 瀹炴椂閫氫俊锛氬缓 WebSocket 闀胯繛鎺� + 鎷夌绾挎秷鎭紙pullOnce finally 鎶� loading 褰掍綅锛�
+ webSocketStore.connect()
+ await pullOnce()
+
+ // 5. 榛樿閫変腑绗竴涓細璇濓紱鑻ョ疆椤跺垎缁勫浜庢姌鍙犳�侊紝闇�璺宠繃琚姌鍙犻殣钘忕殑缃《椤癸紝閬垮厤鑷姩灞曞紑鎶樺彔
+ const sorted = conversationStore.getSortedConversationList
+ const firstVisible = pickFirstVisibleConversation(sorted)
+ if (firstVisible && !conversationStore.activeConversation) {
+ conversationStore.setActiveConversation(firstVisible)
+ }
+ } catch (error) {
+ // 1. 棣栨媺澶辫触锛氭墜鍔ㄥ浣� loading锛坧ullOnce 娌¤窇鍒帮紝瀹冪殑 finally 鍏滀笉鍒拌繖閲岋級锛屽惁鍒欏悗缁細璇濆垪琛ㄥ啓鍏ュ叏琚棭 return 闃绘柇
+ // 2. WebSocket 涓嶅湪杩欓噷 disconnect鈥斺�旇矾鐢辩寮�浼氳蛋 onUnmounted 鑷劧娓呯悊锛岀敤鎴蜂篃鍙互鍒锋柊閲嶈瘯
+ conversationStore.loading = false
+ console.error('[IM] 鍒濆鍖栧け璐�', error)
+ }
+})
+
+/**
+ * 閫夐灞忚嚜鍔ㄦ縺娲荤殑浼氳瘽锛涚疆椤跺垎缁勬姌鍙犳椂璺宠繃琚姌鍙犻殣钘忕殑缃《椤癸紝閬垮厤婵�娲诲悗琚嚜鍔ㄩ《涓婃潵
+ *
+ * 鎶樺彔鎬佸垽瀹氬彧鐪嬬敤鎴峰紑鍏筹紱闈炵疆椤� / 鏈夋湭璇荤殑缃《椤瑰缁堝彲瑙侊紝鍏ㄦ槸鍙姌鍙犵疆椤舵椂鍥為��鍒� sorted[0] 鍏滃簳
+ */
+function pickFirstVisibleConversation(sorted: Conversation[]): Conversation | undefined {
+ if (sorted.length === 0) {
+ return undefined
+ }
+ const pinnedExpanded =
+ localStorage.getItem(StorageKeys.localStorage.conversationPinnedExpanded) === 'true'
+ if (pinnedExpanded) {
+ return sorted[0]
+ }
+ return sorted.find((c) => !c.top || (!c.silent && (c.unreadCount || 0) > 0)) ?? sorted[0]
+}
+
+/** 鏍囩鍏抽棴鍓� flush 鑽夌闃熷垪锛沝ebounce 榛樿 trail-edge 瑙﹀彂锛屾渶鍚庝竴娆¤緭鍏ュ彲鑳借繕鍘嬪湪闃熷垪閲� */
+function onBeforeUnload() {
+ conversationStore.flushConversationDraftSave()
+}
+window.addEventListener('beforeunload', onBeforeUnload)
+
+/** 绂诲紑 IM 涓诲3锛氬彇娑� pull銆佹柇寮� WebSocket銆佷繚瀛樿崏绋裤�佸仠姝㈣闊炽�佽В缁� unload锛屽苟缁撴潫褰撳墠 IM session */
+onUnmounted(() => {
+ cancelPull()
+ webSocketStore.disconnect()
+ conversationStore.flushConversationDraftSave()
+ faceStore.clear()
+ // 妯″潡绾у崟渚� audio 涓嶄細闅忚鍥惧嵏杞借嚜鍔ㄥ仠锛屼富鍔ㄥ仠鎺夐伩鍏嶅垏璺敱鍚庤闊崇户缁搷
+ voicePlayer.stop()
+ window.removeEventListener('beforeunload', onBeforeUnload)
+ // 鍋滄褰撳墠 IM session 骞舵竻鐞嗗悇 store 鍐呭瓨
+ void stopRequests()
+})
+
+/**
+ * 褰撳墠浼氳瘽鍒囨崲锛氭湰鍦版竻闆舵湭璇� + 涓婃姤鍚庣宸茶 + 绉佽亰琛�"瀵规柟宸茶鍒板摢鏉�"
+ *
+ * type+targetId 涓�璧风洃鍚細绉佽亰涓庣兢鑱� id 鍚屽彿鏃跺垏鎹篃鑳借Е鍙戯紱鍏跺畠浼氳瘽宸茶鐘舵�佺敱 WebSocket
+ * READ / RECEIPT 浜嬩欢琚姩鍚屾銆傜鑱婅ˉ涓�娆℃媺瀵规柟宸茶浣嶇疆锛屽讥琛ョ绾� / 澶氱婕忔帀鐨� RECEIPT 鎺ㄩ��
+ */
+watch(
+ () => [
+ conversationStore.activeConversation?.type,
+ conversationStore.activeConversation?.targetId
+ ],
+ async ([type, targetId]) => {
+ if (!targetId) {
+ return
+ }
+ // 鏈湴娓呴浂鏈 + 涓婃姤鍚庣宸茶锛岃鍏跺畠绔� / 瀵规柟 UI 鍚屾
+ await readActive()
+ // 绉佽亰琛ヤ竴娆�"瀵规柟宸茶鍒板摢鏉�"锛屽讥琛ョ绾� / 澶氱婕忔帀鐨� RECEIPT 鎺ㄩ��
+ if (type === ImConversationType.PRIVATE) {
+ void syncPrivateReadStatus(targetId)
+ }
+ }
+)
+
+/**
+ * 娴忚鍣ㄦ爣绛� title 鎷间笂鏈鏁板墠缂�锛涗緥锛�(63鏉℃湭璇�)鑺嬮亾婧愮爜
+ *
+ * 璺敱鍒囨崲鏃� router.afterEach 浼氳皟 useTitle 閲嶇疆 title锛涚敤 nextTick 鎺掑湪瀹冧箣鍚庡啀瑕嗙洊
+ * 涓�骞剁洃鍚� route.fullPath锛孖M 瀛愯矾鐢卞垏鎹紙娑堟伅 / 閫氳褰曪級涔熻兘閲嶆柊鍔犱笂鍓嶇紑
+ */
+watch(
+ [() => conversationStore.getTotalUnreadCount, () => route.fullPath],
+ ([count]) => {
+ nextTick(() => {
+ const base = preferences.app.name
+ document.title = count > 0 ? `(${count > 99 ? '99+' : count}鏉℃湭璇�)${base}` : base
+ })
+ },
+ { immediate: true }
+)
+</script>
+
+<template>
+ <!--
+ IM 澶栧眰瀹瑰櫒锛氳亰澶╂ā鍧楃殑鍏ㄥ睆娌夋蹈寮忓3
+ - 宸︿晶 ToolBar锛氬ご鍍� + 涓� Tab锛堟秷鎭�/濂藉弸/缇よ亰锛�+ 搴曢儴璁剧疆
+ - 鍙充晶 <router-view>锛氭寜璺敱娓叉煋 MessagePage / FriendPage / GroupPage
+ - 鎸傝浇鍏ㄥ眬寮瑰眰锛歎serInfoCard / GroupInfoCard / ContextMenu
+ -->
+ <div
+ class="im-home flex w-full h-full overflow-hidden bg-[var(--ant-color-bg-layout)] text-[var(--ant-color-text)]"
+ >
+ <ToolBar />
+ <!--
+ keep-alive 缂撳瓨瀛愰〉闈細
+ - 鍒� Tab 涓嶉噸寤虹粍浠讹紝MessagePanel 婊氬姩浣嶇疆銆佽緭鍏ユ鑽夌绛� UI 鐘舵�佷笉涓�
+ - Vue 3 閲� keep-alive 涓嶈兘鐩存帴鍖� <router-view>锛堜細鏈夎鍛婏級锛屽繀椤昏蛋 v-slot 鎷� Component
+ -->
+ <router-view v-if="childRouteReady" v-slot="{ Component }">
+ <keep-alive>
+ <component :is="Component" />
+ </keep-alive>
+ </router-view>
+ <div v-else class="flex-1 bg-[var(--ant-color-bg-container)]"></div>
+
+ <!-- 鍏ㄥ眬寮瑰眰锛氱敱 useImUiStore 缁熶竴璋冨害 -->
+ <UserInfoCard />
+ <GroupInfoCard />
+ <ContextMenu />
+
+ <!-- 瀹炴椂閫氳瘽娴眰锛涚洃鍚� rtcStore 鍏ㄥ眬鐘舵�侊紝鍙湪浠绘剰 IM 瀛愰〉寮瑰嚭 -->
+ <RtcCallContainer />
+ </div>
+</template>
+
+<style scoped>
+:global(:root) {
+ --im-border-color-lighter: #e8eaed;
+ --im-resize-line-color: #d8dde5;
+
+ /*
+ * antd 4.x 涓嶈緭鍑� --ant-color-* 鍏ㄥ眬 CSS 鍙橀噺锛堜粎 React antd 鐨� theme.cssVar 鏀寔锛夛紝
+ * 鑰� IM 鑱婂ぉ绔ぇ閲忕洿鎺ヤ娇鐢� var(--ant-color-*)銆傛澶勭敤 Vben design token 鍏滃簳瀹氫箟锛�
+ * 鏄犲皠瑙勫垯涓� useAntdDesignTokens 鍠傜粰 antd 鐨� token 瀹屽叏涓�鑷达紝闅忎富棰橈紙浜�/鏆楋級鑷姩鍒囨崲銆�
+ * 缂哄け鏃惰繖浜涘彉閲忎负绌� 鈫� 鑳屾櫙閫忔槑銆佽竟妗�/濉厖澶辨晥锛堝吀鍨嬶細琛ㄦ儏闈㈡澘閫忓嚭杈撳叆妗嗗崰浣嶆枃瀛楋級銆�
+ */
+ --ant-color-bg-container: hsl(var(--card));
+ --ant-color-bg-elevated: hsl(var(--popover));
+ --ant-color-bg-layout: hsl(var(--background-deep));
+ --ant-color-border: hsl(var(--border));
+ --ant-color-border-secondary: hsl(var(--border));
+ --ant-color-text: hsl(var(--foreground));
+ --ant-color-text-secondary: hsl(var(--foreground) / 65%);
+ --ant-color-text-placeholder: hsl(var(--foreground) / 45%);
+ --ant-color-text-disabled: hsl(var(--foreground) / 30%);
+ /*
+ * fill 绯诲垪锛氭祬鑹蹭笅鐢� Element Plus 椋庢牸鐨勩�屽喎璋冩祬鐏板疄鑹层�嶈�岄潪鍗婇�忔槑娣辫壊锛�
+ * 鍚﹀垯闈㈡澘锛堜細璇濆垪琛� / 娑堟伅闈㈡澘绛夛級鍙犲湪鐏板簳涓婁細鏄捐剰鍙戞殫锛屽拰 Vue3+EP 鐨勫共鍑�鐧藉樊璺濇槑鏄俱��
+ * 鍙栧�间緷缁勪欢瀹為檯鐢ㄦ硶锛歴econdary 澶氱敤浜庨潰鏉垮簳锛堝彇鏈�娴咃紝瀵归綈 EP --el-bg-color-page #f5f7fa锛夛紝
+ * tertiary 澶氱敤浜� hover / 鍥炬爣鍧楋紙姣旈潰鏉跨暐娣憋紝淇濊瘉 hover 鍙锛夈�傛繁鑹插湪 .dark 閲屽彟琛岃鐩栥��
+ */
+ --ant-color-fill: #e2e6ec;
+ --ant-color-fill-secondary: #f4f6f9;
+ --ant-color-fill-tertiary: #eaedf2;
+ --ant-color-fill-dark: #d5dae2;
+ --ant-color-primary: hsl(var(--primary));
+ --ant-color-primary-hover: hsl(var(--primary) / 80%);
+ --ant-color-primary-bg: hsl(var(--primary) / 12%);
+ --ant-color-primary-bg-hover: hsl(var(--primary) / 18%);
+ --ant-color-info: hsl(var(--primary));
+ --ant-color-success: hsl(var(--success));
+ --ant-color-success-bg: hsl(var(--success) / 12%);
+ --ant-color-success-bg-hover: hsl(var(--success) / 18%);
+ --ant-color-warning: hsl(var(--warning));
+ --ant-color-warning-bg: hsl(var(--warning) / 12%);
+ --ant-color-warning-bg-hover: hsl(var(--warning) / 18%);
+ --ant-color-error: hsl(var(--destructive));
+}
+
+:global(.dark) {
+ --im-border-color-lighter: rgb(255 255 255 / 12%);
+ --im-resize-line-color: rgb(255 255 255 / 18%);
+
+ /* 娣辫壊涓� fill 鍥炲埌銆屽熀浜庡墠鏅壊鐨勫崐閫忔槑浜壊銆嶏紝鍙犲湪娣卞簳涓婂緱鍒拌嚜鐒剁殑寰寒琛ㄩ潰 */
+ --ant-color-fill: hsl(var(--foreground) / 12%);
+ --ant-color-fill-secondary: hsl(var(--foreground) / 8%);
+ --ant-color-fill-tertiary: hsl(var(--foreground) / 4%);
+ --ant-color-fill-dark: hsl(var(--foreground) / 18%);
+}
+</style>
--
Gitblit v1.9.3