123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942 |
- <template >
- <h3 style="position: absolute; left: 50px; top: 30px;">
- <i>WebChat</i>
- </h3>
- <div class="chat-container">
- <div class="chat-left-container">
- <div class="mac-dots-container">
- <div class="mac-dot red"></div>
- <div class="mac-dot yellow"></div>
- <div class="mac-dot green"></div>
- </div>
- <a-avatar :src="photo" :size="40" style="margin-left: 10px; margin-top: 20px; border: 2px solid white; border-radius: 50%;"/>
- <div class="chat-menus">
- <MessageOutlined
- @click="handleIconClick('message')"
- :style="{ color: iconColors.message }"
- @mouseover="handleIconHover('message')"
- @mouseout="handleIconLeave('message')"
- />
- <UserOutlined
- @click="handleIconClick('user')"
- :style="{ color: iconColors.user }"
- @mouseover="handleIconHover('user')"
- @mouseout="handleIconLeave('user')"
- />
- <div class="bell-with-notification" :width="20">
- <BellOutlined
- @click="handleIconClick('bell')"
- :style="{ color: iconColors.bell }"
- @mouseover="handleIconHover('bell')"
- @mouseout="handleIconLeave('bell')"
- />
- <span class="message-red-point" :style="{ display: redPointStatus }"></span>
- </div>
- <WechatOutlined
- @click="handleIconClick('moment')"
- :style="{ color: iconColors.moment }"
- @mouseover="handleIconHover('moment')"
- @mouseout="handleIconLeave('moment')"
- />
- <LoginOutlined style="position: absolute; bottom: 20px"
- @click="handleIconClick('logout')"
- :style="{ color: iconColors.setting }"
- @mouseover="handleIconHover('logout')"
- @mouseout="handleIconLeave('logout')"
- />
- </div>
- </div>
- <div class="chat-center-container">
- <div class="chat-center-header-container">
- <a-input v-model:value="searchInput" placeholder="搜索" class="search-input" />
- <a-dropdown v-model:visible="dropdownVisible">
- <a-button type="primary" class="search-button">
- <PlusOutlined />
- </a-button>
- <template #overlay>
- <a-menu>
- <a-menu-item key="1" @click="createGroup">创建群聊{{ parentSetUser?.userName }}</a-menu-item>
- <a-menu-item key="2" @click="subscribe">加人/订阅</a-menu-item>
- </a-menu>
- </template>
- </a-dropdown>
- </div>
- <div class="chat-center-body-container">
- <component
- :is="currentCenterComponent"
- :parentSetUser="parentSetUser"
- :friendsData="friendsData"
- :waitConfirmList="waitConfirmList"
- :chattingList="chattingList"
- :messageList="messageList"
- :autoSelectedChatting="autoSelectedChatting"
- :selectChatUser="selectChatUser"
- @update:waitConfirmList="newList => waitConfirmList = newList"
- @select-chat-user="handleSelectChatUser"
- @dblclick-user="onUserDblClick">
- </component>
- </div>
- </div>
- <div class="chat-right-container">
- <component :is="currentChatComponent" :selectChatUser="selectChatUser" :openVideo="openVideo" > </component>
- </div>
- </div>
- <!-- 添加好友/机器人 -->
- <a-modal
- v-model:visible="subscribeModalVisible"
- title="搜索好友/机器人/订阅公众号"
- :okButtonProps="{ hidden: true }"
- :cancelButtonProps="{ hidden: true }"
- >
- <a-input
- style="height: 40px; line-height: 40px; margin-top: 20px; text-indent: 2em;"
- v-model:value="subscribeQueryInput"
- placeholder="请输入用户名"
- @keydown.enter="searchAccount"
- />
- <p style="color: #666; margin-top: 20px;">搜索结果:</p>
- <div v-if="searchAccountInfo" class="search-account-card">
- <div>
- <a-avatar :src="searchAccountInfo.photo" :size="60" style="border: 2px solid white; border-radius: 50%;"/>
- </div>
- <div style="margin-left: 10px;">
- <div style="font-weight: 500; font-size: 15px; color: black;">{{ searchAccountInfo.userName }}</div>
- <div style="margin-top: 5px;">{{ searchAccountInfo.signature }}</div>
- <a-button type="primary" @click="doSubscribe" class="doSubscribeBtn">
- {{ searchAccountInfo.roleCode <= 2 ? '添加好友' : searchAccountInfo.roleCode == 5 ? '添加机器人' : '订阅公众号'}}
- </a-button>
- </div>
- </div>
- </a-modal>
- <!-- 创建群聊 -->
- <a-modal
- title="新建群聊"
- style="height: 400px; width: 600px; border-radius: 3px;"
- :visible="createGroupVisible"
- okText = "创 建"
- cancelText = "取 消"
- :okButtonProps="{ createGroupLoading }"
- @ok="handleCreateGroup"
- @cancel="handleCancelGroup"
- >
- <div style="display: flex; gap: 20px; margin-top: 30px;">
- <!-- 左侧好友列表 -->
- <div style="flex: 1; border-right: 1px solid #eee; padding-right: 20px;">
- <a-checkbox-group v-model:value="selectedFriends">
- <a-checkbox v-for="friend in friendsData.users?.accounts"
- :key="friend.userId"
- :value="friend.userId"
- class="group-friend-item">
- <img :src="friend.photo" alt="avatar" class="group-friend-avatar" />
- <span class="group-friend-name">{{ friend.userName }}</span>
- </a-checkbox>
- </a-checkbox-group>
- </div>
- <!-- 右侧已选列表 -->
- <div style="flex: 1; padding-left: 10px;">
- <div style="font-weight: 500; margin-bottom: 10px;">已选成员({{ selectedFriends.length }}人)</div>
- <div style="height: 300px; overflow-y: auto;">
- <div
- v-for="userId in selectedFriends"
- :key="userId"
- style="display: flex; align-items: center; padding: 6px;"
- >
- <a-avatar :src="getFriendPhoto(userId)" size="small" />
- <span style="margin-left: 8px;">{{ getFriendName(userId) }}</span>
- </div>
- <div
- v-if="selectedFriends.length === 0"
- style="color: #999; text-align: center; margin-top: 50px;"
- >
- 请从左侧选择好友
- </div>
- </div>
- </div>
- </div>
- </a-modal>
- <div class="video-offer-card" v-if="videoOfferCardVisible">
- <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>
- <p style="padding-left: 20px;">
- <b style="color: blue;">{{videoOfferUser?.userName}}</b> 给你发来了音视频邀请
- </p>
- <div style="margin-top: 15px; text-align: right;">
- <a-button type="primary" @click="rejectVideoOffer" style="width: 60px; background-color: #D34343;">拒 接</a-button>
- <a-button type="primary" @click="aggreeVideoOffer" style="width: 60px; background-color: #1FB759; margin-left: 20px;">接 听</a-button>
- </div>
- </div>
- <div class="video-offer-card" v-if="groupVideoOfferCardVisible">
- <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>
- <p style="padding-left: 20px;">
- <b>{{videoOfferProxyUser?.userName}}</b>在<b style="color: orangered;">{{videoOfferUser?.userName}}群组</b>内发起了音视频邀请
- </p>
- <div style="margin-top: 15px; text-align: right;">
- <a-button type="primary" @click="rejectVideoOffer" style="width: 60px; background-color: #D34343;">拒 接</a-button>
- <a-button type="primary" @click="aggreeVideoOffer" style="width: 60px; background-color: #1FB759; margin-left: 20px;">接 听</a-button>
- </div>
- </div>
- </template>
- <script>
- import '../css/custom-antd.css';
- import axios from 'axios';
- import { provide, defineComponent, ref, onMounted, onUnmounted, watch } from 'vue';
- import { PlusOutlined } from '@ant-design/icons-vue';
- import { Avatar, Input, Button, Dropdown, Menu, MenuItem, message } from 'ant-design-vue';
- import MessageList from '../views/MessageList.vue';
- import FriendList from '../views/FriendList.vue';
- import ChattingList from '../views/Chatting.vue';
- import WaitConfirmList from '../views/WaitConfirmList.vue';
- import ChatCore from '../views/ChatCore.vue';
- export default defineComponent({
- components: {
- 'a-input': Input,
- PlusOutlined,
- Input,
- Button,
- Dropdown,
- Menu,
- MenuItem,
- Avatar,
- MessageList,
- WaitConfirmList,
- FriendList,
- ChattingList,
- ChatCore
- },
- setup() {
- const userId = ref('');
- const loginUser = ref({});
- const photo = ref('');
- const searchInput = ref('');
- const dropdownVisible = ref(false);
- const oauthCode = ref(null);
- const socket = ref(null);
- const reconnectInterval = ref(5000); // 重连间隔时间,单位:毫秒
- const heartbeatInterval = ref(30000); // 心跳间隔时间,单位:毫秒
- const isConnected = ref(false);
- const messageQueue = ref([]); // 存储未发送的消息队列
- const inputRef = ref(null);
- const inputValue = ref('');
- const searchAccountInfo = ref(null)
- const subscribeModalVisible = ref(false)
- const subscribeQueryInput = ref('');
- const redPointStatus = ref('none');
- const currentCenterComponent = ref('ChattingList');
- const currentChatComponent = ref('ChatCore');
- // 定义响应式数据
- const activeIcon = ref('');
- const friendsData = ref({});
- const chattingList = ref([]);
- const messageList = ref([]);
- const waitConfirmList = ref([])
- const lastChatTime = ref('')
- const autoSelectedChatting = ref(true)
- const createGroupVisible = ref(false)
- const createGroupLoading = ref(false)
- const videoOfferUser = ref(null);
- const videoOfferProxyUser = ref(null);
- // 一对一音视频呼叫提醒显示状态
- const videoOfferCardVisible = ref(false);
- // 多人群聊音视频呼叫提醒显示状态
- const groupVideoOfferCardVisible = ref(false);
- const parentSetUser = ref(null);
- const openVideo = ref(false);
- // 用对象记录每个图标的颜色状态
- const iconColors = ref({
- message: '#28c940',
- user: 'white',
- bell: 'white',
- moment: 'white',
- setting: 'white'
- });
- // 存储当前对话选中的账号角色类型
- const selectChatUser = ref({});
- provide('loginUser', loginUser);
- const selectedFriends = ref([]);
- const friendsMap = ref(new Map()); // 用于快速查找好友信息
- // 获取好友信息映射
- const buildFriendsMap = () => {
- friendsMap.value.clear();
- friendsData.value.users?.accounts?.forEach(friend => {
- friendsMap.value.set(friend.userId, friend);
- });
- };
- // 获取好友名称
- const getFriendName = (userId) => {
- return friendsMap.value.get(userId)?.userName || '未知用户';
- };
- // 获取好友头像
- const getFriendPhoto = (userId) => {
- return friendsMap.value.get(userId)?.photo || '';
- };
-
- watch(friendsData, () => {
- buildFriendsMap();
- });
- const aggreeVideoOffer = () => {
- // 父组件记录呼叫人
- parentSetUser.value = videoOfferUser.value;
- selectChatUser.value = videoOfferUser.value;
- // 隐藏音视频呼叫提示
- videoOfferCardVisible.value = false;
- // 初始化自动打开音视频窗口参数
- openVideo.value = true;
- // 切换到对话列表
- currentCenterComponent.value = 'ChattingList';
- }
- const rejectVideoOffer = () => {
- currentCenterComponent.value = 'ChattingList';
- parentSetUser.value = null;
- videoOfferCardVisible.value = false;
- openVideo.value = false;
- }
- // 处理对话选择事件
- const handleSelectChatUser = (selectUser) => {
- selectChatUser.value = selectUser;
- };
-
- // 双击好友列表,选中用户对话
- const onUserDblClick = (selectUser) => {
- selectChatUser.value = selectUser;
- parentSetUser.value = selectUser;
- handleIconClick("message");
- }
- // 处理图标点击事件
- const handleIconClick = (iconName) => {
- activeIcon.value = iconName;
- // 将所有图标颜色重置为白色
- for (const key in iconColors.value) {
- iconColors.value[key] = 'white';
- }
- // 将点击的图标颜色设为绿色
- iconColors.value[iconName] = '#28c940';
- if(iconName === 'bell') {
- redPointStatus.value = 'none';
- }
- switch (iconName) {
- case 'message':
- currentCenterComponent.value = 'ChattingList';
- break;
- case 'user':
- currentCenterComponent.value = 'FriendList';
- break;
- case 'bell':
- currentCenterComponent.value = 'WaitConfirmList';
- break;
- case 'logout':
- logout();
- break;
- default:
- currentCenterComponent.value = 'ChattingList';
- }
- };
- // 处理鼠标悬停事件
- const handleIconHover = (iconName) => {
- if (activeIcon.value!== iconName) {
- iconColors.value[iconName] = '#28c940';
- }
- };
- // 处理鼠标离开事件
- const handleIconLeave = (iconName) => {
- if (activeIcon.value!== iconName) {
- iconColors.value[iconName] = 'white';
- }
- };
- const handleSendMessage = (event) => {
- if (inputValue.value) {
- console.log('发送消息:', inputValue.value);
- // 在这里添加发送消息的逻辑,例如将消息发送到服务器等
- inputValue.value = ''; // 清空输入框
- // 重新聚焦输入框
- inputRef.value.focus();
- event.preventDefault(); // 阻止回车键的默认行为(如换行)
- }
- };
- const createGroup = () => {
- console.log('创建群组操作');
- // 在此添加创建群组的具体逻辑
- dropdownVisible.value = false; // 关闭下拉菜单
- createGroupVisible.value = true;
- };
- const subscribe = () => {
- // 显示订阅/添加好友弹窗
- subscribeModalVisible.value = true;
- // 在此添加订阅的具体逻辑
- dropdownVisible.value = false; // 关闭下拉菜单
- };
-
- // 将 doSubscribe 定义为异步函数
- const doSubscribe = async () => {
- try {
- const response = await axios.post(`/client-service/chat/account-relation/subscribe/` + searchAccountInfo.value.userId, {
- headers: {
- 'oauth-code': oauthCode.value,
- 'origin-url': window.location.href
- }
- });
- if (response.data.code === 40001) {
- // 未登录
- window.location.href = response.data.redirect_url;
- } else if (response.data.code === 200) {
- message.info(searchAccountInfo.value.roleCode <= 2 ? '好友申请已发出' : searchAccountInfo.value.roleCode == 5 ? '成功添加机器人' : '成功订阅公众号');
- // 显示订阅/添加好友弹窗
- subscribeModalVisible.value = false;
- } else {
- message.error(response.data.msg);
- }
- } catch (error) {
- message.error('订阅失败');
- }
- }
- const logout = async () => {
- try {
- const response = await axios.get(`/client-service/chat/account/logout`, {
- headers: {
- 'oauth-code': oauthCode.value,
- 'origin-url': window.location.href
- }
- });
- if (response.data.code === 200) {
- window.location.href = response.data.data;
- }
- } catch (error) {
- message.error('退出失败');
- }
- }
- const searchAccount = () => {
- oauthCode.value = new URLSearchParams(window.location.search).get('oauthCode');
- axios.get(`/client-service/chat/account/query`, {
- // 这里可以添加请求的配置,例如 headers 或 params
- params: {
- account: subscribeQueryInput.value
- },
- headers: {
- 'Content-Type': 'application/json',
- 'origin-url': window.location.href,
- 'oauth-code': oauthCode.value
- }
- }).then(function (response) {
- if (response.data.code === 40001) {
- // 未登录
- window.location.href = response.data.redirect_url;
- } else if (response.data.code === 200 && response.data.data) {
- searchAccountInfo.value = response.data.data;
- if (searchAccountInfo.value === null) {
- message.error("账号不存在");
- }
- }
- }).catch(function (error) {
- // 处理错误
- console.error(error);
- });
- }
- // 加载待审核的好友申请列表
- const loadWaitConfirmList = () => {
- axios.get(`/client-service/chat/account-relation/wait-confirm/list`, {
- // 这里可以添加请求的配置,例如 headers 或 params
- headers: {
- 'Content-Type': 'application/json',
- 'origin-url': window.location.href,
- 'oauth-code': oauthCode.value
- }
- }).then(function (response) {
- if (response.data.code === 40001) {
- // 未登录
- window.location.href = response.data.redirect_url;
- } else if (response.data.code === 200) {
- waitConfirmList.value = response.data.data;
- if(waitConfirmList.value.length > 0) {
- redPointStatus.value = "block"
- }
- }
- }).catch(function (error) {
- // 处理错误
- console.error(error);
- });
- }
- const loadChattingList = () => {
- axios.get(`/client-service/chat/message/chatting/list`, {
- // 这里可以添加请求的配置,例如 headers 或 params
- params: {
- "lastChatTime": lastChatTime.value
- },
- headers: {
- 'Content-Type': 'application/json',
- 'origin-url': window.location.href,
- 'oauth-code': oauthCode.value
- }
- }).then(function (response) {
- if (response.data.code === 40001) {
- // 未登录
- window.location.href = response.data.redirect_url;
- } else if (response.data.code === 200) {
- chattingList.value = response.data.data;
- }
- }).catch(function (error) {
- // 处理错误
- console.error(error);
- });
- }
- const loadFriendList = () => {
- axios.get(`/client-service/chat/account-relation/list`, {
- // 这里可以添加请求的配置,例如 headers 或 params
- headers: {
- 'Content-Type': 'application/json',
- 'origin-url': window.location.href,
- 'oauth-code': oauthCode.value
- }
- }).then(function (response) {
- if (response.data.code === 40001) {
- // 未登录
- window.location.href = response.data.redirect_url;
- } else if (response.data.code === 200) {
- friendsData.value = response.data.data;
- }
- }).catch(function (error) {
- // 处理错误
- console.error(error);
- });
- }
- const loadCurrentUserInfo = () => {
- oauthCode.value = new URLSearchParams(window.location.search).get('oauthCode');
- axios.get(`/client-service/chat/account/current/info`, {
- // 这里可以添加请求的配置,例如 headers 或 params
- headers: {
- 'Content-Type': 'application/json',
- 'origin-url': window.location.href,
- 'oauth-code': oauthCode.value
- }
- }).then(function (response) {
- if (response.data.code === 40001) {
- // 未登录
- window.location.href = response.data.redirect_url;
- } else if (response.data.code === 200) {
- userId.value = response.data.data.userId;
- photo.value = response.data.data.photo;
- loginUser.value = response.data.data;
- loadChattingList();
- loadFriendList();
- loadWaitConfirmList();
- connectWebSocket();
- }
- }).catch(function (error) {
- // 处理错误
- console.error(error);
- });
- }
- onMounted(() => {
-
- });
- onUnmounted(() => {
- if (eventSource) {
- eventSource.close();
- }
- });
- const connectWebSocket = () => {
- socket.value = new WebSocket(`/connect-service/ws/chat/PC_WEB_INDEX/`+userId.value);
- socket.value.onopen = () => {
- console.log('WebSocket 已连接');
- isConnected.value = true;
- // 发送队列中的消息
- messageQueue.value.forEach(message => socket.value.send(message));
- messageQueue.value = [];
- startHeartbeat();
- };
- socket.value.onmessage = (event) => {
- console.log('App组件收到消息:', event.data);
- // 在此处理收到的消息
- const eventMessage = JSON.parse(event.data);
- const messageType = eventMessage.type;
- if (messageType === 5) {
- // 申请添加好友,红点提醒
- redPointStatus.value = "block";
- loadWaitConfirmList();
- }
- else if (messageType === 7) {
- autoSelectedChatting.value = false;
- loadChattingList();
- }
- else if (messageType === 8) {
- // 一对一音视频邀请通知
- videoOfferUser.value = eventMessage.sender;
- videoOfferCardVisible.value = true;
- }
- else if (messageType === 9) {
- // 群聊多人音视频邀请通知
- videoOfferUser.value = eventMessage.sender;
- videoOfferProxyUser.value = eventMessage.proxySender;
- groupVideoOfferCardVisible.value = true;
- }
- };
- socket.value.onerror = (error) => {
- console.error('WebSocket 错误:', error);
- isConnected.value = false;
- // 关闭当前连接
- socket.value.close();
- // 尝试重连
- setTimeout(connectWebSocket, reconnectInterval.value);
- };
- socket.value.onclose = () => {
- console.log('WebSocket 已关闭');
- isConnected.value = false;
- // 尝试重连
- setTimeout(connectWebSocket, reconnectInterval.value);
- };
- };
- const startHeartbeat = () => {
- const heartbeat = () => {
- if (isConnected.value) {
- socket.value.send('ping'); // 发送心跳包
- }
- };
- setInterval(heartbeat, heartbeatInterval.value);
- };
- const sendMessage = (message) => {
- if (isConnected.value) {
- socket.value.send(message);
- } else {
- messageQueue.value.push(message);
- }
- };
- const handleCreateGroup = async () => {
- if (selectedFriends.value.length < 3) {
- message.error("请至少选择3位好友");
- return;
- }
- createGroupLoading.value = true;
- try {
- const response = await axios.post(`/client-service/chat/account-relation/createGroup`,
- {
- userIds: selectedFriends.value
- },
- {
- headers: {
- 'oauth-code': oauthCode.value,
- 'origin-url': window.location.href
- }
- });
- if (response.data.code === 200) {
- createGroupVisible.value = false;
- selectedFriends.value = [];
- createGroupLoading.value = false;
- } else {
- message.error(response.data.msg);
- createGroupLoading.value = false;
- }
- } catch (error) {
- message.error("创建群聊失败");
- createGroupLoading.value = false;
- }
- };
- const handleCancelGroup = () => {
- createGroupVisible.value = false;
- }
- onMounted(() => {
- loadCurrentUserInfo();
- });
- onUnmounted(() => {
- if (socket.value) {
- socket.value.close();
- }
- });
-
- return {
- openVideo,
- parentSetUser,
- videoOfferCardVisible,
- groupVideoOfferCardVisible,
- videoOfferUser,
- videoOfferProxyUser,
- userId,
- photo,
- loginUser,
- searchInput,
- dropdownVisible,
- subscribeQueryInput,
- searchAccountInfo,
- subscribeModalVisible,
- oauthCode,
- inputRef,
- inputValue,
- activeIcon,
- iconColors,
- redPointStatus,
- chattingList,
- messageList,
- waitConfirmList,
- currentCenterComponent,
- currentChatComponent,
- friendsData,
- lastChatTime,
- selectChatUser,
- autoSelectedChatting,
- createGroupVisible,
- selectedFriends,
- createGroupLoading,
- rejectVideoOffer,
- aggreeVideoOffer,
- getFriendName,
- getFriendPhoto,
- handleCreateGroup,
- getFriendName,
- getFriendPhoto,
- handleCreateGroup,
- handleCancelGroup,
- logout,
- handleIconClick,
- handleIconHover,
- handleIconLeave,
- sendMessage,
- createGroup,
- subscribe,
- handleSendMessage,
- searchAccount,
- doSubscribe,
- loadFriendList,
- loadCurrentUserInfo,
- loadChattingList,
- handleSelectChatUser,
- onUserDblClick
- };
- }
- });
- </script>
- <style scoped>
- * {
- margin: 0px;
- padding: 0px;
- }
- .mac-dots-container {
- margin-top: 10px;
- display: flex;
- justify-content: flex-start;
- align-items: center;
- padding: 5px;
- width: 60px;
- margin-left: 5px;
- }
- .mac-dot {
- width: 10px;
- height: 10px;
- border-radius: 50%;
- margin-right: 5px;
- }
- .red {
- background-color: #ff5f57;
- }
- .yellow {
- background-color: #ffbd2e;
- }
- .green {
- background-color: #28c940;
- }
- .chat-container {
- position: absolute;
- width: 1100px;
- height: 700px;
- left: 50%;
- top: 55%;
- margin-left: -550px;
- margin-top: -400px;
- border-radius: 6px;
- overflow: hidden;
- /* box-shadow: 0px 0px 10px #eeeded; */
- background-color: white;
- border: 1px solid #e3e3e3;
- }
- .chat-left-container {
- position: absolute;
- top: 0px;
- left: 0px;
- width: 60px;
- height: 100%;
- background-color: black;
- }
- .chat-center-container {
- position: absolute;
- top: 0px;
- left: 60px;
- width: 270px;
- height: 100%;
- border-right: 1px solid #e3e3e3;
- background-color: #f4f4f4;
- }
- .chat-right-container {
- position: absolute;
- top: 0px;
- right: 0px;
- width: calc(100% - 330px);
- height: 100%;
- }
- .chat-center-header-container {
- position: absolute;
- top: 0px;
- left: 0px;
- width: 100%;
- height: 50px;
- border-bottom: 1px solid #e3e3e3;
- background-color: #f9f9f9;
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- .search-input {
- height: 30px;
- width: 210px;
- margin-left: 10px;
- background-color: #e5e5e5;
- border: none;
- }
- .search-button {
- width: 28px;
- height: 28px;
- margin-right: 10px;
- background-color: black;
- }
- .search-account-card {
- position: relative;
- margin-top: 10px;
- display: flex;
- align-items: center;
- border: 1px solid rgb(239, 236, 236);
- padding: 20px;
- }
- .doSubscribeBtn {
- position: absolute;
- background-color: black;
- right: 8px;
- bottom: 8px;
- padding: 2px 10px;
- }
- .chat-menus {
- display: flex;
- flex-direction: column;
- margin-top: 30px;
- width: 18px;
- margin-left: 21px;
- }
- .chat-menus .anticon {
- color: white;
- font-size: 19px;
- margin-bottom: 30px;
- }
- .bell-with-notification {
- width: 20px;
- height: auto;
- position: relative;
- }
- .message-red-point {
- position: absolute;
- content: '';
- right: -4px;
- top: -4px;
- width: 8px;
- height: 8px;
- background-color: red;
- border-radius: 50%;
- display: none;
- }
- .chat-center-body-container {
- position: relative;
- width: 100%;
- height: 100%;
- margin-top: 50px;
- overflow-x: hidden;
- }
- .chat-center-body-container::-webkit-scrollbar {/*滚动条整体样式*/
- width: 0px; /*高宽分别对应横竖滚动条的尺寸*/
- height: 0px;
- }
- .chat-center-body-container::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
- border-radius: 10px;
- background-color: transparent;
- 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);
- }
- .chat-center-body-container::-webkit-scrollbar-track {/*滚动条里面轨道*/
- -webkit-box-shadow: inset 0 0 1px rgba(0,0,0,0.2);
- /*border-radius: 10px;*/
- background: #EDEDED;
- }
- .group-friend-avatar {
- width: 30px;
- border-radius: 5px;
- margin-right: 5px;
- }
- .group-friend-item {
- display: flex;
- align-items: center;
- gap: 10px;
- width: 100%;
- margin-bottom: 10px; /* 添加间距 */
- }
- .group-friends-list {
- max-height: 350px;
- overflow-x: hidden;
- overflow-y: scroll;
- }
- .group-friends-list::-webkit-scrollbar {/*滚动条整体样式*/
- width: 0px; /*高宽分别对应横竖滚动条的尺寸*/
- height: 0px;
- }
- .group-friends-list::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
- border-radius: 10px;
- background-color: transparent;
- 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);
- }
- .group-friends-list::-webkit-scrollbar-track {/*滚动条里面轨道*/
- -webkit-box-shadow: inset 0 0 1px rgba(0,0,0,0.2);
- /*border-radius: 10px;*/
- background: #EDEDED;
- }
- .video-offer-card {
- position: fixed;
- padding: 20px 40px;
- border-radius: 8px;
- overflow: hidden;
- z-index: 2999;
- right: 50px;
- top: 50px;
- background-color: white;
- border: 1px solid rgb(241, 240, 240);
- box-shadow: 0px 0px 30px rgb(215, 215, 215);
- }
- </style>
|