├── .browserslistrc ├── .gitignore ├── .prettierignore ├── .prettierrc.cjs ├── README.md ├── babel.config.js ├── jsconfig.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── IM │ ├── config │ │ └── index.js │ ├── constant │ │ ├── contacts.js │ │ ├── group.js │ │ ├── index.js │ │ └── message.js │ ├── index.js │ ├── initwebsdk.js │ ├── listener │ │ ├── imConnectListener.js │ │ ├── imContactListener.js │ │ ├── imGroupListener.js │ │ ├── imPresenceListener.js │ │ ├── imReadAckListener.js │ │ ├── imReciveMessageListener.js │ │ └── index.js │ └── miniCore │ │ └── index.js ├── api │ ├── login.js │ ├── register.js │ └── resetPassword.js ├── assets │ ├── callkit │ │ ├── acceptCall@2x.png │ │ ├── avatar-big@2x.png │ │ ├── avatar@2x.png │ │ ├── avtool.svg │ │ ├── camera-close@2x.png │ │ ├── camera@2x.png │ │ ├── change.png │ │ ├── hangupCall@2x.png │ │ ├── invite_member@2x.png │ │ ├── microphone-mute@2x.png │ │ ├── microphone@2x.png │ │ ├── minimodal@2x.png │ │ ├── narrow@2x.png │ │ ├── rtc-bg@2x.png │ │ ├── talking@2x.png │ │ ├── video-bg2.png │ │ ├── video-bg@2x.png │ │ └── video-loading@2x.png │ ├── header_bg.jpg │ ├── images │ │ ├── Group 78@3x.png │ │ ├── Mask_group.png │ │ ├── Mask_group2.png │ │ ├── avatar │ │ │ ├── inform.png │ │ │ ├── jiaqun2x.png │ │ │ └── theme2x.png │ │ ├── ease_default_avatar.png │ │ ├── gender │ │ │ ├── Group76.png │ │ │ └── Group77.png │ │ ├── loginIcon.png │ │ ├── playAudio │ │ │ ├── msg_recv_audio01@2x.png │ │ │ ├── msg_recv_audio01@3x.png │ │ │ ├── msg_recv_audio02@2x.png │ │ │ ├── msg_recv_audio02@3x.png │ │ │ ├── msg_recv_audio@2x.png │ │ │ ├── msg_recv_audio@3x.png │ │ │ ├── msg_send_audio01@2x.png │ │ │ ├── msg_send_audio01@3x.png │ │ │ ├── msg_send_audio02@3x.png │ │ │ ├── msg_send_audio@2x.png │ │ │ └── msg_send_audio@3x.png │ │ ├── tabbar │ │ │ ├── 1461654066965_.pic.jpg │ │ │ ├── 1471654067125_.pic.jpg │ │ │ ├── 1481654067168_.pic.jpg │ │ │ ├── graycontacts.png │ │ │ ├── grayconversation.png │ │ │ ├── highlightconversation.png │ │ │ ├── higtlightcontacts.png │ │ │ ├── session2x.png │ │ │ └── sessionhighlight2x.png │ │ └── web-demo-base.png │ ├── messages │ │ ├── failed@3x.png │ │ ├── img_xmark.png │ │ ├── read@3x.png │ │ ├── received@3x.png │ │ ├── sending@3x.png │ │ └── sent@3x.png │ ├── online_icon │ │ ├── Busy.png │ │ ├── Do_not_Disturb.png │ │ ├── Offline.png │ │ ├── Online.png │ │ ├── custom.png │ │ └── leave.png │ └── ring.mp3 ├── components │ ├── EaseCallKit │ │ ├── alertModal.vue │ │ ├── components │ │ │ ├── miniStreamContainer.vue │ │ │ ├── multiCall.vue │ │ │ └── singleCall.vue │ │ ├── config │ │ │ └── initAgoraRtc.js │ │ ├── constants │ │ │ ├── callKitEvent.js │ │ │ ├── imClient.js │ │ │ └── index.js │ │ ├── hooks │ │ │ ├── index.js │ │ │ ├── useCallKitEvent.js │ │ │ └── useManageChannel.js │ │ ├── index.vue │ │ └── utils │ │ │ ├── callMessages.js │ │ │ ├── createUid.js │ │ │ ├── getChannelDetails.js │ │ │ └── getRtcToken.js │ ├── InviteCallMembers │ │ └── index.vue │ ├── SearchInput │ │ └── index.vue │ ├── UserStatus │ │ └── index.vue │ └── Welcome │ │ └── index.vue ├── constant │ ├── emojis.js │ ├── errorCode.js │ ├── index.js │ ├── informType.js │ ├── messageType.js │ ├── onLineStatus.js │ └── warningText.js ├── hooks │ ├── index.js │ ├── useGetUserMapInfo.js │ ├── usePlayRing.js │ ├── useSetEMLogConfig.js │ ├── useSordedContactsWithPinyin.js │ └── useUserInfoExt.js ├── main.js ├── router │ └── index.js ├── store │ ├── index.js │ └── modules │ │ ├── contacts.js │ │ ├── conversation.js │ │ ├── groups.js │ │ ├── message.js │ │ └── usersProfile.js ├── styles │ ├── element │ │ └── index.scss │ ├── iconfont │ │ ├── demo.css │ │ ├── demo_index.html │ │ ├── iconfont.css │ │ ├── iconfont.js │ │ ├── iconfont.json │ │ └── iconfont.ttf │ └── reset │ │ └── reset.css ├── utils │ ├── dateFormater.js │ ├── fileSizeFormat.js │ ├── getArrdifference.js │ ├── handleSomeData │ │ ├── checkLastMsgIsHasMention.js │ │ ├── createInform.js │ │ ├── handlePresence.js │ │ ├── handleSDKErrorNotifi.js │ │ ├── index.js │ │ ├── setMessageKey.js │ │ └── sortPinyinFriendItem.js │ ├── parseDownloadResponse.js │ ├── paseLink.js │ ├── request.js │ └── waterMark.js └── views │ ├── Chat │ ├── components │ │ ├── AboutGroups │ │ │ ├── GroupsDetails │ │ │ │ ├── index.scss │ │ │ │ └── index.vue │ │ │ └── GroupsManagement │ │ │ │ ├── GroupAnnoun.vue │ │ │ │ ├── GroupBlackList.vue │ │ │ │ ├── GroupDesc.vue │ │ │ │ ├── GroupMembers.vue │ │ │ │ ├── GroupMuteList.vue │ │ │ │ └── index.vue │ │ ├── Contacts │ │ │ ├── components │ │ │ │ ├── ContactInfos.vue │ │ │ │ ├── ContactsItem.vue │ │ │ │ ├── ContactsRemark.vue │ │ │ │ └── JoinedGroupsItem.vue │ │ │ └── index.vue │ │ ├── Conversation │ │ │ ├── components │ │ │ │ └── ConversationList.vue │ │ │ └── index.vue │ │ ├── InformDetails │ │ │ └── index.vue │ │ ├── Message │ │ │ ├── components │ │ │ │ ├── ChatContainerHeader │ │ │ │ │ ├── index.scss │ │ │ │ │ └── index.vue │ │ │ │ ├── ChatInputBox │ │ │ │ │ ├── components │ │ │ │ │ │ ├── AudioMessage │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ ├── CustomMessage │ │ │ │ │ │ │ └── ShareUserCard.vue │ │ │ │ │ │ ├── FileMessage │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ ├── ImageMessage │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ ├── TextMessage │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ └── VideoMessage │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ ├── index.scss │ │ │ │ │ └── index.vue │ │ │ │ ├── ChatMessageListItem │ │ │ │ │ ├── index.scss │ │ │ │ │ └── index.vue │ │ │ │ └── suit │ │ │ │ │ ├── audio.vue │ │ │ │ │ ├── emojiContainer.vue │ │ │ │ │ ├── modifyMessage.vue │ │ │ │ │ ├── msgQuote.vue │ │ │ │ │ ├── previewSendImg.vue │ │ │ │ │ └── reportMessage.vue │ │ │ ├── index.scss │ │ │ └── index.vue │ │ └── NavBar │ │ │ ├── components │ │ │ ├── AboutUserInfoCard │ │ │ │ ├── EditUserInfoCard.vue │ │ │ │ └── MiniInfoCard.vue │ │ │ ├── ApplyComponents │ │ │ │ ├── addFriends.vue │ │ │ │ ├── applyJoinGroups.vue │ │ │ │ ├── createGroups.vue │ │ │ │ └── index.vue │ │ │ ├── Logout.vue │ │ │ ├── PersonalsettingCard │ │ │ │ └── index.vue │ │ │ └── UserOnlineStatusCard.vue │ │ │ └── index.vue │ └── index.vue │ └── Login │ ├── components │ ├── CustomImConfig │ │ └── index.vue │ ├── LoginInput │ │ ├── emloginWithPasswordLogin.vue │ │ └── index.vue │ ├── RegisterInput │ │ └── index.vue │ └── ResetPassword │ │ └── index.vue │ └── index.vue ├── vue.config.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | not ie 11 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | presets -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 每一行的宽度(显示的字符数) 3 | printWidth: 80, 4 | 5 | // tab健的空格数 6 | tabWidth: 2, 7 | 8 | // 是否在对象中的括号之间打印空格,{a:5}格式化为{ a: 5 } 9 | bracketSpacing: true, 10 | 11 | // 箭头函数的参数无论有几个,都要括号包裹 12 | arrowParens: 'always', 13 | 14 | // 换行符的使用 15 | endOfLine: 'lf', 16 | 17 | // 是否用单引号, 项目中全部使用单引号 18 | singleQuote: true, 19 | 20 | // 对象或者数组的最后一个元素后面是否要加逗号 21 | trailingComma: 'all', 22 | 23 | // 是否加分号,项目中统一加分号 24 | semi: true, 25 | 26 | // 是否使用tab格式化: 不使用 27 | useTabs: false, 28 | }; 29 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/cli-plugin-babel/preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "baseUrl": "./", 6 | "moduleResolution": "node", 7 | "paths": { 8 | "@/*": ["src/*"] 9 | }, 10 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webim-vue3-demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vue-cli-service serve", 7 | "build": "vue-cli-service build --mode production", 8 | "format": "prettier --write ." 9 | }, 10 | "dependencies": { 11 | "@vueuse/core": "^8.4.2", 12 | "agora-rtc-sdk-ng": "^4.14.0", 13 | "axios": "^0.27.2", 14 | "benz-amr-recorder": "^1.1.3", 15 | "core-js": "^3.8.3", 16 | "easemob-websdk": "4.12.1", 17 | "element-plus": "^2.7.8", 18 | "nprogress": "^0.2.0", 19 | "pinyin-pro": "^3.10.2", 20 | "vue": "^3.2.13", 21 | "vue-at": "^3.0.0-alpha.2", 22 | "vue-router": "^4.0.3", 23 | "vuex": "^4.0.0" 24 | }, 25 | "devDependencies": { 26 | "@babel/core": "^7.12.16", 27 | "@vue/cli-plugin-babel": "~5.0.0", 28 | "@vue/cli-plugin-router": "~5.0.0", 29 | "@vue/cli-plugin-vuex": "~5.0.0", 30 | "@vue/cli-service": "~5.0.0", 31 | "sass": "^1.51.0", 32 | "sass-loader": "^12.6.0" 33 | }, 34 | "engines": { 35 | "node": "16.x || 17.x" 36 | }, 37 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" 38 | } 39 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 12 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 46 | 70 | 71 | 93 | -------------------------------------------------------------------------------- /src/IM/config/index.js: -------------------------------------------------------------------------------- 1 | //环信appKey默认配置项 2 | export const DEFAULT_EASEMOB_APPKEY = 'easemob#easeim'; 3 | //以下两个非必须 4 | export const DEFAULT_EASEMOB_SOCKET_URL = '//im-api-v2.easemob.com/ws'; 5 | export const DEFAULT_EASEMOB_REST_URL = '//a1.easemob.com'; 6 | -------------------------------------------------------------------------------- /src/IM/constant/contacts.js: -------------------------------------------------------------------------------- 1 | /** 消息类型:subscribe: 请求加好友,unsubscribed: 取消或拒绝加好友,subscribed: 加好友成功。 */ 2 | export const CONTACT_OPERATION_TYPE = { 3 | SUBSCRIBE: 'subscribe', 4 | UNSUBSCRIBED: 'unsubscribed', 5 | SUBSCRIBED: 'subscribed', 6 | }; 7 | /** 8 | * 由于联系人事件监听回调操作类型只有以上三种类型,但是在UI上需要区分更多的类型, 9 | * 因此基于SDK事件类型自行区分不同回调类型 */ 10 | export const CONTACT_OPERATION_CUSTOM_TYPE = { 11 | CONTACT_INVITED: 'contact_invited', // 收到好友邀请 12 | CONTACT_ADDED: 'contact_added', // 新增联系人 13 | CONTACT_DELETED: 'contact_deleted', // 删除联系人 14 | CONTACT_REFUSE: 'contact_refuse', // 好友请求被拒绝 15 | CONTACT_AGREED: 'contact_agreed', // 好友请求被同意 16 | }; 17 | -------------------------------------------------------------------------------- /src/IM/constant/group.js: -------------------------------------------------------------------------------- 1 | /** 操作类型:
2 | * create: 当前用户在其他设备上创建群组时触发。
3 | * destroy: 当群组销毁时触发。
4 | * requestToJoin:当有人申请加群时触发。只有群主和管理员会收到这个事件。
5 | * acceptRequest:当你的加群申请被通过时会触发。只有申请人会收到这个事件。
6 | * joinPublicGroupDeclined:当你的加群申请被拒绝时会触发。只有申请人会收到这个事件。
7 | * inviteToJoin:当收到加群邀请时触发。
8 | * acceptInvite:受邀人接受加群邀请时触发。
9 | * rejectInvite:当受邀人拒绝你的加群邀请时触发。
10 | * removeMember: 当被移除群组或被加入黑名单时触发。只有被移除的人会收到这个事件。
11 | * unblockMember: 当被从黑名单中移除时触发。只有被移除的人会收到这个事件。
12 | * updateInfo: 当修改群组信息时触发。
13 | * memberPresence:当有人加入群组时触发。
14 | * memberAbsence: 当有人离开群组时触发。
15 | * directJoined:当不需要你的确认直接被拉进群时触发。 16 | * changeOwner:当转让群组时触发。只有新老群主会收到这个事件。 17 | * setAdmin: 当被设为管理员时触发。只有被设为管理员的人会收到这个事件。
18 | * removeAdmin:当被移除管理员时触发。只有被移除管理人员的人会收到这个事件。
19 | * muteMember: 当被禁言时触发。只有被禁言的人会收到这个事件。
20 | * unmuteMember:当被解除禁言时触发。只有被解除禁言的人会收到这个事件。
21 | * updateAnnouncement:当群组公告更新时触发。
22 | * deleteAnnouncement:当删除群组公告时触发。
23 | * uploadFile:当上传群组共享文件时触发。
24 | * deleteFile:当删除群组共享文件时触发。
25 | * addUserToAllowlist:当被加入群组白名单时触发。
26 | * removeAllowlistMember:当被从群组白名单移除时触发。
27 | * muteAllMembers:当群组全员禁言时触发。
28 | * unmuteAllMembers:当群组解除全员禁言时触发。
29 | * memberAttributesUpdate: 当群成员属性更新时触发。
30 | */ 31 | export const GROUP_OPERATION_TYPE = { 32 | CREATE: 'create', 33 | DESTROY: 'destroy', 34 | REQUEST_TO_JOIN: 'requestToJoin', 35 | ACCEPT_REQUEST: 'acceptRequest', 36 | JOIN_PUBLIC_GROUP_DECLINED: 'joinPublicGroupDeclined', 37 | INVITE_TO_JOIN: 'inviteToJoin', 38 | ACCEPT_INVITE: 'acceptInvite', 39 | REJECT_INVITE: 'rejectInvite', 40 | REMOVE_MEMBER: 'removeMember', 41 | UNBLOCK_MEMBER: 'unblockMember', 42 | UPDATE_INFO: 'updateInfo', 43 | MEMBER_PRESENCE: 'memberPresence', 44 | MEMBER_ABSENCE: 'memberAbsence', 45 | DIRECT_JOINED: 'directJoined', 46 | CHANGE_OWNER: 'changeOwner', 47 | SET_ADMIN: 'setAdmin', 48 | REMOVE_ADMIN: 'removeAdmin', 49 | MUTE_MEMBER: 'muteMember', 50 | UNMUTE_MEMBER: 'unmuteMember', 51 | UPDATE_ANNOUNCEMENT: 'updateAnnouncement', 52 | DELETE_ANNOUNCEMENT: 'deleteAnnouncement', 53 | UPLOAD_FILE: 'uploadFile', 54 | DELETE_FILE: 'deleteFile', 55 | ADD_USER_TO_ALLOWLIST: 'addUserToAllowlist', 56 | REMOVE_ALLOWLIST_MEMBER: 'removeAllowlistMember', 57 | MUTE_ALL_MEMBERS: 'muteAllMembers', 58 | UNMUTE_ALL_MEMBERS: 'unmuteAllMembers', 59 | REMOVE_ALLOWLIST_MEMBER: 'removeAllowlistMember', 60 | MUTE_ALL_MEMBERS: 'muteAllMembers', 61 | UNMUTE_ALL_MEMBERS: 'unmuteAllMembers', 62 | MEMBER_ATTRIBUTES_UPDATE: 'memberAttributesUpdate', 63 | }; 64 | export const GROUP_ROLE_TYPE = { 65 | OWNER: 'owner', 66 | ADMIN: 'admin', 67 | MEMBER: 'member', 68 | }; 69 | -------------------------------------------------------------------------------- /src/IM/constant/index.js: -------------------------------------------------------------------------------- 1 | export * from './group'; 2 | export * from './contacts'; 3 | export * from './message'; 4 | 5 | export const CHAT_TYPE = { 6 | SINGLE: 'singleChat', 7 | GROUP: 'groupChat', 8 | }; 9 | -------------------------------------------------------------------------------- /src/IM/constant/message.js: -------------------------------------------------------------------------------- 1 | export const MESSAGE_TYPE = { 2 | TEXT: 'txt', 3 | LOCAL: 'loc', 4 | IMAGE: 'img', 5 | FILE: 'file', 6 | VIDEO: 'video', 7 | AUDIO: 'audio', 8 | COMMAND: 'cmd', 9 | CUSTOM: 'custom', 10 | COMBINE: 'combine', 11 | }; 12 | -------------------------------------------------------------------------------- /src/IM/index.js: -------------------------------------------------------------------------------- 1 | import EMClient from './miniCore'; 2 | export { EMClient }; 3 | -------------------------------------------------------------------------------- /src/IM/initwebsdk.js: -------------------------------------------------------------------------------- 1 | //引入环信SDK 2 | import EaseChatSDK from 'easemob-websdk'; 3 | import { 4 | DEFAULT_EASEMOB_APPKEY, 5 | DEFAULT_EASEMOB_SOCKET_URL, 6 | DEFAULT_EASEMOB_REST_URL, 7 | } from './config'; 8 | // 读取自定义配置(因demo需要自定义配置,非必须) 9 | const webimConfig = window.localStorage.getItem('webimConfig'); 10 | const CUSTOM_CONFIG = (webimConfig && JSON.parse(webimConfig)) || {}; 11 | 12 | //存放实例化后所有的方法 13 | // let EaseIMClient = {}; 14 | // window.EaseIM = EaseIM = Easemob_SDK; 15 | //实例化环信SDK 16 | /* 17 | * isHttpDNS: isPrivate为true开启私有化配置则走自有配置的url以及apiUrl, 18 | * 否则为true就SDK自助获取DNS地址。 19 | * 【特别注意】如果不需要私有化配置,也就是自己定义url以及apiUrl。isHttpDNS、url、apiUrl,均可不用填写只用填入appKey!SDK内部会进行自动获取! 20 | **/ 21 | const EaseChatClient = new EaseChatSDK.connection({ 22 | appKey: CUSTOM_CONFIG.appKey ? CUSTOM_CONFIG.appKey : DEFAULT_EASEMOB_APPKEY, 23 | isHttpDNS: !CUSTOM_CONFIG.isPrivate, //取反isPrivate 24 | url: CUSTOM_CONFIG.imServer 25 | ? CUSTOM_CONFIG.imServer 26 | : DEFAULT_EASEMOB_SOCKET_URL, 27 | apiUrl: CUSTOM_CONFIG.restServer 28 | ? `${CUSTOM_CONFIG.restServer}:${CUSTOM_CONFIG.port}` 29 | : DEFAULT_EASEMOB_REST_URL, 30 | }); 31 | //向EaseChatClient下添加构建消息方法。 32 | EaseChatClient.Message = EaseChatSDK.message; 33 | 34 | export { EaseChatSDK, EaseChatClient }; 35 | -------------------------------------------------------------------------------- /src/IM/listener/imConnectListener.js: -------------------------------------------------------------------------------- 1 | import router from '@/router'; 2 | import store from '@/store'; 3 | import { handleSDKErrorNotifi } from '@/utils/handleSomeData'; 4 | import { EMClient } from '../index'; 5 | import { usePlayRing } from '@/hooks'; 6 | export const imConnectListener = () => { 7 | const mountConnectEventListener = () => { 8 | const { isOpenPlayRing, clickRing } = usePlayRing(); 9 | EMClient.addEventHandler('connection', { 10 | onConnected: () => { 11 | store.commit('CHANGE_LOGIN_STATUS', true); 12 | if (isOpenPlayRing.value) clickRing(); 13 | fetchLoginUsersInitData(); 14 | router.replace('/chat'); 15 | }, 16 | onDisconnected: () => { 17 | router.push('/login'); 18 | store.commit('CHANGE_LOGIN_STATUS', false); 19 | }, 20 | onOnline: () => { 21 | store.commit('CHANGE_NETWORK_STATUS', true); 22 | }, // 本机网络连接成功。 23 | onOffline: () => { 24 | store.commit('CHANGE_NETWORK_STATUS', false); 25 | }, // 本机网络掉线。 26 | onError: (error) => { 27 | handleSDKErrorNotifi(error.type, error.message); 28 | }, 29 | }); 30 | }; 31 | 32 | //fetch 登陆用户的初始数据 33 | const fetchLoginUsersInitData = () => { 34 | getMyUserInfos(); 35 | fetchFriendList(); 36 | fetchTheLoginUserBlickList(); 37 | fetchGroupList(); 38 | //初始化vuex中的会话列表相关数据 39 | // store.dispatch('getConversationListFromLocal') 40 | store.dispatch('getConversationList'); 41 | }; 42 | //获取登陆用户属性 43 | const getMyUserInfos = () => { 44 | const userId = EMClient.user; 45 | store.dispatch('getMyUserInfo', userId); 46 | }; 47 | //获取好友列表 48 | const fetchFriendList = () => { 49 | store.dispatch('fetchAllContactsListWithRemarkFromServer'); 50 | }; 51 | //获取黑名单列表 52 | const fetchTheLoginUserBlickList = () => store.dispatch('fetchBlackList'); 53 | //获取加入的群组列表 54 | const fetchGroupList = () => store.dispatch('fetchJoinedGroupListFromServer'); 55 | return { 56 | mountConnectEventListener, 57 | fetchLoginUsersInitData, 58 | getMyUserInfos, 59 | fetchFriendList, 60 | fetchTheLoginUserBlickList, 61 | fetchGroupList, 62 | }; 63 | }; 64 | -------------------------------------------------------------------------------- /src/IM/listener/imContactListener.js: -------------------------------------------------------------------------------- 1 | import { EMClient } from '../index'; 2 | import { INFORM_FROM } from '@/constant'; 3 | import store from '@/store'; 4 | import { CONTACT_OPERATION_CUSTOM_TYPE } from '../constant'; 5 | export const imContactListener = () => { 6 | const submitInformData = (fromType, informContent) => { 7 | store.dispatch('createNewInform', { fromType, informContent }); 8 | }; 9 | const onDispatchContactEvent = (eventType, data) => { 10 | console.log('onDispatchContactEvent', data); 11 | switch (eventType) { 12 | case CONTACT_OPERATION_CUSTOM_TYPE.CONTACT_INVITED: 13 | { 14 | submitInformData(INFORM_FROM.FRIEND, data); 15 | } 16 | break; 17 | case CONTACT_OPERATION_CUSTOM_TYPE.CONTACT_ADDED: 18 | { 19 | submitInformData(INFORM_FROM.FRIEND, data); 20 | store.dispatch('onAddNewContact', data); 21 | } 22 | break; 23 | case CONTACT_OPERATION_CUSTOM_TYPE.CONTACT_AGREED: 24 | { 25 | //改掉data中的type 26 | data.type = 'other_person_agree'; 27 | submitInformData(INFORM_FROM.FRIEND, data); 28 | store.dispatch('onAddNewContact', data); 29 | } 30 | break; 31 | case CONTACT_OPERATION_CUSTOM_TYPE.CONTACT_REFUSE: 32 | { 33 | data.type = 'other_person_refuse'; 34 | submitInformData(INFORM_FROM.FRIEND, data); 35 | } 36 | break; 37 | case CONTACT_OPERATION_CUSTOM_TYPE.CONTACT_DELETED: { 38 | submitInformData(INFORM_FROM.FRIEND, data); 39 | store.dispatch('onDeleteContact', data); 40 | } 41 | default: 42 | break; 43 | } 44 | }; 45 | const mountContactEventListener = () => { 46 | EMClient.addEventHandler('friendListen', { 47 | // 收到好友邀请触发此方法。 48 | onContactInvited: (data) => { 49 | //写入INFORM 50 | console.log('onContactInvited', data); 51 | onDispatchContactEvent( 52 | CONTACT_OPERATION_CUSTOM_TYPE.CONTACT_INVITED, 53 | data, 54 | ); 55 | }, 56 | // 联系人被删除时触发此方法。 57 | onContactDeleted: (data) => { 58 | //写入INFORM 59 | console.log('onContactDeleted', data); 60 | onDispatchContactEvent( 61 | CONTACT_OPERATION_CUSTOM_TYPE.CONTACT_DELETED, 62 | data, 63 | ); 64 | }, 65 | // 新增联系人会触发此方法。 66 | onContactAdded: (data) => { 67 | console.log('onContactAdded', data); 68 | onDispatchContactEvent( 69 | CONTACT_OPERATION_CUSTOM_TYPE.CONTACT_ADDED, 70 | data, 71 | ); 72 | }, 73 | // 好友请求被拒绝时触发此方法。 74 | onContactRefuse: (data) => { 75 | //写入INFORM 76 | console.log('onContactRefuse', data); 77 | onDispatchContactEvent( 78 | CONTACT_OPERATION_CUSTOM_TYPE.CONTACT_REFUSE, 79 | data, 80 | ); 81 | }, 82 | // 好友请求被同意时触发此方法。 83 | onContactAgreed: (data) => { 84 | //写入INFORM 85 | console.log('onContactAgreed', data); 86 | onDispatchContactEvent( 87 | CONTACT_OPERATION_CUSTOM_TYPE.CONTACT_AGREED, 88 | data, 89 | ); 90 | }, 91 | }); 92 | }; 93 | return { 94 | mountContactEventListener, 95 | }; 96 | }; 97 | -------------------------------------------------------------------------------- /src/IM/listener/imGroupListener.js: -------------------------------------------------------------------------------- 1 | import { EMClient } from '../index'; 2 | import { INFORM_FROM } from '@/constant'; 3 | import store from '@/store'; 4 | import { GROUP_OPERATION_TYPE } from '../constant'; 5 | export const imGroupListener = () => { 6 | const submitInformData = (fromType, informContent) => { 7 | store.dispatch('createNewInform', { fromType, informContent }); 8 | }; 9 | const onDispatchGroupEvent = (groupevent) => { 10 | const { operation, id: groupId, from } = groupevent; 11 | switch (operation) { 12 | //入群通知 13 | case GROUP_OPERATION_TYPE.MEMBER_PRESENCE: 14 | { 15 | const params = { 16 | groupId, 17 | type: 'groupMemberCount', 18 | params: groupevent.memberCount, 19 | }; 20 | store.commit('UPDATE_CACHE_GROUP_INFO', params); 21 | store.commit('UPDATE_GROUP_MEMBERS', { 22 | groupId, 23 | type: GROUP_OPERATION_TYPE.MEMBER_PRESENCE, 24 | member: from, 25 | }); 26 | } 27 | break; 28 | //群成员退群通知 29 | case GROUP_OPERATION_TYPE.MEMBER_ABSENCE: 30 | { 31 | //退群通知 32 | const params = { 33 | groupId, 34 | type: 'groupMemberCount', 35 | params: groupevent.memberCount, 36 | }; 37 | store.commit('UPDATE_CACHE_GROUP_INFO', params); 38 | store.commit('UPDATE_GROUP_MEMBERS', { 39 | groupId, 40 | type: GROUP_OPERATION_TYPE.MEMBER_ABSENCE, 41 | member: from, 42 | }); 43 | } 44 | break; 45 | //群组公告更新 46 | case GROUP_OPERATION_TYPE.UPDATE_ANNOUNCEMENT: 47 | { 48 | console.log('>>>>群组公告更新', groupevent); 49 | store.dispatch('fetchAnnounmentFromServer', groupId); 50 | } 51 | break; 52 | //群组管理员设置 53 | case GROUP_OPERATION_TYPE.SET_ADMIN: 54 | { 55 | store.commit('UPDATE_GORUPS_ADMIN', { 56 | type: GROUP_OPERATION_TYPE.SET_ADMIN, 57 | groupId, 58 | userId: from, 59 | }); 60 | } 61 | break; 62 | //群组管理员取消 63 | case GROUP_OPERATION_TYPE.REMOVE_ADMIN: 64 | { 65 | store.commit('UPDATE_GORUPS_ADMIN', { 66 | type: GROUP_OPERATION_TYPE.REMOVE_ADMIN, 67 | groupId, 68 | userId: from, 69 | }); 70 | } 71 | break; 72 | //群组成员禁言 73 | case GROUP_OPERATION_TYPE.MUTE_MEMBER: 74 | { 75 | store.dispatch('fetchGroupsMuteListFromServer', groupId); 76 | } 77 | break; 78 | //群组成员解除禁言 79 | case GROUP_OPERATION_TYPE.UNMUTE_MEMBER: 80 | { 81 | store.dispatch('fetchGroupsMuteListFromServer', groupId); 82 | } 83 | break; 84 | //被移出群组 85 | case GROUP_OPERATION_TYPE.REMOVE_MEMBER: 86 | { 87 | //从群组列表中删除某群 88 | store.commit('DELETE_JOINED_GROUP_LIST', { 89 | groupId: groupId, 90 | }); 91 | } 92 | break; 93 | //群组解散 94 | case GROUP_OPERATION_TYPE.DESTROY: 95 | { 96 | console.log('>>>>解散删除群组'); 97 | //从群组列表中删除某群 98 | store.commit('DELETE_JOINED_GROUP_LIST', { 99 | groupId: groupId, 100 | }); 101 | } 102 | break; 103 | //群组内更新了群组信息 104 | case GROUP_OPERATION_TYPE.UPDATE_INFO: 105 | { 106 | const { detail } = groupevent; 107 | if (detail.name) { 108 | store.commit('UPDATE_CACHE_GROUP_INFO', { 109 | groupId, 110 | type: 'groupName', 111 | params: detail.name, 112 | }); 113 | } else if (detail.description) { 114 | store.commit('UPDATE_CACHE_GROUP_INFO', { 115 | groupId, 116 | type: 'groupDescription', 117 | params: detail.description, 118 | }); 119 | } 120 | } 121 | break; 122 | //接受入群邀请 123 | case GROUP_OPERATION_TYPE.ACCEPT_REQUEST: 124 | { 125 | //更新群组列表 126 | store.dispatch('fetchJoinedGroupListFromServer', { 127 | startPageNum: 0, 128 | }); 129 | } 130 | break; 131 | //群成员更新了群组内成员属性 132 | case GROUP_OPERATION_TYPE.MEMBER_ATTRIBUTES_UPDATE: 133 | { 134 | commit('SET_GROUP_MEMBERS_INFO', { 135 | groupId: informContent.id, 136 | inGroupInfo: [ 137 | { 138 | [informContent.from]: { 139 | nickName: informContent?.attributes?.nickName, 140 | }, 141 | }, 142 | ], 143 | }); 144 | } 145 | break; 146 | //管理员直接邀请进群 147 | case GROUP_OPERATION_TYPE.DIRECT_JOINED: { 148 | //更新群组列表 149 | store.dispatch('fetchJoinedGroupListFromServer', { 150 | startPageNum: 0, 151 | }); 152 | } 153 | default: 154 | break; 155 | } 156 | }; 157 | const mountGroupEventListener = () => { 158 | EMClient.addEventHandler('groupEvent', { 159 | onGroupEvent: (groupevent) => { 160 | console.log('groupEvent: ', groupevent); 161 | submitInformData(INFORM_FROM.GROUP, groupevent); 162 | onDispatchGroupEvent(groupevent); 163 | }, 164 | }); 165 | }; 166 | return { 167 | mountGroupEventListener, 168 | }; 169 | }; 170 | -------------------------------------------------------------------------------- /src/IM/listener/imPresenceListener.js: -------------------------------------------------------------------------------- 1 | import { EMClient } from '../index'; 2 | import store from '@/store'; 3 | export const imPresenceListener = () => { 4 | //处理登陆用户状态的变更 5 | const getUserPresence = (status) => { 6 | store.dispatch('handlePresenceChanges', status); 7 | }; 8 | const mountPresenceEventListener = () => { 9 | EMClient.addEventHandler('presenceStatusChange', { 10 | onPresenceStatusChange: (status) => { 11 | getUserPresence(...status); 12 | }, 13 | }); 14 | }; 15 | return { 16 | mountPresenceEventListener, 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /src/IM/listener/imReadAckListener.js: -------------------------------------------------------------------------------- 1 | import { EMClient } from '../index'; 2 | import store from '@/store'; 3 | import { MESSAGE_STATUS_TYPE } from '@/constant'; 4 | export const imReadAckListener = () => { 5 | const mountReadAckEventListener = () => { 6 | console.log('mountReadAckEventListener'); 7 | EMClient.addEventHandler('aboutReadAckMessage', { 8 | // 当前用户收到消息已读回执。 9 | onReadMessage: (message) => { 10 | updateMessageReadStatus(message); 11 | }, 12 | // 当前用户收到会话已读回执。 13 | onChannelMessage: (message) => { 14 | updateConversationReadStatus(message); 15 | }, 16 | }); 17 | }; 18 | //根据收到的单条消息已读回执更新消息已读状态状态 19 | const updateMessageReadStatus = (message) => { 20 | const { mid, to, from } = message; 21 | const key = to === EMClient.user ? from : to; 22 | const payload = { 23 | id: mid, 24 | key, 25 | type: MESSAGE_STATUS_TYPE.READ_STATUS, 26 | }; 27 | store.commit('UPDATE_MESSAGE_IDS_COLLECTION', payload); 28 | }; 29 | //根据收到会话已读回执更新整个会话为已读状态 30 | const updateConversationReadStatus = (message) => { 31 | const { to, from } = message; 32 | const key = to === EMClient.user ? from : to; 33 | const payload = { 34 | key, 35 | type: MESSAGE_STATUS_TYPE.CHANLE_STATUS, 36 | }; 37 | store.commit('UPDATE_MESSAGE_IDS_COLLECTION', payload); 38 | }; 39 | return { 40 | mountReadAckEventListener, 41 | }; 42 | }; 43 | -------------------------------------------------------------------------------- /src/IM/listener/imReciveMessageListener.js: -------------------------------------------------------------------------------- 1 | import { EMClient } from '../index'; 2 | import { CHAT_TYPE } from '../constant'; 3 | import { CHANGE_MESSAGE_BODAY_TYPE } from '@/constant'; 4 | import { setMessageKey } from '@/utils/handleSomeData'; 5 | import store from '@/store'; 6 | export const imReviceMessageListener = () => { 7 | //接收的消息往store中push 8 | const pushNewMessage = (message) => { 9 | store.dispatch('createNewMessage', message); 10 | store.dispatch('UsersProfile/processMessageExt', message, { root: true }); 11 | }; 12 | //收到他人的撤回指令 13 | const otherRecallMessage = (message) => { 14 | const { from, to, mid } = message; 15 | //单对单的撤回to必然为登陆的用户id,群组发起撤回to必然为群组id 所以key可以这样来区分群组或者单人。 16 | const key = to === EMClient.user ? from : to; 17 | 18 | const chatType = to === EMClient.user ? CHAT_TYPE.SINGLE : CHAT_TYPE.GROUP; 19 | store.commit('CHANGE_MESSAGE_BODAY', { 20 | type: CHANGE_MESSAGE_BODAY_TYPE.RECALL, 21 | key, 22 | mid, 23 | }); 24 | store.dispatch('updateConversationList', { 25 | conversationId: key, 26 | chatType, 27 | }); 28 | }; 29 | //收到消息修改指令 30 | const otherModifyMessage = (message) => { 31 | const { from, to, id: mid, chatType } = message; 32 | //单对单的撤回to必然为登陆的用户id,群组发起撤回to必然为群组id 所以key可以这样来区分群组或者单人。 33 | if (!to) return; 34 | const key = to === EMClient.user ? from : to; 35 | store.commit('CHANGE_MESSAGE_BODAY', { 36 | type: CHANGE_MESSAGE_BODAY_TYPE.MODIFY, 37 | key, 38 | mid, 39 | message, 40 | }); 41 | store.dispatch('updateConversationList', { 42 | conversationId: key, 43 | chatType, 44 | }); 45 | }; 46 | const mountReviceMessageEventListener = () => { 47 | /* message 相关监听 */ 48 | EMClient.addEventHandler('messageListen', { 49 | onTextMessage: function (message) { 50 | pushNewMessage(message); 51 | }, // 收到文本消息。 52 | onEmojiMessage: function (message) { 53 | pushNewMessage(message); 54 | }, // 收到表情消息。 55 | onImageMessage: function (message) { 56 | pushNewMessage(message); 57 | }, // 收到图片消息。 58 | onCmdMessage: function (message) {}, // 收到命令消息。 59 | onAudioMessage: function (message) { 60 | pushNewMessage(message); 61 | }, // 收到音频消息。 62 | onLocationMessage: function (message) { 63 | pushNewMessage(message); 64 | }, // 收到位置消息。 65 | onFileMessage: function (message) { 66 | pushNewMessage(message); 67 | }, // 收到文件消息。 68 | onCustomMessage: function (message) { 69 | pushNewMessage(message); 70 | }, // 收到自定义消息。 71 | onVideoMessage: function (message) { 72 | pushNewMessage(message); 73 | }, // 收到视频消息。 74 | onRecallMessage: function (message) { 75 | otherRecallMessage(message); 76 | }, // 收到消息撤回回执。 77 | onModifiedMessage: function (message) { 78 | otherModifyMessage(message); 79 | }, 80 | }); 81 | }; 82 | return { 83 | mountReviceMessageEventListener, 84 | pushNewMessage, 85 | otherModifyMessage, 86 | otherRecallMessage, 87 | }; 88 | }; 89 | -------------------------------------------------------------------------------- /src/IM/listener/index.js: -------------------------------------------------------------------------------- 1 | import { imConnectListener } from './imConnectListener'; 2 | import { imReviceMessageListener } from './imReciveMessageListener'; 3 | import { imPresenceListener } from './imPresenceListener'; 4 | import { imContactListener } from './imContactListener'; 5 | import { imGroupListener } from './imGroupListener'; 6 | import { imReadAckListener } from './imReadAckListener'; 7 | /* mount all listener */ 8 | export const mountAllEMListener = () => { 9 | const { mountConnectEventListener } = imConnectListener(); 10 | mountConnectEventListener(); 11 | const { mountPresenceEventListener } = imPresenceListener(); 12 | mountPresenceEventListener(); 13 | const { mountReviceMessageEventListener } = imReviceMessageListener(); 14 | mountReviceMessageEventListener(); 15 | const { mountContactEventListener } = imContactListener(); 16 | mountContactEventListener(); 17 | const { mountGroupEventListener } = imGroupListener(); 18 | mountGroupEventListener(); 19 | const { mountReadAckEventListener } = imReadAckListener(); 20 | mountReadAckEventListener(); 21 | }; 22 | export { 23 | imConnectListener, 24 | imPresenceListener, 25 | imReviceMessageListener, 26 | imContactListener, 27 | imGroupListener, 28 | imReadAckListener, 29 | }; 30 | -------------------------------------------------------------------------------- /src/IM/miniCore/index.js: -------------------------------------------------------------------------------- 1 | //MiniCore 2 | import MiniCore from 'easemob-websdk/miniCore/miniCore'; 3 | import * as contactPlugin from 'easemob-websdk/contact/contact'; 4 | import * as groupPlugin from 'easemob-websdk/group/group'; 5 | import * as presencePlugin from 'easemob-websdk/presence/presence'; 6 | import * as localCachePlugin from 'easemob-websdk/localCache/localCache'; 7 | import { 8 | DEFAULT_EASEMOB_APPKEY, 9 | DEFAULT_EASEMOB_SOCKET_URL, 10 | DEFAULT_EASEMOB_REST_URL, 11 | } from '../config'; 12 | let miniCore = {}; 13 | const IM_IS_OPEN_CUSTOM_SERVER_CONFIG = 14 | JSON.parse(window.localStorage.getItem('IM_IS_OPEN_CUSTOM_SERVER_CONFIG')) || 15 | false; 16 | const webimConfig = window.localStorage.getItem('webimConfig'); 17 | const CUSTOM_CONFIG = (webimConfig && JSON.parse(webimConfig)) || {}; 18 | const initEMClient = () => { 19 | // 读取自定义配置(因demo需要自定义配置,非必须) 20 | const configOptions = {}; 21 | console.log( 22 | 'IM_IS_OPEN_CUSTOM_SERVER_CONFIG', 23 | IM_IS_OPEN_CUSTOM_SERVER_CONFIG, 24 | ); 25 | if (IM_IS_OPEN_CUSTOM_SERVER_CONFIG) { 26 | Object.assign(configOptions, { 27 | appKey: CUSTOM_CONFIG.appKey 28 | ? CUSTOM_CONFIG.appKey 29 | : DEFAULT_EASEMOB_APPKEY, 30 | isHttpDNS: !CUSTOM_CONFIG.isPrivate, //取反isPrivate 31 | url: CUSTOM_CONFIG.imWebsocketServer 32 | ? CUSTOM_CONFIG.imWebsocketServer 33 | : DEFAULT_EASEMOB_SOCKET_URL, 34 | apiUrl: CUSTOM_CONFIG.restServer 35 | ? `${CUSTOM_CONFIG.restServer}:${CUSTOM_CONFIG.port}` 36 | : DEFAULT_EASEMOB_REST_URL, 37 | }); 38 | } else { 39 | Object.assign(configOptions, { 40 | appKey: DEFAULT_EASEMOB_APPKEY, 41 | }); 42 | console.log('configOptions', configOptions); 43 | } 44 | miniCore = new MiniCore({ ...configOptions }); 45 | return miniCore; 46 | }; 47 | initEMClient(); 48 | 49 | if (Object.keys(miniCore).length) { 50 | //注册插件 51 | miniCore.usePlugin(contactPlugin); 52 | miniCore.usePlugin(groupPlugin); 53 | miniCore.usePlugin(presencePlugin); 54 | miniCore.usePlugin(localCachePlugin, 'localCache'); 55 | } 56 | export default miniCore; 57 | -------------------------------------------------------------------------------- /src/api/login.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | //获取用户登陆token 4 | // export function fetchUserLoginToken(params) { 5 | // return request({ 6 | // url: '/inside/app/user/login', 7 | // method: 'post', 8 | // data: params 9 | // }) 10 | // } 11 | 12 | //新获取用户登录token 13 | // export function fetchUserLoginToken(params) { 14 | // return request({ 15 | // url: '/inside/app/user/login/V1', 16 | // method: 'post', 17 | // data: params 18 | // }) 19 | // } 20 | //新获取用户登录token v2 21 | export function fetchUserLoginToken(params) { 22 | return request({ 23 | url: '/inside/app/user/login/V2', 24 | method: 'post', 25 | data: params, 26 | }); 27 | } 28 | //新获取短信验证码 29 | export function fetchUserLoginSmsCode(phoneNumber) { 30 | return request({ 31 | url: `/inside/app/sms/send/${phoneNumber}`, 32 | method: 'post', 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /src/api/register.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | //生成图片验证码 4 | export function createImageCode(params) { 5 | return request({ 6 | url: '/inside/app/image', 7 | method: 'get', 8 | params: params, 9 | }); 10 | } 11 | 12 | //发送短信请求 13 | export function fetchAuthCode(params) { 14 | return request({ 15 | url: '/inside/app/sms/send', 16 | method: 'post', 17 | data: params, 18 | }); 19 | } 20 | 21 | //注册用户 22 | export function registerUser(params) { 23 | return request({ 24 | url: '/inside/app/user/register', 25 | method: 'post', 26 | data: params, 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /src/api/resetPassword.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | //重置密码第一步(请求修改密码权限权限) 4 | export function requestModifyPwd(params) { 5 | return request({ 6 | url: '/inside/app/user/reset/password', 7 | method: 'post', 8 | data: params, 9 | }); 10 | } 11 | 12 | //重置密码第二步(上传修改后的新密码) 13 | export function updateNewPasswrod(params) { 14 | const { userId, newPassword } = params; 15 | return request({ 16 | url: `/inside/app/user/${userId}/password`, 17 | method: 'put', 18 | data: { newPassword }, 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /src/assets/callkit/acceptCall@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/callkit/acceptCall@2x.png -------------------------------------------------------------------------------- /src/assets/callkit/avatar-big@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/callkit/avatar-big@2x.png -------------------------------------------------------------------------------- /src/assets/callkit/avatar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/callkit/avatar@2x.png -------------------------------------------------------------------------------- /src/assets/callkit/avtool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/callkit/camera-close@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/callkit/camera-close@2x.png -------------------------------------------------------------------------------- /src/assets/callkit/camera@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/callkit/camera@2x.png -------------------------------------------------------------------------------- /src/assets/callkit/change.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/callkit/change.png -------------------------------------------------------------------------------- /src/assets/callkit/hangupCall@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/callkit/hangupCall@2x.png -------------------------------------------------------------------------------- /src/assets/callkit/invite_member@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/callkit/invite_member@2x.png -------------------------------------------------------------------------------- /src/assets/callkit/microphone-mute@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/callkit/microphone-mute@2x.png -------------------------------------------------------------------------------- /src/assets/callkit/microphone@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/callkit/microphone@2x.png -------------------------------------------------------------------------------- /src/assets/callkit/minimodal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/callkit/minimodal@2x.png -------------------------------------------------------------------------------- /src/assets/callkit/narrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/callkit/narrow@2x.png -------------------------------------------------------------------------------- /src/assets/callkit/rtc-bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/callkit/rtc-bg@2x.png -------------------------------------------------------------------------------- /src/assets/callkit/talking@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/callkit/talking@2x.png -------------------------------------------------------------------------------- /src/assets/callkit/video-bg2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/callkit/video-bg2.png -------------------------------------------------------------------------------- /src/assets/callkit/video-bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/callkit/video-bg@2x.png -------------------------------------------------------------------------------- /src/assets/callkit/video-loading@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/callkit/video-loading@2x.png -------------------------------------------------------------------------------- /src/assets/header_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/header_bg.jpg -------------------------------------------------------------------------------- /src/assets/images/Group 78@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/Group 78@3x.png -------------------------------------------------------------------------------- /src/assets/images/Mask_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/Mask_group.png -------------------------------------------------------------------------------- /src/assets/images/Mask_group2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/Mask_group2.png -------------------------------------------------------------------------------- /src/assets/images/avatar/inform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/avatar/inform.png -------------------------------------------------------------------------------- /src/assets/images/avatar/jiaqun2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/avatar/jiaqun2x.png -------------------------------------------------------------------------------- /src/assets/images/avatar/theme2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/avatar/theme2x.png -------------------------------------------------------------------------------- /src/assets/images/ease_default_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/ease_default_avatar.png -------------------------------------------------------------------------------- /src/assets/images/gender/Group76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/gender/Group76.png -------------------------------------------------------------------------------- /src/assets/images/gender/Group77.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/gender/Group77.png -------------------------------------------------------------------------------- /src/assets/images/loginIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/loginIcon.png -------------------------------------------------------------------------------- /src/assets/images/playAudio/msg_recv_audio01@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/playAudio/msg_recv_audio01@2x.png -------------------------------------------------------------------------------- /src/assets/images/playAudio/msg_recv_audio01@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/playAudio/msg_recv_audio01@3x.png -------------------------------------------------------------------------------- /src/assets/images/playAudio/msg_recv_audio02@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/playAudio/msg_recv_audio02@2x.png -------------------------------------------------------------------------------- /src/assets/images/playAudio/msg_recv_audio02@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/playAudio/msg_recv_audio02@3x.png -------------------------------------------------------------------------------- /src/assets/images/playAudio/msg_recv_audio@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/playAudio/msg_recv_audio@2x.png -------------------------------------------------------------------------------- /src/assets/images/playAudio/msg_recv_audio@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/playAudio/msg_recv_audio@3x.png -------------------------------------------------------------------------------- /src/assets/images/playAudio/msg_send_audio01@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/playAudio/msg_send_audio01@2x.png -------------------------------------------------------------------------------- /src/assets/images/playAudio/msg_send_audio01@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/playAudio/msg_send_audio01@3x.png -------------------------------------------------------------------------------- /src/assets/images/playAudio/msg_send_audio02@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/playAudio/msg_send_audio02@3x.png -------------------------------------------------------------------------------- /src/assets/images/playAudio/msg_send_audio@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/playAudio/msg_send_audio@2x.png -------------------------------------------------------------------------------- /src/assets/images/playAudio/msg_send_audio@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/playAudio/msg_send_audio@3x.png -------------------------------------------------------------------------------- /src/assets/images/tabbar/1461654066965_.pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/tabbar/1461654066965_.pic.jpg -------------------------------------------------------------------------------- /src/assets/images/tabbar/1471654067125_.pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/tabbar/1471654067125_.pic.jpg -------------------------------------------------------------------------------- /src/assets/images/tabbar/1481654067168_.pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/tabbar/1481654067168_.pic.jpg -------------------------------------------------------------------------------- /src/assets/images/tabbar/graycontacts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/tabbar/graycontacts.png -------------------------------------------------------------------------------- /src/assets/images/tabbar/grayconversation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/tabbar/grayconversation.png -------------------------------------------------------------------------------- /src/assets/images/tabbar/highlightconversation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/tabbar/highlightconversation.png -------------------------------------------------------------------------------- /src/assets/images/tabbar/higtlightcontacts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/tabbar/higtlightcontacts.png -------------------------------------------------------------------------------- /src/assets/images/tabbar/session2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/tabbar/session2x.png -------------------------------------------------------------------------------- /src/assets/images/tabbar/sessionhighlight2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/tabbar/sessionhighlight2x.png -------------------------------------------------------------------------------- /src/assets/images/web-demo-base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/images/web-demo-base.png -------------------------------------------------------------------------------- /src/assets/messages/failed@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/messages/failed@3x.png -------------------------------------------------------------------------------- /src/assets/messages/img_xmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/messages/img_xmark.png -------------------------------------------------------------------------------- /src/assets/messages/read@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/messages/read@3x.png -------------------------------------------------------------------------------- /src/assets/messages/received@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/messages/received@3x.png -------------------------------------------------------------------------------- /src/assets/messages/sending@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/messages/sending@3x.png -------------------------------------------------------------------------------- /src/assets/messages/sent@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/messages/sent@3x.png -------------------------------------------------------------------------------- /src/assets/online_icon/Busy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/online_icon/Busy.png -------------------------------------------------------------------------------- /src/assets/online_icon/Do_not_Disturb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/online_icon/Do_not_Disturb.png -------------------------------------------------------------------------------- /src/assets/online_icon/Offline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/online_icon/Offline.png -------------------------------------------------------------------------------- /src/assets/online_icon/Online.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/online_icon/Online.png -------------------------------------------------------------------------------- /src/assets/online_icon/custom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/online_icon/custom.png -------------------------------------------------------------------------------- /src/assets/online_icon/leave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/online_icon/leave.png -------------------------------------------------------------------------------- /src/assets/ring.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/assets/ring.mp3 -------------------------------------------------------------------------------- /src/components/EaseCallKit/alertModal.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 49 | 50 | 103 | -------------------------------------------------------------------------------- /src/components/EaseCallKit/components/miniStreamContainer.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 70 | 138 | -------------------------------------------------------------------------------- /src/components/EaseCallKit/config/initAgoraRtc.js: -------------------------------------------------------------------------------- 1 | import AgoraRTC from 'agora-rtc-sdk-ng'; 2 | 3 | const AgoraAppId = '15cb0d28b87b425ea613fc46f7c9f974'; 4 | //设置日志输出等级 5 | AgoraRTC.setLogLevel(3); 6 | 7 | export { AgoraAppId, AgoraRTC }; 8 | -------------------------------------------------------------------------------- /src/components/EaseCallKit/constants/callKitEvent.js: -------------------------------------------------------------------------------- 1 | /* CALLER 主叫 CALLEE 被叫 */ 2 | const CALLKIT_EVENT_CODE = { 3 | CALLER_ACCPET: 0, 4 | CALLEE_ACCPET: 1, 5 | CALLER_BUSY: 2, 6 | CALLEE_BUSY: 3, 7 | CALLER_REFUSE: 4, 8 | CALLEE_REFUSE: 5, 9 | HANGUP: 6, 10 | TIMEOUT: 7, 11 | CANCEL: 8, 12 | CALLER_CANCEL: 9, 13 | OTHER_HANDLE: 10, 14 | NOT_HAVE_CAMERA: 11, 15 | NOT_HAVE_MICROPHONE: 12, 16 | }; 17 | const CALLKIT_EVENT_TYPE = { 18 | [CALLKIT_EVENT_CODE.CALLER_ACCPET]: { 19 | code: CALLKIT_EVENT_CODE.CALLER_ACCPET, 20 | description: 'CALLER_ACCPET', 21 | }, 22 | [CALLKIT_EVENT_CODE.CALLEE_ACCPET]: { 23 | code: CALLKIT_EVENT_CODE.CALLEE_ACCPET, 24 | description: 'CALLEE_ACCPET', 25 | }, 26 | [CALLKIT_EVENT_CODE.CALLER_BUSY]: { 27 | code: CALLKIT_EVENT_CODE.CALLER_BUSY, 28 | description: 'CALLER_BUSY', 29 | }, 30 | [CALLKIT_EVENT_CODE.CALLEE_BUSY]: { 31 | code: CALLKIT_EVENT_CODE.CALLEE_BUSY, 32 | description: 'CALLEE_BUSY', 33 | }, 34 | [CALLKIT_EVENT_CODE.CALLER_REFUSE]: { 35 | code: CALLKIT_EVENT_CODE.CALLER_REFUSE, 36 | description: 'CALLER_REFUSE', 37 | }, 38 | [CALLKIT_EVENT_CODE.CALLEE_REFUSE]: { 39 | code: CALLKIT_EVENT_CODE.CALLEE_REFUSE, 40 | description: 'CALLEE_REFUSE', 41 | }, 42 | [CALLKIT_EVENT_CODE.HANGUP]: { 43 | code: CALLKIT_EVENT_CODE.HANGUP, 44 | description: 'HANGUP', 45 | }, 46 | [CALLKIT_EVENT_CODE.TIMEOUT]: { 47 | code: CALLKIT_EVENT_CODE.TIMEOUT, 48 | description: 'TIMEOUT', 49 | }, 50 | [CALLKIT_EVENT_CODE.CANCEL]: { 51 | code: CALLKIT_EVENT_CODE.CANCEL, 52 | description: 'CANCEL', 53 | }, 54 | [CALLKIT_EVENT_CODE.CALLER_CANCEL]: { 55 | code: CALLKIT_EVENT_CODE.CALLER_CANCEL, 56 | description: 'CALLER_CANCEL', 57 | }, 58 | [CALLKIT_EVENT_CODE.OTHER_HANDLE]: { 59 | code: CALLKIT_EVENT_CODE.OTHER_HANDLE, 60 | description: 'OTHER_HANDLE', 61 | }, 62 | [CALLKIT_EVENT_CODE.NOT_HAVE_CAMERA]: { 63 | code: CALLKIT_EVENT_CODE.NOT_HAVE_CAMERA, 64 | description: 'NOT_HAVE_CAMERA', 65 | }, 66 | [CALLKIT_EVENT_CODE.NOT_HAVE_MICROPHONE]: { 67 | code: CALLKIT_EVENT_CODE.NOT_HAVE_MICROPHONE, 68 | description: 'NOT_HAVE_MICROPHONE', 69 | }, 70 | }; 71 | 72 | export { CALLKIT_EVENT_CODE, CALLKIT_EVENT_TYPE }; 73 | -------------------------------------------------------------------------------- /src/components/EaseCallKit/constants/imClient.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 该常量实为暂存外层传入的实力化后的SDK客户端,以及msg 构建方法,供callkit内部进行调用。 3 | * */ 4 | let IMClient = null; 5 | let MsgCreateFn = null; 6 | const _setImClient = (imclient, msgCreateFn) => { 7 | if (!imclient) throw 'imclient must pass!'; 8 | if (!msgCreateFn) throw 'create message function must pass!'; 9 | IMClient = imclient; 10 | MsgCreateFn = msgCreateFn; 11 | }; 12 | 13 | export { IMClient, MsgCreateFn, _setImClient }; 14 | -------------------------------------------------------------------------------- /src/components/EaseCallKit/constants/index.js: -------------------------------------------------------------------------------- 1 | const MSG_TYPE = 'rtcCallWithAgora'; 2 | const CALL_INVITE_TEXT = { 3 | 0: '邀请您进行语音通话', 4 | 1: '邀请您进行视频通话', 5 | 2: '邀请您进行多人通话', 6 | }; 7 | const CALL_TYPES = { 8 | SINGLE_VOICE: 0, 9 | SINGLE_VIDEO: 1, 10 | MULTI_VIDEO: 2, 11 | }; 12 | const CALL_TYPE = { 13 | [CALL_TYPES.SINGLE_VOICE]: 0, //一对一语音 14 | [CALL_TYPES.SINGLE_VIDEO]: 1, //一对一视频 15 | [CALL_TYPES.MULTI_VIDEO]: 2, //多人音视频 16 | }; 17 | const CALLSTATUS = { 18 | idle: 0, //闲置 19 | inviting: 1, //邀请中 20 | alerting: 2, //弹窗中 21 | confirmRing: 3, // caller 22 | receivedConfirmRing: 4, // callee 23 | answerCall: 5, 24 | receivedAnswerCall: 6, 25 | confirmCallee: 7, 26 | }; 27 | 28 | const CALL_ACTIONS_TYPE = { 29 | INVITE: 'invite', //邀请 30 | RTC_CALL: 'rtcCall', //rtcCall 31 | CANCEL: 'cancelCall', //取消 32 | ANSWER: 'answerCall', //答复 33 | ALERT: 'alert', //弹出通话窗口 34 | CONFIRM_RING: 'confirmRing', //窗口响铃待确认 35 | CONFIRM_CALLEE: 'confirmCallee', //被叫方确认 36 | VIDEO_TO_VOICE: 'videoToVoice', //视频转语音 37 | }; 38 | 39 | const ANSWER_TYPE = { 40 | BUSY: 'busy', //忙碌 41 | ACCPET: 'accept', //同意 42 | REFUSE: 'refuse', //拒绝 43 | }; 44 | export { 45 | MSG_TYPE, 46 | CALL_TYPES, 47 | CALL_TYPE, 48 | CALL_INVITE_TEXT, 49 | CALLSTATUS, 50 | CALL_ACTIONS_TYPE, 51 | ANSWER_TYPE, 52 | }; 53 | -------------------------------------------------------------------------------- /src/components/EaseCallKit/hooks/index.js: -------------------------------------------------------------------------------- 1 | import useManageChannel from './useManageChannel'; 2 | import useCallKitEvent from './useCallKitEvent'; 3 | export { useManageChannel, useCallKitEvent }; 4 | -------------------------------------------------------------------------------- /src/components/EaseCallKit/hooks/useCallKitEvent.js: -------------------------------------------------------------------------------- 1 | import { reactive } from 'vue'; 2 | import { 3 | CALLKIT_EVENT_CODE, 4 | CALLKIT_EVENT_TYPE, 5 | } from '../constants/callKitEvent'; 6 | const channelEvents = reactive({}); 7 | const EVENT_NAME = 'EASECALLKIT'; 8 | const EVENT_LEVEL = { 9 | 0: 'SUCCESS', 10 | 1: 'WARNING', 11 | 2: 'FAIL', 12 | 3: 'INFO', 13 | }; 14 | export default function useChannelEvent() { 15 | const SUB_CHANNEL_EVENT = (eventName = 'EASECALLKIT', fn) => { 16 | if (channelEvents[eventName]) { 17 | channelEvents[eventName].push(fn); 18 | } else { 19 | channelEvents[eventName] = []; 20 | channelEvents[eventName].push(fn); 21 | } 22 | }; 23 | const PUB_CHANNEL_EVENT = (eventName = 'EASECALLKIT', data) => { 24 | /** 25 | * const eventParams = { 26 | * type: Object,对外抛出type类别 27 | * ext: Object,对外抛出事件内容 28 | * callType: number,音视频会话类型 29 | * eventHxId: string 事件定义来源 30 | * } 31 | */ 32 | if (channelEvents[eventName]) { 33 | channelEvents[eventName].length && 34 | channelEvents[eventName].forEach((fn) => { 35 | fn(data); 36 | }); 37 | } 38 | }; 39 | const UN_SUB_CHANNEL_ENENT = (eventName = 'EASECALLK') => { 40 | if (channelEvents[eventName]) { 41 | delete channelEvents[eventName]; 42 | } 43 | }; 44 | return { 45 | EVENT_NAME, 46 | EVENT_LEVEL, 47 | CALLKIT_EVENT_CODE, 48 | CALLKIT_EVENT_TYPE, 49 | SUB_CHANNEL_EVENT, 50 | PUB_CHANNEL_EVENT, 51 | UN_SUB_CHANNEL_ENENT, 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /src/components/EaseCallKit/utils/createUid.js: -------------------------------------------------------------------------------- 1 | function uuid() { 2 | var temp_url = URL.createObjectURL(new Blob()); 3 | var uuid = temp_url.toString(); // blob:https://xxx.com/b250d159-e1b6-4a87-9002-885d90033be3 4 | URL.revokeObjectURL(temp_url); 5 | return uuid.substr(uuid.lastIndexOf('/') + 1); 6 | } 7 | export default uuid; 8 | -------------------------------------------------------------------------------- /src/components/EaseCallKit/utils/getChannelDetails.js: -------------------------------------------------------------------------------- 1 | export default function (EaseIMConn, payload) { 2 | const { username, channelName } = payload; 3 | const myHeaders = new Headers(); 4 | myHeaders.append('authorization', `Bearer ${EaseIMConn.context.accessToken}`); 5 | var requestOptions = { 6 | method: 'GET', 7 | headers: myHeaders, 8 | redirect: 'follow', 9 | }; 10 | return new Promise(function (resolve, reject) { 11 | fetch( 12 | `${ 13 | EaseIMConn.apiUrl 14 | }/channel/mapper?userAccount=${username}&channelName=${channelName}&appkey=${window.encodeURIComponent( 15 | EaseIMConn.appKey, 16 | )}`, 17 | requestOptions, 18 | ) 19 | .then((response) => response.text()) 20 | .then((result) => { 21 | resolve(JSON.parse(result)); 22 | }) 23 | .catch((error) => { 24 | reject(error); 25 | }); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/EaseCallKit/utils/getRtcToken.js: -------------------------------------------------------------------------------- 1 | export default function (EaseIMConn, payload) { 2 | const { username, channelName } = payload; 3 | const myHeaders = new Headers(); 4 | myHeaders.append('authorization', `Bearer ${EaseIMConn.context.accessToken}`); 5 | var requestOptions = { 6 | method: 'GET', 7 | headers: myHeaders, 8 | redirect: 'follow', 9 | }; 10 | return new Promise(function (resolve, reject) { 11 | fetch( 12 | `${ 13 | EaseIMConn.apiUrl 14 | }/token/rtcToken/v1?userAccount=${username}&channelName=${channelName}&appkey=${window.encodeURIComponent( 15 | EaseIMConn.appKey, 16 | )}`, 17 | requestOptions, 18 | ) 19 | .then((response) => response.text()) 20 | .then((result) => { 21 | resolve(JSON.parse(result)); 22 | }) 23 | .catch((error) => { 24 | reject(error); 25 | }); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/InviteCallMembers/index.vue: -------------------------------------------------------------------------------- 1 | 52 | 71 | 72 | -------------------------------------------------------------------------------- /src/components/UserStatus/index.vue: -------------------------------------------------------------------------------- 1 | 76 | 86 | 87 | 106 | -------------------------------------------------------------------------------- /src/components/Welcome/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 17 | 18 | 75 | -------------------------------------------------------------------------------- /src/constant/emojis.js: -------------------------------------------------------------------------------- 1 | export const emojis = [ 2 | '😀', 3 | '😃', 4 | '😄', 5 | '😁', 6 | '😆', 7 | '😅', 8 | '🤣', 9 | '😂', 10 | '🙂', 11 | '🙃', 12 | '😉', 13 | '😊', 14 | '😇', 15 | '😍', 16 | '🤩', 17 | '😘', 18 | '😗', 19 | '😚', 20 | '😙', 21 | '😋', 22 | '😛', 23 | '😜', 24 | '🤪', 25 | '😝', 26 | '🤑', 27 | '🤗', 28 | '🤭', 29 | '🤫', 30 | '🤔', 31 | '🤐', 32 | '🤨', 33 | '😐', 34 | '😑', 35 | '😶', 36 | '😏', 37 | '😒', 38 | '🙄', 39 | '😬', 40 | '🤥', 41 | '😌', 42 | '😔', 43 | '😪', 44 | '🤤', 45 | '😴', 46 | '😷', 47 | '🤒', 48 | '🤕', 49 | '🤢', 50 | '🤮', 51 | '🤧', 52 | '😵', 53 | '🤯', 54 | '🤠', 55 | '😎', 56 | '🤓', 57 | '🧐', 58 | '😕', 59 | '😟', 60 | '🙁', 61 | '😮', 62 | '😯', 63 | '😲', 64 | '😳', 65 | '😦', 66 | '😧', 67 | '😨', 68 | '😰', 69 | '😥', 70 | '😢', 71 | '😭', 72 | '😱', 73 | '😖', 74 | '😣', 75 | '😞', 76 | '😓', 77 | '😩', 78 | '😫', 79 | '😤', 80 | '😡', 81 | '😠', 82 | '🤬', 83 | '😈', 84 | '👿', 85 | '💀', 86 | '💩', 87 | '🤡', 88 | '👹', 89 | '👺', 90 | '👻', 91 | '👽', 92 | '👾', 93 | '🤖', 94 | '😺', 95 | '😸', 96 | '😹', 97 | '😻', 98 | '😼', 99 | '😽', 100 | '🙀', 101 | '😿', 102 | '😾', 103 | '💋', 104 | '👋', 105 | '🤚', 106 | '🖐', 107 | '✋', 108 | '🖖', 109 | '👌', 110 | '🤞', 111 | '🤟', 112 | '🤘', 113 | '🤙', 114 | '👈', 115 | '👉', 116 | '👆', 117 | '🖕', 118 | '👇', 119 | '👍', 120 | '👎', 121 | '✊', 122 | '👊', 123 | '🤛', 124 | '🤜', 125 | '👏', 126 | '🙌', 127 | '👐', 128 | '🤲', 129 | '🤝', 130 | '🙏', 131 | '💅', 132 | '🤳', 133 | '💪', 134 | '👂', 135 | '👃', 136 | '🧠', 137 | '👀', 138 | '👁', 139 | '👅', 140 | '👄', 141 | '👶', 142 | '🧒', 143 | '👦', 144 | '👧', 145 | '🧑', 146 | '👱', 147 | '👨', 148 | '🧔', 149 | '👱‍', 150 | '👨‍', 151 | '👨‍', 152 | '👩', 153 | '👱‍', 154 | '👩‍', 155 | '👩‍', 156 | '👩‍', 157 | '👩‍', 158 | '🧓', 159 | '👴', 160 | '👵', 161 | '🙍', 162 | '🙅', 163 | '🙆', 164 | '💁', 165 | '🙋', 166 | '🙇', 167 | '🙇‍', 168 | '🙇‍', 169 | '🤦', 170 | '🤷', 171 | '🤷‍', 172 | '🤷‍', 173 | '👨‍⚕️', 174 | '👩‍⚕️', 175 | '👨‍🎓', 176 | '👩‍🎓', 177 | '👨‍🏫', 178 | '👩‍🏫', 179 | '👨‍⚖️', 180 | '👩‍⚖️', 181 | '👨‍🌾', 182 | '👩‍🌾', 183 | '👨‍🍳', 184 | '👩‍🍳', 185 | '👨‍🔧', 186 | '👩‍🔧', 187 | '👨‍🏭', 188 | '👩‍🏭', 189 | '👨‍💼', 190 | '👩‍💼', 191 | '👨‍🔬', 192 | '👩‍🔬', 193 | '👨‍💻', 194 | '👩‍💻', 195 | '👨‍🎤', 196 | '👩‍🎤', 197 | '👨‍🎨', 198 | '👩‍🎨', 199 | '👨‍✈️', 200 | '👩‍✈️', 201 | '👨‍🚀', 202 | '👩‍🚀', 203 | '👨‍🚒', 204 | '👩‍🚒', 205 | '👮', 206 | '👮‍♂️', 207 | '👮‍♀️', 208 | '🕵', 209 | '🕵️‍♂️', 210 | '🕵️‍♀️', 211 | '💂', 212 | '💂‍', 213 | '💂‍', 214 | '👷', 215 | '👷‍', 216 | '👷‍', 217 | '🤴', 218 | '👸', 219 | '👳', 220 | '👳‍', 221 | '👳‍', 222 | '👲', 223 | '🧕', 224 | '🤵', 225 | '👰', 226 | '🤰', 227 | '🤱', 228 | '👼', 229 | '🎅', 230 | '🤶', 231 | '🧙', 232 | '🧚', 233 | '🧛', 234 | '🧜', 235 | '🧝', 236 | '🧞', 237 | '🧟', 238 | '💆', 239 | '💇', 240 | '🚶', 241 | '🏃', 242 | '💃', 243 | '🕺', 244 | '🕴', 245 | '👯', 246 | '🧖', 247 | '🧖‍', 248 | '🧖‍', 249 | '🧘', 250 | '👭', 251 | '👫', 252 | '👬', 253 | '💏', 254 | '👨‍', 255 | '👩‍', 256 | '💑', 257 | '👨‍', 258 | '👩‍', 259 | '👪', 260 | '👨‍👩‍👦', 261 | '👨‍👩‍👧', 262 | '👨‍👩‍👧‍👦', 263 | '👨‍👩‍👦‍👦', 264 | '👨‍👩‍👧‍👧', 265 | '👨‍👨‍👦', 266 | '👨‍👨‍👧', 267 | '👨‍👨‍👧‍👦', 268 | '👩‍👩‍👦', 269 | '👩‍👩‍👧', 270 | '👩‍👩‍👧‍👦', 271 | '👩‍👩‍👦‍👦', 272 | '👩‍👩‍👧‍👧', 273 | '👨‍👦', 274 | '👨‍👦‍👦', 275 | '👨‍👧', 276 | '👨‍👧‍👦', 277 | '👨‍👧‍👧', 278 | '👩‍👦', 279 | '👩‍👦‍👦', 280 | '👩‍👧', 281 | '👩‍👧‍👦', 282 | '👩‍👧‍👧', 283 | '🗣', 284 | '👤', 285 | '👥', 286 | '👣', 287 | '🌂', 288 | '☂', 289 | '👓', 290 | '🕶', 291 | '👔', 292 | '👕', 293 | '👖', 294 | '🧣', 295 | '🧤', 296 | '🧥', 297 | '🧦', 298 | '👗', 299 | '👘', 300 | '👙', 301 | '👚', 302 | '👛', 303 | '👜', 304 | '👝', 305 | '🎒', 306 | '👞', 307 | '👟', 308 | '👠', 309 | '👡', 310 | '👢', 311 | '👑', 312 | '👒', 313 | '🎩', 314 | '🎓', 315 | '🧢', 316 | '⛑', 317 | '💄', 318 | '💍', 319 | '💼', 320 | ]; 321 | -------------------------------------------------------------------------------- /src/constant/errorCode.js: -------------------------------------------------------------------------------- 1 | // const ERROR_TYPE = { 2 | // login: 1, 3 | // }; 4 | 5 | export const ERROR_MAP_DESCRIPTION = { 6 | /* 登陆相关 7 | */ 8 | 0: { 9 | none: '未知错误!', 10 | }, 11 | 1: { 12 | 'invalid password': '密码错误!', 13 | 'login failed': '登陆失败!', 14 | 'user not found': '该用户不存在!', 15 | }, 16 | 17: { 17 | duplicate_unique_property_exists: 'id已存在!', 18 | resource_limited: '注册已达上限请开通企业版!', 19 | unauthorized: '未开放授权注册!', 20 | resource_not_found: '账号不存在!', 21 | }, 22 | 28: { 23 | 'appkey or token error': '未登录!', 24 | }, 25 | 101: { 26 | 'file exceeding maximum limit': '文件大小超出限制(默认10M)!', 27 | none: '文件相关未知错误!', 28 | }, 29 | 217: { 30 | 'the user was kicked by other device': '其他端踢出了该账号!', 31 | }, 32 | /* 群组相关 */ 33 | 602: { 34 | 'not in group or chatroom': '已不再该群组中!', 35 | }, 36 | 605: { 37 | 'The chat room dose not exist.': '此群不存在!', 38 | }, 39 | /* 消息相关 */ 40 | 221: { 41 | 'not contact': '非好友关系,不可发送消息!', 42 | }, 43 | 400: { 44 | 'UserId password error.': '用户密码错误!', 45 | 'Please wait a moment while trying to send.': 46 | '验证码在有效期内,请勿重复发送!', 47 | 'Image verification code error.': 48 | '图片验证码错误,请更换验证码或重新输入!', 49 | 'Image code id cannot be empty.': '请填入图片验证码!', 50 | 'Phone number cannot be empty.': '获取图片验证码请填入手机号!', 51 | 'UserId hfp already exists.': '用户已注册!', 52 | 'phone number illegal': '手机号不合法!', 53 | 'Please send SMS to get mobile phone verification code.': 54 | '请发送短信获取手机验证码!', 55 | 'SMS verification code error.': '验证码错误!', 56 | }, 57 | 603: { 58 | blocked: '对方已将您加入黑名单!', 59 | blacklist: '已在该群黑名单当中!无法加入该群。', 60 | already: '已加入该群!', 61 | }, 62 | 504: { 63 | 'exceed recall time limit': '消息超过可撤回时间!', 64 | }, 65 | 507: { 66 | muted: '已被禁言!', 67 | }, 68 | 508: { 69 | moderation: '内容审核不通过!请检查发送内容。', 70 | }, 71 | // e.type === '603' 被拉黑 72 | // e.type === '605' 群组不存在 73 | // e.type === '602' 不在群组或聊天室中 74 | // e.type === '504' 撤回消息时超出撤回时间 75 | // e.type === '505' 未开通消息撤回 76 | // e.type === '506' 没有在群组或聊天室白名单 77 | // e.type === '501' 消息包含敏感词 78 | // e.type === '502' 被设置的自定义拦截捕获 79 | // e.type === '503' 未知错误 80 | }; 81 | -------------------------------------------------------------------------------- /src/constant/index.js: -------------------------------------------------------------------------------- 1 | export * from './messageType'; 2 | export * from './informType'; 3 | export * from './warningText'; 4 | export * from './emojis'; 5 | export * from './onLineStatus'; 6 | export * from './errorCode'; 7 | -------------------------------------------------------------------------------- /src/constant/informType.js: -------------------------------------------------------------------------------- 1 | import { CHAT_TYPE } from './messageType'; 2 | export const INFORM_NAME = { 3 | FRIEND_INVITE: '好友申请', 4 | FRIEND_BUILD: '已成为好友', 5 | FRIEND_DELETED: '好友关系解除', 6 | FRIEND_APPLY_REFUSE: '好友申请被拒绝', 7 | FRIEND_APPLY_AGREE: '好友申请已通过', 8 | GROUP_JOIN_SUCCESS: '成员入群成功', 9 | GROUP_QUIT_SUCCESS: '成员退出群组成功', 10 | GROUP_INVITE_JOIN: '邀请加入群组', 11 | GROUP_REQUESTTOJOIN: '申请加入群组', 12 | GROUP_REMOVE_MEMBER: '移出了群成员', 13 | GROUP_DIRECT_MEMBER: '被直接拉入群组', 14 | GROUP_UPDATE_ANNOUNCEMENT: '更新了群组公告', 15 | GROUP_SET_ADMIN: '设定为管理员', 16 | GROUP_REMOVE_ADMIN: '移除管理员', 17 | GROUP_MUTE_MEMBER: '禁言成员', 18 | GROUP_UNMUTE_MEMBER: '移除成员禁言', 19 | GROUP_DESTORY: '解散群组', 20 | GROUP_ACCEPTREQUEST: '同意入群申请', 21 | GROUP_UPDATE_INFO: '更新群组信息', 22 | GROUP_UPDATE_MEMBER_ATTRIBUTES: '群组成员属性更新', 23 | }; 24 | export const INFORM_TYPE = { 25 | subscribe: INFORM_NAME.FRIEND_INVITE, 26 | subscribed: INFORM_NAME.FRIEND_BUILD, 27 | unsubscribed: INFORM_NAME.FRIEND_DELETED, 28 | other_person_refuse: INFORM_NAME.FRIEND_APPLY_REFUSE, 29 | other_person_agree: INFORM_NAME.FRIEND_APPLY_AGREE, 30 | memberPresence: INFORM_NAME.GROUP_JOIN_SUCCESS, 31 | memberAbsence: INFORM_NAME.GROUP_QUIT_SUCCESS, 32 | inviteToJoin: INFORM_NAME.GROUP_INVITE_JOIN, 33 | removeMember: INFORM_NAME.GROUP_REMOVE_MEMBER, 34 | directJoined: INFORM_NAME.GROUP_DIRECT_MEMBER, 35 | updateAnnouncement: INFORM_NAME.GROUP_UPDATE_ANNOUNCEMENT, 36 | setAdmin: INFORM_NAME.GROUP_SET_ADMIN, 37 | removeAdmin: INFORM_NAME.GROUP_REMOVE_ADMIN, 38 | muteMember: INFORM_NAME.GROUP_MUTE_MEMBER, 39 | unmuteMember: INFORM_NAME.GROUP_UNMUTE_MEMBER, 40 | destroy: INFORM_NAME.GROUP_DESTORY, 41 | requestToJoin: INFORM_NAME.GROUP_REQUESTTOJOIN, 42 | acceptRequest: INFORM_NAME.GROUP_ACCEPTREQUEST, 43 | updateInfo: INFORM_NAME.GROUP_UPDATE_INFO, 44 | memberAttributesUpdate: INFORM_NAME.GROUP_UPDATE_MEMBER_ATTRIBUTES, 45 | }; 46 | export const INFORM_FROM = { 47 | FRIEND: CHAT_TYPE.SINGLE, 48 | GROUP: CHAT_TYPE.GROUP, 49 | }; 50 | export default { 51 | INFORM_TYPE, 52 | INFORM_FROM, 53 | }; 54 | -------------------------------------------------------------------------------- /src/constant/messageType.js: -------------------------------------------------------------------------------- 1 | export const SESSION_MESSAGE_TYPE = { 2 | img: '[图片]', 3 | file: '[文件]', 4 | audio: '[语音]', 5 | loc: '[位置]', 6 | video: '[视频]', 7 | }; 8 | 9 | export const CUSTOM_MSG_EVENT_TYPE = { 10 | userCard: '个人名片', 11 | }; 12 | export const ALL_MESSAGE_TYPE = { 13 | TEXT: 'txt', 14 | IMAGE: 'img', 15 | AUDIO: 'audio', 16 | LOCAL: 'loc', 17 | VIDEO: 'video', 18 | FILE: 'file', 19 | CUSTOM: 'custom', 20 | CMD: 'cmd', 21 | INFORM: 'inform', //这个类型不在环信消息类型内,属于自己定义的一种系统通知类的消息。 22 | }; 23 | export const CUSTOM_MESSAGE_TYPE = { 24 | INFORM: 'inform', //这个类型不在环信消息类型内,属于自己定义的一种系统通知类的消息。 25 | }; 26 | export const CHAT_TYPE = { 27 | SINGLE: 'singleChat', 28 | GROUP: 'groupChat', 29 | }; 30 | 31 | export const MENTION_ALL = { 32 | TEXT: '所有人', 33 | VALUE: 'ALL', 34 | }; 35 | export const CHANGE_MESSAGE_BODAY_TYPE = { 36 | RECALL: 0, 37 | DELETE: 1, 38 | MODIFY: 2, 39 | }; 40 | 41 | export const MESSAGE_STATUS = { 42 | SUCCESS: 'success', 43 | FAIL: 'fail', 44 | SENDING: 'sending', 45 | }; 46 | export const MESSAGE_STATUS_TYPE = { 47 | READ_STATUS: 'readStatus', 48 | SEND_STATUS: 'sendStatus', 49 | CHANLE_STATUS: 'chanleStatus', 50 | }; 51 | 52 | //单个消息列表最大长度 53 | export const MAX_MESSAGE_LIST_COUNT = 100; 54 | export default { 55 | SESSION_MESSAGE_TYPE, 56 | CUSTOM_MSG_EVENT_TYPE, 57 | ALL_MESSAGE_TYPE, 58 | CHAT_TYPE, 59 | MENTION_ALL, 60 | CHANGE_MESSAGE_BODAY_TYPE, 61 | MESSAGE_STATUS, 62 | MESSAGE_STATUS_TYPE, 63 | }; 64 | -------------------------------------------------------------------------------- /src/constant/onLineStatus.js: -------------------------------------------------------------------------------- 1 | export const onLineStatus = { 2 | Online: { label: '在线', style: 'background-color:#49FD1D' }, 3 | Leave: { label: '离开', style: 'background-color:#4E4239' }, 4 | Cloaking: { 5 | label: '勿扰', 6 | style: 'background-color:#F27014', 7 | }, 8 | Offline: { label: '离线', style: 'background-color:#BEC1BD' }, 9 | }; 10 | -------------------------------------------------------------------------------- /src/constant/warningText.js: -------------------------------------------------------------------------------- 1 | export const SWINDLER_GO_DIE = [ 2 | '时刻绷紧防范之弦,谨防新型电信诈骗。', 3 | '号码陌⽣勿轻接,虚拟电话设陷阱。', 4 | '飞来⼤奖莫惊喜,让您掏钱洞⽆底。', 5 | '不存贪婪⼼,诈骗难得逞。', 6 | '提⾼防骗意识,增强防范能⼒,构筑电信诈骗“防⽕墙。', 7 | '骗⼈之⼼不可有,防骗之⼼不可⽆。', 8 | '⽹上汇款需警惕,电话核实莫⼤意。', 9 | '执法办案有规范,怎会汇款到个⼈。', 10 | '不明电话及时挂,可疑短信不要回。', 11 | '⽹络购物便利多,⽀付流程要仔细。', 12 | '投资理财和股票,多是骗⼦设的套。', 13 | '不信陌⽣短信,拒接陌⽣来电,让骗⼦⽆从下⼿。', 14 | '⼀不贪⼆不占,诈骗再诡玩不转。', 15 | '遇到恐吓要淡定,说你违法莫慌张,⼀旦难分真与假,警方电话110。', 16 | '陌⽣来电要提防,多⽅确认防上当。', 17 | '致富⼗年功,诈骗⼀场空。', 18 | '积极加强⾃我防范意识,共同提⾼识骗防骗能⼒。', 19 | '防范⽹络的骗术,不贪便宜要记住。', 20 | ' 和谐⽹络你我共享,电信诈骗⼤家共防。', 21 | '真假⽹店难分辨,购物不慎就被骗。', 22 | '个⼈信息顶重要,密码账号保管好。', 23 | '飞来⼤奖莫惊喜,让你掏钱洞⽆底。', 24 | '安全账户⼦虚有,⼤额汇款要三思。', 25 | '异地刷卡消费现,不要着急忙给钱。', 26 | '电话通知接传票,实为骗钱设圈套。', 27 | '刷卡消费莫离眼,防⽌盗刷盯着点。', 28 | '⼼中⽆贪念,骗局远⾝边。', 29 | '转账汇款须谨慎,万元以上到柜⾯。', 30 | '陌⽣电话勿轻信,对⽅⾝份要核清。', 31 | '电信诈骗不难防,不给不要不上当。', 32 | '陌⽣信息不要理,以防害⼈⼜害⼰。', 33 | ]; 34 | 35 | export const EASEIM_HINT = 36 | '【安全提示】本应用仅用于环信产品功能开发测试,请勿用于非法用途。任何涉及转账、汇款、裸聊、网恋、网购退款、投资理财等统统都是诈骗,请勿相信!'; 37 | 38 | export const WARM_TIP = '【温馨提示】该群仅供试用,72小时后将被删除!'; 39 | export default { SWINDLER_GO_DIE, EASEIM_HINT, WARM_TIP }; 40 | -------------------------------------------------------------------------------- /src/hooks/index.js: -------------------------------------------------------------------------------- 1 | import usePlayRing from './usePlayRing'; 2 | import useGetUserMapInfo from './useGetUserMapInfo'; 3 | import { useSetEMLogConfig } from './useSetEMLogConfig'; 4 | import useSordedContactsWithPinyin from './useSordedContactsWithPinyin'; 5 | import { useUserInfoExt } from './useUserInfoExt'; 6 | export { 7 | usePlayRing, 8 | useGetUserMapInfo, 9 | useSetEMLogConfig, 10 | useSordedContactsWithPinyin, 11 | useUserInfoExt, 12 | }; 13 | -------------------------------------------------------------------------------- /src/hooks/useGetUserMapInfo.js: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import { useStore } from 'vuex'; 3 | import defaultContactAvatar from '@/assets/images/avatar/theme2x.png'; 4 | import defaultGroupAvatar from '@/assets/images/avatar/jiaqun2x.png'; 5 | const useGetUserMapInfo = () => { 6 | const store = useStore(); 7 | /* UsersProfile */ 8 | //用户昵称 9 | const getUsersProfileDisplayName = (targetId) => 10 | store.getters['UsersProfile/getDisplayName'](targetId); 11 | //用户群内昵称(前提是该用户有在群内设置群属性) 12 | const getUsersProfileGroupDisplayName = (targetId, groupId) => 13 | store.getters['UsersProfile/getGroupDisplayName'](groupId, targetId); 14 | //用户头像 15 | const getUsersProfileAvatarUrl = (targetId) => 16 | store.getters['UsersProfile/getAvatarUrl'](targetId); 17 | /* 群组相关依赖数据源 */ 18 | //获取加入的群组列表 19 | const getJoinedGroupList = computed(() => store.getters.getJoinedGroupList); 20 | //获取群组详情(展示群组名称等信息) 21 | const groupDetailMap = computed(() => store.getters.getGroupDetailMap); 22 | //获取群组名 23 | const getGroupNameByGroupId = (groupId) => { 24 | return store.getters['getGroupName'](groupId); 25 | }; 26 | const getLoginNickNameById = () => { 27 | const { nickname, hxId } = store.state.loginUserInfo; 28 | // 优先获取用户设置的昵称, 否则获取环信ID 29 | return nickname || hxId; 30 | }; 31 | //获取联系人昵称 32 | //联系人昵称(联系人昵称仅展示remark 或者 该用户属性内昵称字段,不展示从消息内取出的昵称) 33 | const getContactsNickNameById = (targetId) => { 34 | return store.getters['UsersProfile/getContactsDisplayNickName'](targetId); 35 | }; 36 | //获取联系人头像 37 | //联系人头像(联系人头像仅展示该用户属性内头像字段,或展示默认头像,不展示从消息内取出的头像) 38 | const getContactsAvatarById = (targetId) => { 39 | return store.getters['UsersProfile/getContactsDisplayAvatarUrl'](targetId); 40 | }; 41 | //获取群组头像 42 | const getGroupAvatarByGroupId = (groupId) => { 43 | const groupInfo = groupDetailMap.value.get(groupId) ?? {}; 44 | // 优先获取群组设置的头像, 再次尝试获取自定义字段内携带的头像,否则获取默认头像 45 | return groupInfo?.avatar || groupInfo?.custom || defaultGroupAvatar; 46 | }; 47 | const getUserDisplayNameById = (targetId, groupId) => { 48 | if (groupId) { 49 | return getUsersProfileGroupDisplayName(groupId, targetId); 50 | } 51 | return getUsersProfileDisplayName(targetId); 52 | }; 53 | const getUserDisplayAvatarById = (targetId) => { 54 | return getUsersProfileAvatarUrl(targetId); 55 | }; 56 | return { 57 | getGroupNameByGroupId, 58 | getLoginNickNameById, 59 | getContactsNickNameById, 60 | getContactsAvatarById, 61 | getGroupAvatarByGroupId, 62 | getUserDisplayNameById, 63 | getUserDisplayAvatarById, 64 | }; 65 | }; 66 | export default useGetUserMapInfo; 67 | -------------------------------------------------------------------------------- /src/hooks/usePlayRing.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | import { ElMessageBox } from 'element-plus'; 3 | import { useLocalStorage } from '@vueuse/core'; 4 | import _ from 'lodash'; 5 | const is_need_open_ring = ref(false); 6 | export default function () { 7 | const isOpenPlayRing = useLocalStorage('is_open_play_ring', true); 8 | //必须在播放铃声之前保证有一次交互 9 | //触发交互 10 | const clickRing = () => { 11 | const ringDom = document.querySelector('#ring'); 12 | 13 | ringDom.pause(); 14 | }; 15 | //播放铃声 16 | const playRing = _.throttle(async () => { 17 | const ringDom = document.querySelector('#ring'); 18 | 19 | try { 20 | await ringDom.play(); 21 | } catch (error) { 22 | if (!is_need_open_ring.value) { 23 | openRing(); 24 | } 25 | } 26 | }, 3000); 27 | 28 | //请求播放权限 29 | const openRing = () => { 30 | is_need_open_ring.value = true; 31 | ElMessageBox.alert('由于浏览器策略限制,需确认后方可播放新消息提示音!', { 32 | confirmButtonText: 'OK', 33 | callback: () => { 34 | clickRing(); 35 | }, 36 | }); 37 | }; 38 | 39 | return { 40 | isOpenPlayRing, 41 | clickRing, 42 | playRing, 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /src/hooks/useSetEMLogConfig.js: -------------------------------------------------------------------------------- 1 | import { watchEffect } from 'vue'; 2 | import { useLocalStorage } from '@vueuse/core'; 3 | import { EMClient } from '@/IM'; 4 | export const useSetEMLogConfig = () => { 5 | const isOpenedEMLog = useLocalStorage('isOpenedEMLog', false); 6 | const closeEMLog = () => EMClient.logger.disableAll(); 7 | const openEMLog = () => { 8 | EMClient.logger.setConfig({ 9 | useCache: true, // 是否缓存 10 | maxCache: 3 * 1024 * 1024, // 最大缓存字节, 11 | }); 12 | // 缓存全部等级日志 13 | EMClient.logger.setLevel(0); 14 | EMClient.logger.enableAll(); 15 | }; 16 | const donwLoadEMLog = () => EMClient.logger.download(); 17 | watchEffect(() => { 18 | if (isOpenedEMLog.value) { 19 | openEMLog(); 20 | } else { 21 | closeEMLog(); 22 | } 23 | }); 24 | 25 | return { 26 | isOpenedEMLog, 27 | donwLoadEMLog, 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /src/hooks/useSordedContactsWithPinyin.js: -------------------------------------------------------------------------------- 1 | import { computed, reactive, watch, toRefs } from 'vue'; 2 | import _ from 'lodash'; 3 | import { pinyin } from 'pinyin-pro'; 4 | import store from '@/store'; 5 | 6 | const useSortedContactsWithPinyin = () => { 7 | const state = reactive({ 8 | sortedFriendListWithRemark: {}, 9 | }); 10 | 11 | const getContactsWithRemarkMap = computed(() => { 12 | return store.getters.getContactsWithRemarkMap; 13 | }); 14 | 15 | const getContactsUserInfosMap = computed(() => { 16 | return store.getters.getContactsUserInfosMap; 17 | }); 18 | 19 | const _getUserNickName = (hxId) => { 20 | if ( 21 | getContactsWithRemarkMap.value.has(hxId) && 22 | getContactsWithRemarkMap.value.get(hxId).remark 23 | ) { 24 | return getContactsWithRemarkMap.value.get(hxId).remark; 25 | } else if ( 26 | getContactsUserInfosMap.value.has(hxId) && 27 | getContactsUserInfosMap.value.get(hxId).nickname 28 | ) { 29 | return getContactsUserInfosMap.value.get(hxId).nickname; 30 | } else { 31 | return hxId; 32 | } 33 | }; 34 | 35 | watch( 36 | getContactsWithRemarkMap, 37 | (newMap) => { 38 | if (newMap.size > 0) { 39 | const resultObj = {}; 40 | const containerObj = {}; 41 | for (const [key, value] of newMap) { 42 | const pinyinKey = pinyin(_getUserNickName(key), { 43 | pattern: 'initial', 44 | })[0]; 45 | if (!containerObj[pinyinKey]) { 46 | containerObj[pinyinKey] = []; 47 | } 48 | containerObj[pinyinKey].push(value); 49 | } 50 | const keys = _.sortBy(_.keys(containerObj)); 51 | keys.forEach((k) => { 52 | resultObj[k] = containerObj[k]; 53 | }); 54 | state.sortedFriendListWithRemark = { ...resultObj }; 55 | } 56 | }, 57 | { 58 | immediate: true, 59 | deep: true, 60 | }, 61 | ); 62 | return { ...toRefs(state) }; 63 | }; 64 | 65 | export default useSortedContactsWithPinyin; 66 | -------------------------------------------------------------------------------- /src/hooks/useUserInfoExt.js: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import store from '@/store'; 3 | 4 | export function useUserInfoExt(msgOptions) { 5 | const ease_chat_uikit_user_info = computed(() => ({ 6 | nickname: store.getters.loginUserInfo.nickname, 7 | avatarURL: store.getters.loginUserInfo.avatarurl, 8 | })); 9 | 10 | const setUserInfoExt = (options) => { 11 | // 确保ext字段存在 12 | if (!options.ext) { 13 | options.ext = {}; 14 | } 15 | const userInfo = ease_chat_uikit_user_info.value; 16 | if (!userInfo.nickname && !userInfo.avatarURL) return options; 17 | options.ext.ease_chat_uikit_user_info = ease_chat_uikit_user_info.value; 18 | return options; 19 | }; 20 | 21 | return { 22 | setUserInfoExt, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | import router from './router'; 4 | import store from './store'; 5 | 6 | import ElementPlus from 'element-plus'; 7 | import './styles/element/index.scss'; 8 | import zhCn from 'element-plus/es/locale/lang/zh-cn'; 9 | createApp(App) 10 | .use(store) 11 | .use(router) 12 | .use(ElementPlus, { locale: zhCn }) 13 | .mount('#app'); 14 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router'; 2 | import NProgress from 'nprogress'; // progress bar 3 | import 'nprogress/nprogress.css'; // progress bar style 4 | import store from '@/store'; 5 | import Login from '../views/Login'; 6 | 7 | const routes = [ 8 | { 9 | path: '/', 10 | name: 'login', 11 | component: Login, 12 | }, 13 | /* 登陆页 */ 14 | { 15 | path: '/login', 16 | name: 'Login', 17 | component: () => import('../views/Login'), 18 | meta: { 19 | title: '登陆环信', 20 | }, 21 | }, 22 | /* 聊天页 */ 23 | { 24 | path: '/chat', 25 | name: 'Chat', 26 | redirect: '/chat/conversation', 27 | component: () => import('../views/Chat'), 28 | meta: { 29 | title: '开始聊天', 30 | }, 31 | children: [ 32 | /* 会话列表 */ 33 | { 34 | path: 'conversation', 35 | name: 'Conversation', 36 | meta: { 37 | title: '会话', 38 | requiresAuth: true, 39 | }, 40 | component: () => import('../views/Chat/components/Conversation'), 41 | children: [ 42 | //系统通知详情框 43 | { 44 | path: 'informdetails', 45 | component: () => import('../views/Chat/components/InformDetails'), 46 | }, 47 | //聊天对话框 48 | { 49 | path: 'message', 50 | component: () => import('../views/Chat/components/Message'), 51 | }, 52 | ], 53 | }, 54 | /* 联系人页 */ 55 | { 56 | path: 'contacts', 57 | name: 'Contacts', 58 | meta: { 59 | title: '联系页', 60 | requiresAuth: true, 61 | }, 62 | component: () => import('../views/Chat/components/Contacts'), 63 | children: [ 64 | { 65 | path: 'message', 66 | 67 | component: () => import('../views/Chat/components/Message'), 68 | }, 69 | //系统通知详情框 70 | { 71 | path: 'informdetails', 72 | component: () => import('../views/Chat/components/InformDetails'), 73 | }, 74 | { 75 | path: 'contactInfos', 76 | component: () => 77 | import( 78 | '../views/Chat/components/Contacts/components/ContactInfos.vue' 79 | ), 80 | }, 81 | ], 82 | }, 83 | ], 84 | }, 85 | ]; 86 | 87 | const router = createRouter({ 88 | history: createWebHistory(process.env.BASE_URL), 89 | routes, 90 | }); 91 | router.beforeEach((to, from, next) => { 92 | NProgress.start(); 93 | // const loginState = store.state.loginState 94 | const EASEIM_loginUser = window.localStorage.getItem('EASEIM_loginUser'); 95 | const loginUserFromStorage = JSON.parse(EASEIM_loginUser) || {}; 96 | if (to.matched.some((record) => record.meta.requiresAuth)) { 97 | //需要登陆 98 | if (loginUserFromStorage.user && loginUserFromStorage.accessToken) { 99 | //token存在,放行 100 | next(); 101 | NProgress.done(); 102 | } else { 103 | //token不存在,跳转登陆页面 104 | next({ path: '/login' }); 105 | NProgress.done(); 106 | } 107 | } else { 108 | next(); 109 | NProgress.done(); 110 | } 111 | }); 112 | export default router; 113 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex'; 2 | import { EMClient } from '@/IM'; 3 | import Conversation from './modules/conversation'; 4 | import Contacts from './modules/contacts'; 5 | import Message from './modules/message'; 6 | import Groups from './modules/groups'; 7 | import UsersProfile from './modules/usersProfile'; 8 | import { SOURCE_TYPE } from './modules/usersProfile'; 9 | export default createStore({ 10 | state: { 11 | loginState: false, 12 | networkStatus: true, 13 | isShowWarningTips: true, 14 | loginUserInfo: { 15 | hxId: '', 16 | nickname: '', 17 | avatarurl: 18 | 'https://download-sdk.oss-cn-beijing.aliyuncs.com/downloads/IMDemo/avatar/Image5.png', 19 | }, 20 | loginUserOnlineStatus: '', 21 | }, 22 | getters: { 23 | loginUserInfo: (state) => state.loginUserInfo, 24 | loginUserOnlineStatus: (state) => state.loginUserOnlineStatus, 25 | }, 26 | mutations: { 27 | CLOSE_WARNING_TIPS: (state) => (state.isShowWarningTips = false), 28 | CHANGE_LOGIN_STATUS: (state, status) => { 29 | state.loginState = status; 30 | }, 31 | CHANGE_NETWORK_STATUS: (state, status) => { 32 | state.networkStatus = status; 33 | }, 34 | 35 | SET_LOGIN_USER_INFO: (state, infos) => { 36 | state.loginUserInfo = Object.assign(state.loginUserInfo, infos); 37 | }, 38 | SET_LOGIN_USER_ONLINE_STATUS: (state, payload) => { 39 | state.loginUserOnlineStatus = payload; 40 | }, 41 | }, 42 | actions: { 43 | //获取登陆用户的用户属性 44 | getMyUserInfo: async ({ commit }, userId) => { 45 | const { data } = await EMClient.fetchUserInfoById(userId); 46 | data[userId].hxId = userId; 47 | commit('SET_LOGIN_USER_INFO', data[userId]); 48 | commit( 49 | 'UsersProfile/UPDATE_USER_PROFILE', 50 | { 51 | userId, 52 | sourceType: SOURCE_TYPE.CONTACT, 53 | profile: { 54 | ...data[userId], 55 | }, 56 | }, 57 | { root: true }, 58 | ); 59 | }, 60 | //修改登陆用户的用户属性 61 | updateMyUserInfo: async ({ commit }, params) => { 62 | const { data } = await EMClient.updateUserInfo({ ...params }); 63 | commit('SET_LOGIN_USER_INFO', data); 64 | }, 65 | //处理在线状态订阅变更(包含他人的用户状态) 66 | handlePresenceChanges: ({ commit }, status) => { 67 | const { userId, ext: statusType } = status || {}; 68 | if (userId === EMClient.user) { 69 | commit( 70 | 'SET_LOGIN_USER_ONLINE_STATUS', 71 | statusType ? statusType : 'Unset', 72 | ); 73 | } else { 74 | commit('SET_CONTACTS_PRESENCE_TO_MAP', [{ ...status }]); 75 | } 76 | }, 77 | }, 78 | modules: { 79 | Conversation, 80 | Contacts, 81 | Message, 82 | Groups, 83 | UsersProfile, 84 | }, 85 | }); 86 | -------------------------------------------------------------------------------- /src/styles/element/index.scss: -------------------------------------------------------------------------------- 1 | @forward 'element-plus/theme-chalk/src/common/var.scss' with ( 2 | $input: ( 3 | 'border-radius': 57px, 4 | ) 5 | ); 6 | @use 'element-plus/theme-chalk/src/index.scss' as *; 7 | 8 | .el-popover.el-popper { 9 | min-width: 50px; 10 | } 11 | 12 | .conversation_popover { 13 | color: #333 !important; 14 | width: 80px !important; 15 | text-align: center !important; 16 | padding: 5px 3px !important; 17 | } 18 | 19 | .user_status_popover { 20 | color: #333 !important; 21 | width: 135px !important; 22 | box-sizing: border-box !important; 23 | } 24 | 25 | .setting_popover { 26 | color: #333 !important; 27 | width: 135px !important; 28 | box-sizing: border-box !important; 29 | padding: 15px !important; 30 | } 31 | -------------------------------------------------------------------------------- /src/styles/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'iconfont'; /* Project id */ 3 | src: url('iconfont.ttf?t=1715584073131') format('truetype'); 4 | } 5 | 6 | .iconfont { 7 | font-family: 'iconfont' !important; 8 | font-size: 16px; 9 | font-style: normal; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | 14 | .icon-31dianhua:before { 15 | content: '\f0203'; 16 | } 17 | 18 | .icon-01:before { 19 | content: '\e610'; 20 | } 21 | 22 | .icon-mingpian:before { 23 | content: '\e65d'; 24 | } 25 | 26 | .icon-shipin:before { 27 | content: '\e656'; 28 | } 29 | 30 | .icon-shipintonghua-hei:before { 31 | content: '\e6eb'; 32 | } 33 | 34 | .icon-lajitong:before { 35 | content: '\e615'; 36 | } 37 | 38 | .icon-icon_emoji:before { 39 | content: '\e60a'; 40 | } 41 | 42 | .icon-wenjian:before { 43 | content: '\e69f'; 44 | } 45 | 46 | .icon-tuku:before { 47 | content: '\e712'; 48 | } 49 | 50 | .icon-kuaijiehuifu:before { 51 | content: '\e69b'; 52 | } 53 | -------------------------------------------------------------------------------- /src/styles/iconfont/iconfont.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "", 3 | "name": "", 4 | "font_family": "iconfont", 5 | "css_prefix_text": "icon-", 6 | "description": "", 7 | "glyphs": [ 8 | { 9 | "icon_id": "201577", 10 | "name": "3.1电话", 11 | "font_class": "31dianhua", 12 | "unicode": "f0203", 13 | "unicode_decimal": 983555 14 | }, 15 | { 16 | "icon_id": "1236846", 17 | "name": "语音", 18 | "font_class": "01", 19 | "unicode": "e610", 20 | "unicode_decimal": 58896 21 | }, 22 | { 23 | "icon_id": "1301366", 24 | "name": "名片", 25 | "font_class": "mingpian", 26 | "unicode": "e65d", 27 | "unicode_decimal": 58973 28 | }, 29 | { 30 | "icon_id": "4954929", 31 | "name": "视频", 32 | "font_class": "shipin", 33 | "unicode": "e656", 34 | "unicode_decimal": 58966 35 | }, 36 | { 37 | "icon_id": "5590728", 38 | "name": "视频通话-黑", 39 | "font_class": "shipintonghua-hei", 40 | "unicode": "e6eb", 41 | "unicode_decimal": 59115 42 | }, 43 | { 44 | "icon_id": "7587956", 45 | "name": "垃圾桶", 46 | "font_class": "lajitong", 47 | "unicode": "e615", 48 | "unicode_decimal": 58901 49 | }, 50 | { 51 | "icon_id": "14486087", 52 | "name": "icon_emoji", 53 | "font_class": "icon_emoji", 54 | "unicode": "e60a", 55 | "unicode_decimal": 58890 56 | }, 57 | { 58 | "icon_id": "20710439", 59 | "name": "文件", 60 | "font_class": "wenjian", 61 | "unicode": "e69f", 62 | "unicode_decimal": 59039 63 | }, 64 | { 65 | "icon_id": "27334037", 66 | "name": "图库", 67 | "font_class": "tuku", 68 | "unicode": "e712", 69 | "unicode_decimal": 59154 70 | }, 71 | { 72 | "icon_id": "35160156", 73 | "name": "快捷回复", 74 | "font_class": "kuaijiehuifu", 75 | "unicode": "e69b", 76 | "unicode_decimal": 59035 77 | } 78 | ] 79 | } 80 | -------------------------------------------------------------------------------- /src/styles/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/webim-vue-demo/7865d703d303ceb81f1585bca55a25267ca9de69/src/styles/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/styles/reset/reset.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | div, 4 | span, 5 | applet, 6 | object, 7 | iframe, 8 | h1, 9 | h2, 10 | h3, 11 | h4, 12 | h5, 13 | h6, 14 | p, 15 | blockquote, 16 | pre, 17 | a, 18 | abbr, 19 | acronym, 20 | address, 21 | big, 22 | cite, 23 | code, 24 | del, 25 | dfn, 26 | em, 27 | font, 28 | img, 29 | ins, 30 | kbd, 31 | q, 32 | s, 33 | samp, 34 | small, 35 | strike, 36 | strong, 37 | sub, 38 | sup, 39 | tt, 40 | var, 41 | b, 42 | u, 43 | i, 44 | center, 45 | dl, 46 | dt, 47 | dd, 48 | ol, 49 | ul, 50 | li, 51 | fieldset, 52 | form, 53 | label, 54 | legend, 55 | table, 56 | caption, 57 | tbody, 58 | tfoot, 59 | thead, 60 | tr, 61 | th, 62 | td { 63 | margin: 0; 64 | padding: 0; 65 | border: 0; 66 | outline: 0; 67 | font-size: 100%; 68 | vertical-align: baseline; 69 | background: transparent; 70 | } 71 | body { 72 | font-family: 'PingFang SC', 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 73 | 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; 74 | line-height: 1; 75 | } 76 | ol, 77 | ul { 78 | list-style: none; 79 | } 80 | blockquote, 81 | q { 82 | quotes: none; 83 | } 84 | blockquote:before, 85 | blockquote:after, 86 | q:before, 87 | q:after { 88 | content: ''; 89 | content: none; 90 | } 91 | /* remember to define focus styles! */ 92 | :focus { 93 | outline: 0; 94 | } 95 | /* remember to highlight inserts somehow! */ 96 | ins { 97 | text-decoration: none; 98 | } 99 | del { 100 | text-decoration: line-through; 101 | } 102 | /* tables still need 'cellspacing="0"' in the markup */ 103 | table { 104 | border-collapse: collapse; 105 | border-spacing: 0; 106 | } 107 | -------------------------------------------------------------------------------- /src/utils/dateFormater.js: -------------------------------------------------------------------------------- 1 | export function dateFormater(formater, t) { 2 | const date = t ? new Date(t * 1) : new Date(), 3 | Y = date.getFullYear() + '', 4 | M = date.getMonth() + 1, 5 | D = date.getDate(), 6 | H = date.getHours(), 7 | m = date.getMinutes(), 8 | s = date.getSeconds(); 9 | return formater 10 | .replace(/YYYY|yyyy/g, Y) 11 | .replace(/YY|yy/g, Y.substr(2, 2)) 12 | .replace(/MM/g, (M < 10 ? '0' : '') + M) 13 | .replace(/DD/g, (D < 10 ? '0' : '') + D) 14 | .replace(/HH|hh/g, (H < 10 ? '0' : '') + H) 15 | .replace(/mm/g, (m < 10 ? '0' : '') + m) 16 | .replace(/ss/g, (s < 10 ? '0' : '') + s); 17 | } 18 | 19 | export default dateFormater; 20 | -------------------------------------------------------------------------------- /src/utils/fileSizeFormat.js: -------------------------------------------------------------------------------- 1 | export default (value) => { 2 | //文件Size转换 3 | const s = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB']; 4 | const e = Math.floor(Math.log(value) / Math.log(1024)); 5 | return (value / Math.pow(1024, Math.floor(e))).toFixed(2) + ' ' + s[e]; 6 | }; 7 | -------------------------------------------------------------------------------- /src/utils/getArrdifference.js: -------------------------------------------------------------------------------- 1 | export default function (arr1, arr2) { 2 | return arr1.concat(arr2).filter((v, i, arr) => { 3 | return arr.indexOf(v) === arr.lastIndexOf(v); 4 | }); 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/handleSomeData/checkLastMsgIsHasMention.js: -------------------------------------------------------------------------------- 1 | import { EMClient } from '@/IM'; 2 | import { MESSAGE_TYPE } from '@/IM/constant'; 3 | export default function (toDoUpdateMsg, toDoUpdateConversation) { 4 | if (!toDoUpdateMsg) return; 5 | const { ext, type, from } = toDoUpdateMsg; 6 | const EM_AT_LIST = 'em_at_list'; 7 | //当前要更新会话状态如果已为提及则不做处理仍返回true 8 | if (toDoUpdateConversation && toDoUpdateConversation?.isMention) return true; 9 | //如果要更新的消息消息包含扩展提及则返回true 10 | if (type === MESSAGE_TYPE.TEXT) { 11 | if (!ext || !ext[EM_AT_LIST]) return false; 12 | if ( 13 | ext[EM_AT_LIST].includes(EMClient.user) || 14 | (from !== EMClient.user && ext[EM_AT_LIST] === 'ALL') 15 | ) { 16 | return true; 17 | } else { 18 | return false; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/handleSomeData/createInform.js: -------------------------------------------------------------------------------- 1 | /* 构建inform通知 */ 2 | import { INFORM_FROM, INFORM_TYPE } from '@/constant'; 3 | import { EMClient } from '@/IM'; 4 | export default function (fromType, informContnet) { 5 | const { type, from, to, status } = informContnet; 6 | if (fromType === INFORM_FROM.FRIEND) { 7 | let informBody = {}; 8 | //除 type 为subscribe 需要增加一些特别属性值,其他好友通知均为默认格式 9 | if (type === 'subscribe') { 10 | informBody = { 11 | fromType, 12 | type: type, 13 | title: INFORM_TYPE[type], 14 | from: from, 15 | to: to, 16 | time: Date.now(), 17 | desc: status || INFORM_TYPE[type], 18 | isOpearationBtn: true, //是否显示操作按钮? 19 | operationStatus: 0, //0未操作 1同意 2拒绝 20 | }; 21 | } else { 22 | informBody = { 23 | fromType, 24 | type, 25 | title: INFORM_TYPE[type], 26 | from: from, 27 | to: to, 28 | time: Date.now(), 29 | desc: status || INFORM_TYPE[type], 30 | }; 31 | } 32 | informBody.from === EMClient.user 33 | ? (informBody.untreated = 0) 34 | : (informBody.untreated = 1); 35 | return informBody; 36 | } 37 | if (fromType === INFORM_FROM.GROUP) { 38 | let informBody = {}; 39 | 40 | const { operation, from, to, id } = informContnet; 41 | //收到群组邀请加入通知 42 | if (operation === 'inviteToJoin') { 43 | informBody = { 44 | fromType, 45 | operation, 46 | title: '群组通知', 47 | from: from, 48 | to: to, 49 | groupId: id, 50 | time: Date.now(), 51 | desc: INFORM_TYPE[operation], 52 | isOpearationBtn: true, //是否显示操作按钮? 53 | operationStatus: 0, //0未操作 1同意 2拒绝 54 | }; 55 | } else if (operation === 'requestToJoin') { 56 | informBody = { 57 | fromType, 58 | operation, 59 | title: '群组通知', 60 | from: from, 61 | to: to, 62 | groupId: id, 63 | time: Date.now(), 64 | desc: INFORM_TYPE[operation], 65 | isOpearationBtn: true, //是否显示操作按钮? 66 | operationStatus: 0, //0未操作 1同意 2拒绝 67 | }; 68 | } else { 69 | informBody = { 70 | fromType, 71 | operation, 72 | title: '群组通知', 73 | from: from, 74 | to: to, 75 | groupId: id, 76 | time: Date.now(), 77 | desc: INFORM_TYPE[operation] || operation, 78 | }; 79 | } 80 | 81 | informBody.from === EMClient.user 82 | ? (informBody.untreated = 0) 83 | : (informBody.untreated = 1); 84 | return informBody; 85 | } 86 | //untreated 0 已阅 1未读 87 | } 88 | -------------------------------------------------------------------------------- /src/utils/handleSomeData/handlePresence.js: -------------------------------------------------------------------------------- 1 | /* 统一presence 结构体 */ 2 | import _ from 'lodash'; 3 | //处理statusDetails 4 | const handleStatusDetails = (params) => { 5 | let resultArr = []; 6 | if (_.isArray(params)) { 7 | resultArr = _.cloneDeep(params); 8 | } 9 | if (params.constructor == Object) { 10 | for (const key in params) { 11 | if (Object.hasOwnProperty.call(params, key)) { 12 | const status = params[key]; 13 | resultArr.push({ device: key, status: status * 1 }); 14 | } 15 | } 16 | } 17 | return resultArr; 18 | }; 19 | export default function (statusBody) { 20 | return { 21 | uid: statusBody.uid || statusBody.userId, 22 | expiry: statusBody.expiry || statusBody.expire, 23 | lastTime: statusBody.lastTime || statusBody.last_time, 24 | statusDetails: handleStatusDetails( 25 | statusBody.status || statusBody.statusDetails, 26 | ), 27 | ext: statusBody.ext, 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /src/utils/handleSomeData/handleSDKErrorNotifi.js: -------------------------------------------------------------------------------- 1 | /* 构建error弹出 */ 2 | import { ERROR_MAP_DESCRIPTION } from '@/constant'; 3 | import { ElMessage } from 'element-plus'; 4 | 5 | export default function (code, errorDesc = '') { 6 | //针对触发Moderation的消息做特别处理 7 | if (code === 508) { 8 | errorDesc = 'moderation'; 9 | } 10 | if (code === 507) { 11 | errorDesc = 'muted'; 12 | } 13 | const message = 14 | (ERROR_MAP_DESCRIPTION[code] && ERROR_MAP_DESCRIPTION[code][errorDesc]) || 15 | errorDesc; 16 | 17 | ElMessage({ 18 | title: 'Easemob SDK Error', 19 | message: message, 20 | type: 'error', 21 | center: true, 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /src/utils/handleSomeData/index.js: -------------------------------------------------------------------------------- 1 | import handleSDKErrorNotifi from './handleSDKErrorNotifi'; 2 | import setMessageKey from './setMessageKey'; 3 | import createInform from './createInform'; 4 | import sortPinyinFriendItem from './sortPinyinFriendItem'; 5 | import handlePresence from './handlePresence'; 6 | import checkLastMsgIsHasMention from './checkLastMsgIsHasMention'; 7 | export { 8 | handleSDKErrorNotifi, 9 | setMessageKey, 10 | createInform, 11 | checkLastMsgIsHasMention, 12 | sortPinyinFriendItem, 13 | handlePresence, 14 | }; 15 | -------------------------------------------------------------------------------- /src/utils/handleSomeData/setMessageKey.js: -------------------------------------------------------------------------------- 1 | /* 用作根据消息类型处理对象中的key */ 2 | import { EMClient } from '@/IM'; 3 | import { CHAT_TYPE } from '@/IM/constant'; 4 | export default function (msgBody) { 5 | const loginUserId = EMClient.user; 6 | const listKey = 7 | msgBody.chatType === CHAT_TYPE.SINGLE 8 | ? msgBody.to === loginUserId 9 | ? msgBody.from 10 | : msgBody.to 11 | : msgBody.to; 12 | 13 | return listKey; 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/handleSomeData/sortPinyinFriendItem.js: -------------------------------------------------------------------------------- 1 | /* 好友列表按照拼音排序 */ 2 | import _ from 'lodash'; 3 | import { pinyin } from 'pinyin-pro'; 4 | export default function (friendItemData) { 5 | const resultObj = {}; 6 | const containerObj = {}; 7 | for (const key in friendItemData) { 8 | if (Object.hasOwnProperty.call(friendItemData, key)) { 9 | const v = friendItemData[key]; 10 | const pinyinKey = v.nickname 11 | ? pinyin(v.nickname, { pattern: 'initial' })[0] 12 | : pinyin(v.hxId, { pattern: 'initial' })[0]; 13 | if (containerObj[pinyinKey]) { 14 | containerObj[pinyinKey].push(v); 15 | } else { 16 | containerObj[pinyinKey] = []; 17 | containerObj[pinyinKey].push(v); 18 | } 19 | } 20 | } 21 | const resultObjKeys = _.sortBy(_.keys(containerObj)); 22 | 23 | resultObjKeys.forEach((a) => { 24 | resultObj[a] = containerObj[a]; 25 | }); 26 | return resultObj; 27 | } 28 | -------------------------------------------------------------------------------- /src/utils/parseDownloadResponse.js: -------------------------------------------------------------------------------- 1 | export default function parseDownloadResponse(response) { 2 | return (response && response.type && response.type === 'application/json') || 3 | 0 > Object.prototype.toString.call(response).indexOf('Blob') 4 | ? this.url + '?token=' 5 | : window.URL.createObjectURL(response); 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/paseLink.js: -------------------------------------------------------------------------------- 1 | const paseLink = (msg) => { 2 | let isLink = false; 3 | var reg = 4 | /(https?\:\/\/|www\.)([a-zA-Z0-9-]+(\.[a-zA-Z0-9]+)+)(\:[0-9]{2,4})?\/?((\.[:_0-9a-zA-Z-]+)|[:_0-9a-zA-Z-]*\/?)*\??[:_#@*&%0-9a-zA-Z-/=]*/gm; 5 | 6 | msg = msg.replace(reg, function (v) { 7 | const prefix = /^https?/gm.test(v); 8 | isLink = prefix; 9 | return ( 10 | "" + v + '' 11 | ); 12 | }); 13 | 14 | return { isLink, msg }; 15 | }; 16 | 17 | export default paseLink; 18 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | const defaultBaseUrl = '//a1.easemob.com'; 3 | // create an axios instance 4 | const service = axios.create({ 5 | withCredentials: false, 6 | // baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url 7 | baseURL: `${window.location.protocol}${defaultBaseUrl}`, 8 | // withCredentials: true, // send cookies when cross-domain requests 9 | timeout: 30000, // request timeout 10 | headers: { 'Content-Type': 'application/json' }, 11 | }); 12 | // request interceptor 13 | service.interceptors.request.use( 14 | (config) => { 15 | // do something before request is sent 16 | return config; 17 | }, 18 | (error) => { 19 | // do something with request error 20 | 21 | return Promise.reject(error); 22 | }, 23 | ); 24 | 25 | // response interceptor 26 | service.interceptors.response.use( 27 | /** 28 | * If you want to get http information such as headers or status 29 | * Please return response => response 30 | */ 31 | 32 | /** 33 | * Determine the request status by custom code 34 | * Here is just an example 35 | * You can also judge the status by HTTP Status Code 36 | */ 37 | (response) => { 38 | const res = response.data; 39 | const code = response.status; 40 | // if the custom code is not 20000, it is judged as an error. 41 | if (code >= 400) { 42 | return Promise.reject(new Error(res.desc || 'Error')); 43 | } else { 44 | return res; 45 | } 46 | }, 47 | (error) => { 48 | if (error.response) { 49 | const res = error.response.data; // for debug 50 | if (error.response.status === 401 && res.code !== '001') { 51 | } 52 | if (error.response.status === 403) { 53 | res.desc = '您没有权限进行查询和操作!'; 54 | } 55 | return Promise.reject(res.desc || error); 56 | } 57 | return Promise.reject(error); 58 | }, 59 | ); 60 | 61 | export default service; 62 | -------------------------------------------------------------------------------- /src/utils/waterMark.js: -------------------------------------------------------------------------------- 1 | const waterMark = ({ 2 | container, 3 | waterToggle = 'true', 4 | width = '200px', 5 | height = '80px', 6 | textAlign = 'center', 7 | textBaseline = 'middle', 8 | font = '12px Microsoft Yahei', 9 | fillStyle = 'rgba(184, 184, 184, 0.3)', 10 | rotate = '-10', 11 | zIndex = 0, 12 | } = {}) => { 13 | if (waterToggle == 'true') { 14 | const content = `${ 15 | '【环信IM】' + 16 | JSON.parse(window.localStorage.getItem('EASEIM_loginUser')).user || 17 | '环信即时通讯云' 18 | }`; 19 | const canvas = document.createElement('canvas'); 20 | canvas.setAttribute('width', width); 21 | canvas.setAttribute('height', height); 22 | const ctx = canvas.getContext('2d'); 23 | ctx.textAlign = textAlign; 24 | ctx.textBaseline = textBaseline; 25 | ctx.font = font; 26 | ctx.fontColor = 'red'; 27 | ctx.fillStyle = fillStyle; 28 | ctx.rotate((Math.PI / 180) * rotate); 29 | ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 2); 30 | const base64Url = canvas.toDataURL(); 31 | const watermarkDiv = document.createElement('div'); 32 | const styleStr = ` 33 | position:absolute; 34 | top:0; 35 | left:0; 36 | width:100%; 37 | height:100%; 38 | z-index:${zIndex}; 39 | pointer-events:none; 40 | background-repeat:repeat; 41 | background-image:url('${base64Url}') 42 | `; 43 | const __wm = container.querySelector('.__wm'); 44 | watermarkDiv.setAttribute('style', styleStr); 45 | // 防止多次添加 46 | if ((__wm && __wm.getAttribute('style') !== styleStr) || !__wm) { 47 | container.style.position = 'relative'; 48 | container.insertBefore(watermarkDiv, container.firstChild); 49 | watermarkDiv.classList.add('__wm'); 50 | } 51 | 52 | const MutationObserver = 53 | window.MutationObserver || window.WebKitMutationObserver; 54 | // 检查浏览器是否支持这个API 55 | if (MutationObserver) { 56 | let mo = new MutationObserver(function () { 57 | // 只在__wm元素变动才重新调用 __canvasWM 58 | if ((__wm && __wm.getAttribute('style') !== styleStr) || !__wm) { 59 | // 避免一直触发 60 | mo.disconnect(); 61 | mo = null; 62 | waterMark({ container }); 63 | } 64 | }); 65 | 66 | mo.observe(container, { 67 | attributes: true, // 观察目标节点的属性节点 68 | subtree: true, // 观察目标节点的所有后代节点 69 | childList: true, // 观察目标节点的子节点 70 | }); 71 | } 72 | } 73 | }; 74 | 75 | export default waterMark; 76 | -------------------------------------------------------------------------------- /src/views/Chat/components/AboutGroups/GroupsDetails/index.scss: -------------------------------------------------------------------------------- 1 | .app_container { 2 | height: 100%; 3 | overflow: auto; 4 | 5 | .group_func_card { 6 | padding: 15px; 7 | min-height: 72px; 8 | width: 100%; 9 | box-sizing: border-box; 10 | display: flex; 11 | flex-direction: column; 12 | justify-content: flex-start; 13 | align-items: space-around; 14 | 15 | .title { 16 | font-family: 'PingFang SC'; 17 | font-style: normal; 18 | font-weight: 500; 19 | font-size: 12px; 20 | line-height: 18px; 21 | display: flex; 22 | justify-content: flex-start; 23 | align-items: center; 24 | 25 | .icon { 26 | cursor: pointer; 27 | margin-left: 5px; 28 | transition: all 0.3s; 29 | 30 | &:hover { 31 | transform: scale(1.5); 32 | } 33 | } 34 | } 35 | 36 | .content { 37 | font-family: 'PingFang SC'; 38 | font-style: normal; 39 | font-weight: 400; 40 | font-size: 12px; 41 | line-height: 18px; 42 | /* or 150% */ 43 | text-align: justify; 44 | color: #a3a3a3; 45 | // height: 72px; 46 | overflow: hidden; 47 | cursor: pointer; 48 | } 49 | } 50 | 51 | .group_announcements { 52 | padding: 15px; 53 | height: 127px; 54 | width: 100%; 55 | box-sizing: border-box; 56 | display: flex; 57 | flex-direction: column; 58 | justify-content: flex-start; 59 | align-items: space-around; 60 | 61 | .title { 62 | font-family: 'PingFang SC'; 63 | font-style: normal; 64 | font-weight: 500; 65 | font-size: 12px; 66 | line-height: 18px; 67 | } 68 | 69 | .content { 70 | font-family: 'PingFang SC'; 71 | font-style: normal; 72 | font-weight: 400; 73 | font-size: 12px; 74 | line-height: 18px; 75 | /* or 150% */ 76 | text-align: justify; 77 | color: #a3a3a3; 78 | height: 72px; 79 | overflow: hidden; 80 | cursor: pointer; 81 | } 82 | } 83 | 84 | .group_list_card { 85 | padding: 15px 0px 15px 15px; 86 | height: 52px; 87 | box-sizing: border-box; 88 | display: flex; 89 | flex-direction: row; 90 | justify-content: space-between; 91 | align-items: center; 92 | 93 | &:hover { 94 | background: #f3f3f3; 95 | } 96 | 97 | .label { 98 | font-family: 'PingFang SC'; 99 | font-style: normal; 100 | font-weight: 500; 101 | font-size: 12px; 102 | } 103 | 104 | .main { 105 | height: 100%; 106 | width: 80px; 107 | display: flex; 108 | flex-direction: row; 109 | justify-content: flex-end; 110 | align-items: center; 111 | margin: 0 5px; 112 | 113 | .member_count { 114 | min-width: 17px; 115 | height: 20px; 116 | background: #f9f9f9; 117 | border-radius: 108px; 118 | font-family: 'PingFang SC'; 119 | font-style: normal; 120 | font-weight: 500; 121 | font-size: 10px; 122 | line-height: 11px; 123 | color: #a3a3a3; 124 | padding: 5px 8px; 125 | box-sizing: border-box; 126 | } 127 | 128 | .more_list { 129 | > svg { 130 | width: 18.49px; 131 | height: 10.84px; 132 | cursor: pointer; 133 | } 134 | } 135 | } 136 | } 137 | } 138 | .group_list_handle_box { 139 | margin-top: 10px; 140 | padding: 0 2px; 141 | } 142 | .group_list_card_btn { 143 | width: 100%; 144 | height: 40px; 145 | } 146 | :deep(.group_name_input) > .el-input__wrapper { 147 | border-radius: 5px; 148 | } 149 | 150 | -------------------------------------------------------------------------------- /src/views/Chat/components/AboutGroups/GroupsManagement/GroupAnnoun.vue: -------------------------------------------------------------------------------- 1 | 64 | 89 | 90 | 95 | -------------------------------------------------------------------------------- /src/views/Chat/components/AboutGroups/GroupsManagement/GroupDesc.vue: -------------------------------------------------------------------------------- 1 | 70 | 94 | 113 | -------------------------------------------------------------------------------- /src/views/Chat/components/AboutGroups/GroupsManagement/index.vue: -------------------------------------------------------------------------------- 1 | 73 | 100 | 101 | 106 | ./GroupDesc.vue 107 | -------------------------------------------------------------------------------- /src/views/Chat/components/Contacts/components/ContactsItem.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 58 | 59 | 109 | -------------------------------------------------------------------------------- /src/views/Chat/components/Contacts/components/ContactsRemark.vue: -------------------------------------------------------------------------------- 1 | 60 | 88 | 89 | 123 | -------------------------------------------------------------------------------- /src/views/Chat/components/Contacts/components/JoinedGroupsItem.vue: -------------------------------------------------------------------------------- 1 | 12 | 42 | 43 | 80 | -------------------------------------------------------------------------------- /src/views/Chat/components/Conversation/index.vue: -------------------------------------------------------------------------------- 1 | 42 | 64 | 65 | 84 | -------------------------------------------------------------------------------- /src/views/Chat/components/Message/components/ChatContainerHeader/index.scss: -------------------------------------------------------------------------------- 1 | .chat_message_header { 2 | position: relative; 3 | display: flex; 4 | align-items: center; 5 | flex-direction: row; 6 | justify-content: space-between; 7 | height: 61px; 8 | background: #f9f9f9; 9 | border-radius: 0 3px 0 0; 10 | border-bottom: 1px solid #e6e6e6; 11 | 12 | .chat_user_box { 13 | display: flex; 14 | flex-direction: row; 15 | justify-content: flex-start; 16 | align-items: center; 17 | height: 20px; 18 | max-width: 80%; 19 | 20 | .chat_user_name { 21 | font-family: 'PingFang SC'; 22 | overflow: hidden; 23 | text-overflow: ellipsis; 24 | white-space: nowrap; 25 | font-style: normal; 26 | font-weight: 400; 27 | font-size: 17px; 28 | line-height: 20px; 29 | letter-spacing: 0.3px; 30 | color: #333333; 31 | } 32 | } 33 | 34 | .more { 35 | display: flex; 36 | width: 35px; 37 | height: 100%; 38 | align-items: center; 39 | justify-content: center; 40 | font-size: 20px; 41 | cursor: pointer; 42 | transition: all 0.3s; 43 | 44 | &:hover { 45 | transform: scale(1.1); 46 | } 47 | } 48 | } 49 | 50 | .easeim_safe_tips { 51 | position: relative; 52 | padding: 12px 20px; 53 | background-color: #fff4e6; 54 | color: #ff8c39; 55 | line-height: 18px; 56 | font-family: PingFang SC; 57 | font-style: normal; 58 | font-weight: 400; 59 | text-align: justify; 60 | font-size: 12px; 61 | border: none; 62 | 63 | .easeim_close_tips { 64 | position: absolute; 65 | right: 10px; 66 | top: 10px; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/views/Chat/components/Message/components/ChatContainerHeader/index.vue: -------------------------------------------------------------------------------- 1 | 55 | 74 | 75 | 78 | -------------------------------------------------------------------------------- /src/views/Chat/components/Message/components/ChatInputBox/components/AudioMessage/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/Chat/components/Message/components/ChatInputBox/components/FileMessage/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/views/Chat/components/Message/components/ChatInputBox/components/ImageMessage/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/views/Chat/components/Message/components/ChatInputBox/components/VideoMessage/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/views/Chat/components/Message/components/ChatInputBox/index.scss: -------------------------------------------------------------------------------- 1 | .chat_func_box { 2 | position: relative; 3 | display: flex; 4 | align-items: center; 5 | height: 42px; 6 | width: 100%; 7 | background-color: #f7f7f7; 8 | border-top: 1px solid #e6e6e6; 9 | border-bottom: 1px solid #e6e6e6; 10 | line-height: 12px; 11 | 12 | .chat_func_icon { 13 | width: 25px; 14 | height: 25px; 15 | } 16 | 17 | .emojis_box { 18 | position: absolute; 19 | left: 15px; 20 | top: -180px; 21 | width: 330px; 22 | height: 150px; 23 | border-radius: 5px; 24 | display: flex; 25 | flex-direction: row; 26 | flex-wrap: wrap; 27 | justify-content: space-between; 28 | align-items: center; 29 | background: #fff; 30 | padding: 15px 5px; 31 | 32 | .emoji { 33 | display: inline-block; 34 | width: 25px; 35 | height: 25px; 36 | text-align: center; 37 | line-height: 25px; 38 | cursor: pointer; 39 | transition: all 0.3s ease; 40 | 41 | &:hover { 42 | transform: scale(1.2); 43 | } 44 | } 45 | } 46 | 47 | .loading_box { 48 | position: absolute; 49 | right: 5px; 50 | top: 0; 51 | width: 50px; 52 | height: 100%; 53 | font-size: 15px; 54 | } 55 | } 56 | 57 | /* loading svg大小调整 */ 58 | :deep(.circular) { 59 | margin-top: 8px; 60 | width: 25px; 61 | height: 25px; 62 | } 63 | 64 | .iconfont { 65 | margin-right: 12px; 66 | transition: all 0.3s ease; 67 | cursor: pointer; 68 | 69 | &:hover { 70 | transform: scale(1.2); 71 | color: #1b83f9; 72 | } 73 | } 74 | 75 | .record_box { 76 | width: 250px; 77 | height: 180px; 78 | } 79 | -------------------------------------------------------------------------------- /src/views/Chat/components/Message/components/suit/emojiContainer.vue: -------------------------------------------------------------------------------- 1 | 19 | 35 | 36 | 67 | -------------------------------------------------------------------------------- /src/views/Chat/components/Message/components/suit/modifyMessage.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 91 | 92 | 118 | -------------------------------------------------------------------------------- /src/views/Chat/components/Message/components/suit/msgQuote.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 111 | 112 | 163 | -------------------------------------------------------------------------------- /src/views/Chat/components/Message/components/suit/previewSendImg.vue: -------------------------------------------------------------------------------- 1 | 99 | 118 | 119 | 143 | -------------------------------------------------------------------------------- /src/views/Chat/components/Message/components/suit/reportMessage.vue: -------------------------------------------------------------------------------- 1 | 97 | 98 | 144 | 145 | 159 | -------------------------------------------------------------------------------- /src/views/Chat/components/Message/index.scss: -------------------------------------------------------------------------------- 1 | .app_container { 2 | height: 100%; 3 | border-left: 1px solid #e6e6e6; 4 | } 5 | 6 | .chat_message_header { 7 | position: relative; 8 | display: flex; 9 | align-items: center; 10 | flex-direction: row; 11 | justify-content: space-between; 12 | height: 61px; 13 | background: #f9f9f9; 14 | border-radius: 0 3px 0 0; 15 | border-bottom: 1px solid #e6e6e6; 16 | 17 | .chat_user_box { 18 | display: flex; 19 | flex-direction: row; 20 | justify-content: flex-start; 21 | align-items: center; 22 | height: 20px; 23 | max-width: 80%; 24 | 25 | .chat_user_name { 26 | font-family: 'PingFang SC'; 27 | overflow: hidden; 28 | text-overflow: ellipsis; 29 | white-space: nowrap; 30 | font-style: normal; 31 | font-weight: 400; 32 | font-size: 17px; 33 | line-height: 20px; 34 | letter-spacing: 0.3px; 35 | color: #333333; 36 | } 37 | } 38 | 39 | .more { 40 | display: flex; 41 | width: 35px; 42 | height: 100%; 43 | align-items: center; 44 | justify-content: center; 45 | font-size: 20px; 46 | cursor: pointer; 47 | transition: all 0.3s; 48 | 49 | &:hover { 50 | transform: scale(1.1); 51 | } 52 | } 53 | } 54 | 55 | .easeim_safe_tips { 56 | position: relative; 57 | padding: 12px 20px; 58 | background-color: #fff4e6; 59 | color: #ff8c39; 60 | line-height: 18px; 61 | font-family: PingFang SC; 62 | font-style: normal; 63 | font-weight: 400; 64 | text-align: justify; 65 | font-size: 12px; 66 | border: none; 67 | 68 | .easeim_close_tips { 69 | position: absolute; 70 | right: 10px; 71 | top: 10px; 72 | } 73 | } 74 | 75 | .chat_message_main { 76 | padding: 0; 77 | background: #f9f9f9; 78 | 79 | .main_container { 80 | padding: 0 20px; 81 | height: 100%; 82 | // overflow-y: scroll; 83 | 84 | .chat_message_tips { 85 | margin-top: 5px; 86 | width: 100%; 87 | height: 30px; 88 | text-align: center; 89 | line-height: 30px; 90 | 91 | .load_more_msg { 92 | width: 200px; 93 | height: 30px; 94 | border-radius: 20px; 95 | margin: 0 auto; 96 | background: rgba(114, 112, 112, 0.143); 97 | font-size: 13px; 98 | letter-spacing: 0.5px; 99 | // box-shadow: 1px 1px 1px 1px rgba(128, 128, 128, 0.193); 100 | } 101 | } 102 | } 103 | } 104 | 105 | .chat_message_inputbar { 106 | position: relative; 107 | width: 100%; 108 | height: 25%; 109 | padding: 0; 110 | background-color: #f9f9f9; 111 | border-radius: 0 0 3px 0; 112 | } 113 | 114 | :deep(.el-drawer) { 115 | margin-top: 60px; 116 | width: 150px; 117 | height: calc(100% - 60px); 118 | border-radius: 5px 0 0 5px; 119 | 120 | .el-drawer__header { 121 | margin-bottom: 0; 122 | padding-top: 0; 123 | } 124 | 125 | .el-drawer__body { 126 | padding: 0; 127 | // padding-left: 16px; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/views/Chat/components/NavBar/components/AboutUserInfoCard/MiniInfoCard.vue: -------------------------------------------------------------------------------- 1 | 19 | 42 | 43 | 164 | -------------------------------------------------------------------------------- /src/views/Chat/components/NavBar/components/ApplyComponents/addFriends.vue: -------------------------------------------------------------------------------- 1 | 77 | 109 | 110 | 133 | -------------------------------------------------------------------------------- /src/views/Chat/components/NavBar/components/ApplyComponents/applyJoinGroups.vue: -------------------------------------------------------------------------------- 1 | 98 | 131 | 154 | -------------------------------------------------------------------------------- /src/views/Chat/components/NavBar/components/ApplyComponents/index.vue: -------------------------------------------------------------------------------- 1 | 35 | 52 | 53 | -------------------------------------------------------------------------------- /src/views/Chat/components/NavBar/components/Logout.vue: -------------------------------------------------------------------------------- 1 | 31 | 56 | 57 | 76 | -------------------------------------------------------------------------------- /src/views/Chat/components/NavBar/components/PersonalsettingCard/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 95 | 96 | 109 | -------------------------------------------------------------------------------- /src/views/Chat/components/NavBar/components/UserOnlineStatusCard.vue: -------------------------------------------------------------------------------- 1 | 26 | 56 | 57 | 108 | -------------------------------------------------------------------------------- /src/views/Chat/index.vue: -------------------------------------------------------------------------------- 1 | 70 | 83 | 84 | 123 | -------------------------------------------------------------------------------- /src/views/Login/components/CustomImConfig/index.vue: -------------------------------------------------------------------------------- 1 | 63 | 114 | 115 | -------------------------------------------------------------------------------- /src/views/Login/components/LoginInput/emloginWithPasswordLogin.vue: -------------------------------------------------------------------------------- 1 | 66 | 100 | 101 | 212 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('@vue/cli-service'); 2 | module.exports = defineConfig({ 3 | productionSourceMap: false, 4 | transpileDependencies: true, 5 | lintOnSave: false, 6 | devServer: { 7 | host: 'localhost', 8 | port: 9001, 9 | // https:true 10 | }, 11 | chainWebpack: (config) => { 12 | //最小化代码 13 | config.optimization.minimize(true); 14 | //分割代码 15 | config.optimization.splitChunks({ 16 | chunks: 'all', 17 | }); 18 | }, 19 | }); 20 | --------------------------------------------------------------------------------