chat.vue 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942
  1. <template >
  2. <h3 style="position: absolute; left: 50px; top: 30px;">
  3. <i>WebChat</i>
  4. </h3>
  5. <div class="chat-container">
  6. <div class="chat-left-container">
  7. <div class="mac-dots-container">
  8. <div class="mac-dot red"></div>
  9. <div class="mac-dot yellow"></div>
  10. <div class="mac-dot green"></div>
  11. </div>
  12. <a-avatar :src="photo" :size="40" style="margin-left: 10px; margin-top: 20px; border: 2px solid white; border-radius: 50%;"/>
  13. <div class="chat-menus">
  14. <MessageOutlined
  15. @click="handleIconClick('message')"
  16. :style="{ color: iconColors.message }"
  17. @mouseover="handleIconHover('message')"
  18. @mouseout="handleIconLeave('message')"
  19. />
  20. <UserOutlined
  21. @click="handleIconClick('user')"
  22. :style="{ color: iconColors.user }"
  23. @mouseover="handleIconHover('user')"
  24. @mouseout="handleIconLeave('user')"
  25. />
  26. <div class="bell-with-notification" :width="20">
  27. <BellOutlined
  28. @click="handleIconClick('bell')"
  29. :style="{ color: iconColors.bell }"
  30. @mouseover="handleIconHover('bell')"
  31. @mouseout="handleIconLeave('bell')"
  32. />
  33. <span class="message-red-point" :style="{ display: redPointStatus }"></span>
  34. </div>
  35. <WechatOutlined
  36. @click="handleIconClick('moment')"
  37. :style="{ color: iconColors.moment }"
  38. @mouseover="handleIconHover('moment')"
  39. @mouseout="handleIconLeave('moment')"
  40. />
  41. <LoginOutlined style="position: absolute; bottom: 20px"
  42. @click="handleIconClick('logout')"
  43. :style="{ color: iconColors.setting }"
  44. @mouseover="handleIconHover('logout')"
  45. @mouseout="handleIconLeave('logout')"
  46. />
  47. </div>
  48. </div>
  49. <div class="chat-center-container">
  50. <div class="chat-center-header-container">
  51. <a-input v-model:value="searchInput" placeholder="搜索" class="search-input" />
  52. <a-dropdown v-model:visible="dropdownVisible">
  53. <a-button type="primary" class="search-button">
  54. <PlusOutlined />
  55. </a-button>
  56. <template #overlay>
  57. <a-menu>
  58. <a-menu-item key="1" @click="createGroup">创建群聊{{ parentSetUser?.userName }}</a-menu-item>
  59. <a-menu-item key="2" @click="subscribe">加人/订阅</a-menu-item>
  60. </a-menu>
  61. </template>
  62. </a-dropdown>
  63. </div>
  64. <div class="chat-center-body-container">
  65. <component
  66. :is="currentCenterComponent"
  67. :parentSetUser="parentSetUser"
  68. :friendsData="friendsData"
  69. :waitConfirmList="waitConfirmList"
  70. :chattingList="chattingList"
  71. :messageList="messageList"
  72. :autoSelectedChatting="autoSelectedChatting"
  73. :selectChatUser="selectChatUser"
  74. @update:waitConfirmList="newList => waitConfirmList = newList"
  75. @select-chat-user="handleSelectChatUser"
  76. @dblclick-user="onUserDblClick">
  77. </component>
  78. </div>
  79. </div>
  80. <div class="chat-right-container">
  81. <component :is="currentChatComponent" :selectChatUser="selectChatUser" :openVideo="openVideo" > </component>
  82. </div>
  83. </div>
  84. <!-- 添加好友/机器人 -->
  85. <a-modal
  86. v-model:visible="subscribeModalVisible"
  87. title="搜索好友/机器人/订阅公众号"
  88. :okButtonProps="{ hidden: true }"
  89. :cancelButtonProps="{ hidden: true }"
  90. >
  91. <a-input
  92. style="height: 40px; line-height: 40px; margin-top: 20px; text-indent: 2em;"
  93. v-model:value="subscribeQueryInput"
  94. placeholder="请输入用户名"
  95. @keydown.enter="searchAccount"
  96. />
  97. <p style="color: #666; margin-top: 20px;">搜索结果:</p>
  98. <div v-if="searchAccountInfo" class="search-account-card">
  99. <div>
  100. <a-avatar :src="searchAccountInfo.photo" :size="60" style="border: 2px solid white; border-radius: 50%;"/>
  101. </div>
  102. <div style="margin-left: 10px;">
  103. <div style="font-weight: 500; font-size: 15px; color: black;">{{ searchAccountInfo.userName }}</div>
  104. <div style="margin-top: 5px;">{{ searchAccountInfo.signature }}</div>
  105. <a-button type="primary" @click="doSubscribe" class="doSubscribeBtn">
  106. {{ searchAccountInfo.roleCode <= 2 ? '添加好友' : searchAccountInfo.roleCode == 5 ? '添加机器人' : '订阅公众号'}}
  107. </a-button>
  108. </div>
  109. </div>
  110. </a-modal>
  111. <!-- 创建群聊 -->
  112. <a-modal
  113. title="新建群聊"
  114. style="height: 400px; width: 600px; border-radius: 3px;"
  115. :visible="createGroupVisible"
  116. okText = "创 建"
  117. cancelText = "取 消"
  118. :okButtonProps="{ createGroupLoading }"
  119. @ok="handleCreateGroup"
  120. @cancel="handleCancelGroup"
  121. >
  122. <div style="display: flex; gap: 20px; margin-top: 30px;">
  123. <!-- 左侧好友列表 -->
  124. <div style="flex: 1; border-right: 1px solid #eee; padding-right: 20px;">
  125. <a-checkbox-group v-model:value="selectedFriends">
  126. <a-checkbox v-for="friend in friendsData.users?.accounts"
  127. :key="friend.userId"
  128. :value="friend.userId"
  129. class="group-friend-item">
  130. <img :src="friend.photo" alt="avatar" class="group-friend-avatar" />
  131. <span class="group-friend-name">{{ friend.userName }}</span>
  132. </a-checkbox>
  133. </a-checkbox-group>
  134. </div>
  135. <!-- 右侧已选列表 -->
  136. <div style="flex: 1; padding-left: 10px;">
  137. <div style="font-weight: 500; margin-bottom: 10px;">已选成员({{ selectedFriends.length }}人)</div>
  138. <div style="height: 300px; overflow-y: auto;">
  139. <div
  140. v-for="userId in selectedFriends"
  141. :key="userId"
  142. style="display: flex; align-items: center; padding: 6px;"
  143. >
  144. <a-avatar :src="getFriendPhoto(userId)" size="small" />
  145. <span style="margin-left: 8px;">{{ getFriendName(userId) }}</span>
  146. </div>
  147. <div
  148. v-if="selectedFriends.length === 0"
  149. style="color: #999; text-align: center; margin-top: 50px;"
  150. >
  151. 请从左侧选择好友
  152. </div>
  153. </div>
  154. </div>
  155. </div>
  156. </a-modal>
  157. <div class="video-offer-card" v-if="videoOfferCardVisible">
  158. <svg style="position: absolute;left: 20px; top: 15px;" t="1739202022913" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12218" width="32" height="32"><path d="M0 0m228.968944 0l566.062112 0q228.968944 0 228.968944 228.968944l0 566.062112q0 228.968944-228.968944 228.968944l-566.062112 0q-228.968944 0-228.968944-228.968944l0-566.062112q0-228.968944 228.968944-228.968944Z" fill="#FF9B5E" p-id="12219"></path><path d="M582.598758 528.99204c-13.568954-7.834554-13.568954-27.419031 0-35.252313l227.060869-131.093625c13.568954-7.834554 30.529193 1.957684 30.529193 17.62552v262.18725c0 15.667836-16.960239 25.460075-30.529193 17.626793L582.598758 528.99204z" fill="#FFFFFF" opacity=".5" p-id="12220"></path><path d="M249.321739 306.563975m38.161491 0l334.549068 0q38.161491 0 38.161491 38.161491l0 334.549068q0 38.161491-38.161491 38.161491l-334.549068 0q-38.161491 0-38.161491-38.161491l0-334.549068q0-38.161491 38.161491-38.161491Z" fill="#FFFFFF" p-id="12221"></path></svg>
  159. <p style="padding-left: 20px;">
  160. <b style="color: blue;">{{videoOfferUser?.userName}}</b> 给你发来了音视频邀请
  161. </p>
  162. <div style="margin-top: 15px; text-align: right;">
  163. <a-button type="primary" @click="rejectVideoOffer" style="width: 60px; background-color: #D34343;">拒 接</a-button>
  164. <a-button type="primary" @click="aggreeVideoOffer" style="width: 60px; background-color: #1FB759; margin-left: 20px;">接 听</a-button>
  165. </div>
  166. </div>
  167. <div class="video-offer-card" v-if="groupVideoOfferCardVisible">
  168. <svg style="position: absolute;left: 20px; top: 15px;" t="1739202022913" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12218" width="32" height="32"><path d="M0 0m228.968944 0l566.062112 0q228.968944 0 228.968944 228.968944l0 566.062112q0 228.968944-228.968944 228.968944l-566.062112 0q-228.968944 0-228.968944-228.968944l0-566.062112q0-228.968944 228.968944-228.968944Z" fill="#FF9B5E" p-id="12219"></path><path d="M582.598758 528.99204c-13.568954-7.834554-13.568954-27.419031 0-35.252313l227.060869-131.093625c13.568954-7.834554 30.529193 1.957684 30.529193 17.62552v262.18725c0 15.667836-16.960239 25.460075-30.529193 17.626793L582.598758 528.99204z" fill="#FFFFFF" opacity=".5" p-id="12220"></path><path d="M249.321739 306.563975m38.161491 0l334.549068 0q38.161491 0 38.161491 38.161491l0 334.549068q0 38.161491-38.161491 38.161491l-334.549068 0q-38.161491 0-38.161491-38.161491l0-334.549068q0-38.161491 38.161491-38.161491Z" fill="#FFFFFF" p-id="12221"></path></svg>
  169. <p style="padding-left: 20px;">
  170. <b>{{videoOfferProxyUser?.userName}}</b>在<b style="color: orangered;">{{videoOfferUser?.userName}}群组</b>内发起了音视频邀请
  171. </p>
  172. <div style="margin-top: 15px; text-align: right;">
  173. <a-button type="primary" @click="rejectVideoOffer" style="width: 60px; background-color: #D34343;">拒 接</a-button>
  174. <a-button type="primary" @click="aggreeVideoOffer" style="width: 60px; background-color: #1FB759; margin-left: 20px;">接 听</a-button>
  175. </div>
  176. </div>
  177. </template>
  178. <script>
  179. import '../css/custom-antd.css';
  180. import axios from 'axios';
  181. import { provide, defineComponent, ref, onMounted, onUnmounted, watch } from 'vue';
  182. import { PlusOutlined } from '@ant-design/icons-vue';
  183. import { Avatar, Input, Button, Dropdown, Menu, MenuItem, message } from 'ant-design-vue';
  184. import MessageList from '../views/MessageList.vue';
  185. import FriendList from '../views/FriendList.vue';
  186. import ChattingList from '../views/Chatting.vue';
  187. import WaitConfirmList from '../views/WaitConfirmList.vue';
  188. import ChatCore from '../views/ChatCore.vue';
  189. export default defineComponent({
  190. components: {
  191. 'a-input': Input,
  192. PlusOutlined,
  193. Input,
  194. Button,
  195. Dropdown,
  196. Menu,
  197. MenuItem,
  198. Avatar,
  199. MessageList,
  200. WaitConfirmList,
  201. FriendList,
  202. ChattingList,
  203. ChatCore
  204. },
  205. setup() {
  206. const userId = ref('');
  207. const loginUser = ref({});
  208. const photo = ref('');
  209. const searchInput = ref('');
  210. const dropdownVisible = ref(false);
  211. const oauthCode = ref(null);
  212. const socket = ref(null);
  213. const reconnectInterval = ref(5000); // 重连间隔时间,单位:毫秒
  214. const heartbeatInterval = ref(30000); // 心跳间隔时间,单位:毫秒
  215. const isConnected = ref(false);
  216. const messageQueue = ref([]); // 存储未发送的消息队列
  217. const inputRef = ref(null);
  218. const inputValue = ref('');
  219. const searchAccountInfo = ref(null)
  220. const subscribeModalVisible = ref(false)
  221. const subscribeQueryInput = ref('');
  222. const redPointStatus = ref('none');
  223. const currentCenterComponent = ref('ChattingList');
  224. const currentChatComponent = ref('ChatCore');
  225. // 定义响应式数据
  226. const activeIcon = ref('');
  227. const friendsData = ref({});
  228. const chattingList = ref([]);
  229. const messageList = ref([]);
  230. const waitConfirmList = ref([])
  231. const lastChatTime = ref('')
  232. const autoSelectedChatting = ref(true)
  233. const createGroupVisible = ref(false)
  234. const createGroupLoading = ref(false)
  235. const videoOfferUser = ref(null);
  236. const videoOfferProxyUser = ref(null);
  237. // 一对一音视频呼叫提醒显示状态
  238. const videoOfferCardVisible = ref(false);
  239. // 多人群聊音视频呼叫提醒显示状态
  240. const groupVideoOfferCardVisible = ref(false);
  241. const parentSetUser = ref(null);
  242. const openVideo = ref(false);
  243. // 用对象记录每个图标的颜色状态
  244. const iconColors = ref({
  245. message: '#28c940',
  246. user: 'white',
  247. bell: 'white',
  248. moment: 'white',
  249. setting: 'white'
  250. });
  251. // 存储当前对话选中的账号角色类型
  252. const selectChatUser = ref({});
  253. provide('loginUser', loginUser);
  254. const selectedFriends = ref([]);
  255. const friendsMap = ref(new Map()); // 用于快速查找好友信息
  256. // 获取好友信息映射
  257. const buildFriendsMap = () => {
  258. friendsMap.value.clear();
  259. friendsData.value.users?.accounts?.forEach(friend => {
  260. friendsMap.value.set(friend.userId, friend);
  261. });
  262. };
  263. // 获取好友名称
  264. const getFriendName = (userId) => {
  265. return friendsMap.value.get(userId)?.userName || '未知用户';
  266. };
  267. // 获取好友头像
  268. const getFriendPhoto = (userId) => {
  269. return friendsMap.value.get(userId)?.photo || '';
  270. };
  271. watch(friendsData, () => {
  272. buildFriendsMap();
  273. });
  274. const aggreeVideoOffer = () => {
  275. // 父组件记录呼叫人
  276. parentSetUser.value = videoOfferUser.value;
  277. selectChatUser.value = videoOfferUser.value;
  278. // 隐藏音视频呼叫提示
  279. videoOfferCardVisible.value = false;
  280. // 初始化自动打开音视频窗口参数
  281. openVideo.value = true;
  282. // 切换到对话列表
  283. currentCenterComponent.value = 'ChattingList';
  284. }
  285. const rejectVideoOffer = () => {
  286. currentCenterComponent.value = 'ChattingList';
  287. parentSetUser.value = null;
  288. videoOfferCardVisible.value = false;
  289. openVideo.value = false;
  290. }
  291. // 处理对话选择事件
  292. const handleSelectChatUser = (selectUser) => {
  293. selectChatUser.value = selectUser;
  294. };
  295. // 双击好友列表,选中用户对话
  296. const onUserDblClick = (selectUser) => {
  297. selectChatUser.value = selectUser;
  298. parentSetUser.value = selectUser;
  299. handleIconClick("message");
  300. }
  301. // 处理图标点击事件
  302. const handleIconClick = (iconName) => {
  303. activeIcon.value = iconName;
  304. // 将所有图标颜色重置为白色
  305. for (const key in iconColors.value) {
  306. iconColors.value[key] = 'white';
  307. }
  308. // 将点击的图标颜色设为绿色
  309. iconColors.value[iconName] = '#28c940';
  310. if(iconName === 'bell') {
  311. redPointStatus.value = 'none';
  312. }
  313. switch (iconName) {
  314. case 'message':
  315. currentCenterComponent.value = 'ChattingList';
  316. break;
  317. case 'user':
  318. currentCenterComponent.value = 'FriendList';
  319. break;
  320. case 'bell':
  321. currentCenterComponent.value = 'WaitConfirmList';
  322. break;
  323. case 'logout':
  324. logout();
  325. break;
  326. default:
  327. currentCenterComponent.value = 'ChattingList';
  328. }
  329. };
  330. // 处理鼠标悬停事件
  331. const handleIconHover = (iconName) => {
  332. if (activeIcon.value!== iconName) {
  333. iconColors.value[iconName] = '#28c940';
  334. }
  335. };
  336. // 处理鼠标离开事件
  337. const handleIconLeave = (iconName) => {
  338. if (activeIcon.value!== iconName) {
  339. iconColors.value[iconName] = 'white';
  340. }
  341. };
  342. const handleSendMessage = (event) => {
  343. if (inputValue.value) {
  344. console.log('发送消息:', inputValue.value);
  345. // 在这里添加发送消息的逻辑,例如将消息发送到服务器等
  346. inputValue.value = ''; // 清空输入框
  347. // 重新聚焦输入框
  348. inputRef.value.focus();
  349. event.preventDefault(); // 阻止回车键的默认行为(如换行)
  350. }
  351. };
  352. const createGroup = () => {
  353. console.log('创建群组操作');
  354. // 在此添加创建群组的具体逻辑
  355. dropdownVisible.value = false; // 关闭下拉菜单
  356. createGroupVisible.value = true;
  357. };
  358. const subscribe = () => {
  359. // 显示订阅/添加好友弹窗
  360. subscribeModalVisible.value = true;
  361. // 在此添加订阅的具体逻辑
  362. dropdownVisible.value = false; // 关闭下拉菜单
  363. };
  364. // 将 doSubscribe 定义为异步函数
  365. const doSubscribe = async () => {
  366. try {
  367. const response = await axios.post(`/client-service/chat/account-relation/subscribe/` + searchAccountInfo.value.userId, {
  368. headers: {
  369. 'oauth-code': oauthCode.value,
  370. 'origin-url': window.location.href
  371. }
  372. });
  373. if (response.data.code === 40001) {
  374. // 未登录
  375. window.location.href = response.data.redirect_url;
  376. } else if (response.data.code === 200) {
  377. message.info(searchAccountInfo.value.roleCode <= 2 ? '好友申请已发出' : searchAccountInfo.value.roleCode == 5 ? '成功添加机器人' : '成功订阅公众号');
  378. // 显示订阅/添加好友弹窗
  379. subscribeModalVisible.value = false;
  380. } else {
  381. message.error(response.data.msg);
  382. }
  383. } catch (error) {
  384. message.error('订阅失败');
  385. }
  386. }
  387. const logout = async () => {
  388. try {
  389. const response = await axios.get(`/client-service/chat/account/logout`, {
  390. headers: {
  391. 'oauth-code': oauthCode.value,
  392. 'origin-url': window.location.href
  393. }
  394. });
  395. if (response.data.code === 200) {
  396. window.location.href = response.data.data;
  397. }
  398. } catch (error) {
  399. message.error('退出失败');
  400. }
  401. }
  402. const searchAccount = () => {
  403. oauthCode.value = new URLSearchParams(window.location.search).get('oauthCode');
  404. axios.get(`/client-service/chat/account/query`, {
  405. // 这里可以添加请求的配置,例如 headers 或 params
  406. params: {
  407. account: subscribeQueryInput.value
  408. },
  409. headers: {
  410. 'Content-Type': 'application/json',
  411. 'origin-url': window.location.href,
  412. 'oauth-code': oauthCode.value
  413. }
  414. }).then(function (response) {
  415. if (response.data.code === 40001) {
  416. // 未登录
  417. window.location.href = response.data.redirect_url;
  418. } else if (response.data.code === 200 && response.data.data) {
  419. searchAccountInfo.value = response.data.data;
  420. if (searchAccountInfo.value === null) {
  421. message.error("账号不存在");
  422. }
  423. }
  424. }).catch(function (error) {
  425. // 处理错误
  426. console.error(error);
  427. });
  428. }
  429. // 加载待审核的好友申请列表
  430. const loadWaitConfirmList = () => {
  431. axios.get(`/client-service/chat/account-relation/wait-confirm/list`, {
  432. // 这里可以添加请求的配置,例如 headers 或 params
  433. headers: {
  434. 'Content-Type': 'application/json',
  435. 'origin-url': window.location.href,
  436. 'oauth-code': oauthCode.value
  437. }
  438. }).then(function (response) {
  439. if (response.data.code === 40001) {
  440. // 未登录
  441. window.location.href = response.data.redirect_url;
  442. } else if (response.data.code === 200) {
  443. waitConfirmList.value = response.data.data;
  444. if(waitConfirmList.value.length > 0) {
  445. redPointStatus.value = "block"
  446. }
  447. }
  448. }).catch(function (error) {
  449. // 处理错误
  450. console.error(error);
  451. });
  452. }
  453. const loadChattingList = () => {
  454. axios.get(`/client-service/chat/message/chatting/list`, {
  455. // 这里可以添加请求的配置,例如 headers 或 params
  456. params: {
  457. "lastChatTime": lastChatTime.value
  458. },
  459. headers: {
  460. 'Content-Type': 'application/json',
  461. 'origin-url': window.location.href,
  462. 'oauth-code': oauthCode.value
  463. }
  464. }).then(function (response) {
  465. if (response.data.code === 40001) {
  466. // 未登录
  467. window.location.href = response.data.redirect_url;
  468. } else if (response.data.code === 200) {
  469. chattingList.value = response.data.data;
  470. }
  471. }).catch(function (error) {
  472. // 处理错误
  473. console.error(error);
  474. });
  475. }
  476. const loadFriendList = () => {
  477. axios.get(`/client-service/chat/account-relation/list`, {
  478. // 这里可以添加请求的配置,例如 headers 或 params
  479. headers: {
  480. 'Content-Type': 'application/json',
  481. 'origin-url': window.location.href,
  482. 'oauth-code': oauthCode.value
  483. }
  484. }).then(function (response) {
  485. if (response.data.code === 40001) {
  486. // 未登录
  487. window.location.href = response.data.redirect_url;
  488. } else if (response.data.code === 200) {
  489. friendsData.value = response.data.data;
  490. }
  491. }).catch(function (error) {
  492. // 处理错误
  493. console.error(error);
  494. });
  495. }
  496. const loadCurrentUserInfo = () => {
  497. oauthCode.value = new URLSearchParams(window.location.search).get('oauthCode');
  498. axios.get(`/client-service/chat/account/current/info`, {
  499. // 这里可以添加请求的配置,例如 headers 或 params
  500. headers: {
  501. 'Content-Type': 'application/json',
  502. 'origin-url': window.location.href,
  503. 'oauth-code': oauthCode.value
  504. }
  505. }).then(function (response) {
  506. if (response.data.code === 40001) {
  507. // 未登录
  508. window.location.href = response.data.redirect_url;
  509. } else if (response.data.code === 200) {
  510. userId.value = response.data.data.userId;
  511. photo.value = response.data.data.photo;
  512. loginUser.value = response.data.data;
  513. loadChattingList();
  514. loadFriendList();
  515. loadWaitConfirmList();
  516. connectWebSocket();
  517. }
  518. }).catch(function (error) {
  519. // 处理错误
  520. console.error(error);
  521. });
  522. }
  523. onMounted(() => {
  524. });
  525. onUnmounted(() => {
  526. if (eventSource) {
  527. eventSource.close();
  528. }
  529. });
  530. const connectWebSocket = () => {
  531. socket.value = new WebSocket(`/connect-service/ws/chat/PC_WEB_INDEX/`+userId.value);
  532. socket.value.onopen = () => {
  533. console.log('WebSocket 已连接');
  534. isConnected.value = true;
  535. // 发送队列中的消息
  536. messageQueue.value.forEach(message => socket.value.send(message));
  537. messageQueue.value = [];
  538. startHeartbeat();
  539. };
  540. socket.value.onmessage = (event) => {
  541. console.log('App组件收到消息:', event.data);
  542. // 在此处理收到的消息
  543. const eventMessage = JSON.parse(event.data);
  544. const messageType = eventMessage.type;
  545. if (messageType === 5) {
  546. // 申请添加好友,红点提醒
  547. redPointStatus.value = "block";
  548. loadWaitConfirmList();
  549. }
  550. else if (messageType === 7) {
  551. autoSelectedChatting.value = false;
  552. loadChattingList();
  553. }
  554. else if (messageType === 8) {
  555. // 一对一音视频邀请通知
  556. videoOfferUser.value = eventMessage.sender;
  557. videoOfferCardVisible.value = true;
  558. }
  559. else if (messageType === 9) {
  560. // 群聊多人音视频邀请通知
  561. videoOfferUser.value = eventMessage.sender;
  562. videoOfferProxyUser.value = eventMessage.proxySender;
  563. groupVideoOfferCardVisible.value = true;
  564. }
  565. };
  566. socket.value.onerror = (error) => {
  567. console.error('WebSocket 错误:', error);
  568. isConnected.value = false;
  569. // 关闭当前连接
  570. socket.value.close();
  571. // 尝试重连
  572. setTimeout(connectWebSocket, reconnectInterval.value);
  573. };
  574. socket.value.onclose = () => {
  575. console.log('WebSocket 已关闭');
  576. isConnected.value = false;
  577. // 尝试重连
  578. setTimeout(connectWebSocket, reconnectInterval.value);
  579. };
  580. };
  581. const startHeartbeat = () => {
  582. const heartbeat = () => {
  583. if (isConnected.value) {
  584. socket.value.send('ping'); // 发送心跳包
  585. }
  586. };
  587. setInterval(heartbeat, heartbeatInterval.value);
  588. };
  589. const sendMessage = (message) => {
  590. if (isConnected.value) {
  591. socket.value.send(message);
  592. } else {
  593. messageQueue.value.push(message);
  594. }
  595. };
  596. const handleCreateGroup = async () => {
  597. if (selectedFriends.value.length < 3) {
  598. message.error("请至少选择3位好友");
  599. return;
  600. }
  601. createGroupLoading.value = true;
  602. try {
  603. const response = await axios.post(`/client-service/chat/account-relation/createGroup`,
  604. {
  605. userIds: selectedFriends.value
  606. },
  607. {
  608. headers: {
  609. 'oauth-code': oauthCode.value,
  610. 'origin-url': window.location.href
  611. }
  612. });
  613. if (response.data.code === 200) {
  614. createGroupVisible.value = false;
  615. selectedFriends.value = [];
  616. createGroupLoading.value = false;
  617. } else {
  618. message.error(response.data.msg);
  619. createGroupLoading.value = false;
  620. }
  621. } catch (error) {
  622. message.error("创建群聊失败");
  623. createGroupLoading.value = false;
  624. }
  625. };
  626. const handleCancelGroup = () => {
  627. createGroupVisible.value = false;
  628. }
  629. onMounted(() => {
  630. loadCurrentUserInfo();
  631. });
  632. onUnmounted(() => {
  633. if (socket.value) {
  634. socket.value.close();
  635. }
  636. });
  637. return {
  638. openVideo,
  639. parentSetUser,
  640. videoOfferCardVisible,
  641. groupVideoOfferCardVisible,
  642. videoOfferUser,
  643. videoOfferProxyUser,
  644. userId,
  645. photo,
  646. loginUser,
  647. searchInput,
  648. dropdownVisible,
  649. subscribeQueryInput,
  650. searchAccountInfo,
  651. subscribeModalVisible,
  652. oauthCode,
  653. inputRef,
  654. inputValue,
  655. activeIcon,
  656. iconColors,
  657. redPointStatus,
  658. chattingList,
  659. messageList,
  660. waitConfirmList,
  661. currentCenterComponent,
  662. currentChatComponent,
  663. friendsData,
  664. lastChatTime,
  665. selectChatUser,
  666. autoSelectedChatting,
  667. createGroupVisible,
  668. selectedFriends,
  669. createGroupLoading,
  670. rejectVideoOffer,
  671. aggreeVideoOffer,
  672. getFriendName,
  673. getFriendPhoto,
  674. handleCreateGroup,
  675. getFriendName,
  676. getFriendPhoto,
  677. handleCreateGroup,
  678. handleCancelGroup,
  679. logout,
  680. handleIconClick,
  681. handleIconHover,
  682. handleIconLeave,
  683. sendMessage,
  684. createGroup,
  685. subscribe,
  686. handleSendMessage,
  687. searchAccount,
  688. doSubscribe,
  689. loadFriendList,
  690. loadCurrentUserInfo,
  691. loadChattingList,
  692. handleSelectChatUser,
  693. onUserDblClick
  694. };
  695. }
  696. });
  697. </script>
  698. <style scoped>
  699. * {
  700. margin: 0px;
  701. padding: 0px;
  702. }
  703. .mac-dots-container {
  704. margin-top: 10px;
  705. display: flex;
  706. justify-content: flex-start;
  707. align-items: center;
  708. padding: 5px;
  709. width: 60px;
  710. margin-left: 5px;
  711. }
  712. .mac-dot {
  713. width: 10px;
  714. height: 10px;
  715. border-radius: 50%;
  716. margin-right: 5px;
  717. }
  718. .red {
  719. background-color: #ff5f57;
  720. }
  721. .yellow {
  722. background-color: #ffbd2e;
  723. }
  724. .green {
  725. background-color: #28c940;
  726. }
  727. .chat-container {
  728. position: absolute;
  729. width: 1100px;
  730. height: 700px;
  731. left: 50%;
  732. top: 55%;
  733. margin-left: -550px;
  734. margin-top: -400px;
  735. border-radius: 6px;
  736. overflow: hidden;
  737. /* box-shadow: 0px 0px 10px #eeeded; */
  738. background-color: white;
  739. border: 1px solid #e3e3e3;
  740. }
  741. .chat-left-container {
  742. position: absolute;
  743. top: 0px;
  744. left: 0px;
  745. width: 60px;
  746. height: 100%;
  747. background-color: black;
  748. }
  749. .chat-center-container {
  750. position: absolute;
  751. top: 0px;
  752. left: 60px;
  753. width: 270px;
  754. height: 100%;
  755. border-right: 1px solid #e3e3e3;
  756. background-color: #f4f4f4;
  757. }
  758. .chat-right-container {
  759. position: absolute;
  760. top: 0px;
  761. right: 0px;
  762. width: calc(100% - 330px);
  763. height: 100%;
  764. }
  765. .chat-center-header-container {
  766. position: absolute;
  767. top: 0px;
  768. left: 0px;
  769. width: 100%;
  770. height: 50px;
  771. border-bottom: 1px solid #e3e3e3;
  772. background-color: #f9f9f9;
  773. display: flex;
  774. align-items: center;
  775. justify-content: space-between;
  776. }
  777. .search-input {
  778. height: 30px;
  779. width: 210px;
  780. margin-left: 10px;
  781. background-color: #e5e5e5;
  782. border: none;
  783. }
  784. .search-button {
  785. width: 28px;
  786. height: 28px;
  787. margin-right: 10px;
  788. background-color: black;
  789. }
  790. .search-account-card {
  791. position: relative;
  792. margin-top: 10px;
  793. display: flex;
  794. align-items: center;
  795. border: 1px solid rgb(239, 236, 236);
  796. padding: 20px;
  797. }
  798. .doSubscribeBtn {
  799. position: absolute;
  800. background-color: black;
  801. right: 8px;
  802. bottom: 8px;
  803. padding: 2px 10px;
  804. }
  805. .chat-menus {
  806. display: flex;
  807. flex-direction: column;
  808. margin-top: 30px;
  809. width: 18px;
  810. margin-left: 21px;
  811. }
  812. .chat-menus .anticon {
  813. color: white;
  814. font-size: 19px;
  815. margin-bottom: 30px;
  816. }
  817. .bell-with-notification {
  818. width: 20px;
  819. height: auto;
  820. position: relative;
  821. }
  822. .message-red-point {
  823. position: absolute;
  824. content: '';
  825. right: -4px;
  826. top: -4px;
  827. width: 8px;
  828. height: 8px;
  829. background-color: red;
  830. border-radius: 50%;
  831. display: none;
  832. }
  833. .chat-center-body-container {
  834. position: relative;
  835. width: 100%;
  836. height: 100%;
  837. margin-top: 50px;
  838. overflow-x: hidden;
  839. }
  840. .chat-center-body-container::-webkit-scrollbar {/*滚动条整体样式*/
  841. width: 0px; /*高宽分别对应横竖滚动条的尺寸*/
  842. height: 0px;
  843. }
  844. .chat-center-body-container::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
  845. border-radius: 10px;
  846. background-color: transparent;
  847. background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .2) 50%, rgba(255, 255, 255, .2) 75%, transparent 75%, transparent);
  848. }
  849. .chat-center-body-container::-webkit-scrollbar-track {/*滚动条里面轨道*/
  850. -webkit-box-shadow: inset 0 0 1px rgba(0,0,0,0.2);
  851. /*border-radius: 10px;*/
  852. background: #EDEDED;
  853. }
  854. .group-friend-avatar {
  855. width: 30px;
  856. border-radius: 5px;
  857. margin-right: 5px;
  858. }
  859. .group-friend-item {
  860. display: flex;
  861. align-items: center;
  862. gap: 10px;
  863. width: 100%;
  864. margin-bottom: 10px; /* 添加间距 */
  865. }
  866. .group-friends-list {
  867. max-height: 350px;
  868. overflow-x: hidden;
  869. overflow-y: scroll;
  870. }
  871. .group-friends-list::-webkit-scrollbar {/*滚动条整体样式*/
  872. width: 0px; /*高宽分别对应横竖滚动条的尺寸*/
  873. height: 0px;
  874. }
  875. .group-friends-list::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
  876. border-radius: 10px;
  877. background-color: transparent;
  878. background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .2) 50%, rgba(255, 255, 255, .2) 75%, transparent 75%, transparent);
  879. }
  880. .group-friends-list::-webkit-scrollbar-track {/*滚动条里面轨道*/
  881. -webkit-box-shadow: inset 0 0 1px rgba(0,0,0,0.2);
  882. /*border-radius: 10px;*/
  883. background: #EDEDED;
  884. }
  885. .video-offer-card {
  886. position: fixed;
  887. padding: 20px 40px;
  888. border-radius: 8px;
  889. overflow: hidden;
  890. z-index: 2999;
  891. right: 50px;
  892. top: 50px;
  893. background-color: white;
  894. border: 1px solid rgb(241, 240, 240);
  895. box-shadow: 0px 0px 30px rgb(215, 215, 215);
  896. }
  897. </style>