├── .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 |
47 |
48 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
65 |
69 |
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 |
--------------------------------------------------------------------------------
/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 |
31 |
32 |
38 |
47 |
48 |
49 |
50 |
103 |
--------------------------------------------------------------------------------
/src/components/EaseCallKit/components/miniStreamContainer.vue:
--------------------------------------------------------------------------------
1 |
45 |
46 |
47 |
53 |
57 |
58 | 等待接听中...
64 | {{
65 | callTime
66 | }}
67 |
68 |
69 |
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 |
53 |
54 |
55 | {{ item }}
62 |
63 |
64 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/components/UserStatus/index.vue:
--------------------------------------------------------------------------------
1 |
76 |
77 |
78 |
79 | {{
80 | userInfoStatus.onlineDeviceCount > 1
81 | ? `多设备${userInfoStatus.label}`
82 | : `${userInfoStatus.deviceType.toUpperCase()}${userInfoStatus.label}`
83 | }}
84 |
85 |
86 |
87 |
106 |
--------------------------------------------------------------------------------
/src/components/Welcome/index.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
![]()
9 |
欢迎体验 环信 Easemob 即时通讯
10 |
11 | 此Demo包含单聊群聊,添加好友,创建群组等功能,更多其他功能等你发现,快去试试吧!
12 |
13 |
14 |
![]()
15 |
16 |
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 |
65 |
66 |
67 |
68 |
69 | {{ getGroupAnnouncement || '暂无群公告~' }}
70 |
71 |
84 |
85 |
86 |
{{ getGroupAnnouncement || '暂无群公告~' }}
87 |
88 |
89 |
90 |
95 |
--------------------------------------------------------------------------------
/src/views/Chat/components/AboutGroups/GroupsManagement/GroupDesc.vue:
--------------------------------------------------------------------------------
1 |
70 |
71 |
72 |
77 | {{ getGroupDetailFromGroupList.description || '暂无群描述' }}
78 |
79 |
92 |
93 |
94 |
113 |
--------------------------------------------------------------------------------
/src/views/Chat/components/AboutGroups/GroupsManagement/index.vue:
--------------------------------------------------------------------------------
1 |
73 |
74 |
83 |
91 |
92 |
93 |
97 |
98 |
99 |
100 |
101 |
106 | ./GroupDesc.vue
107 |
--------------------------------------------------------------------------------
/src/views/Chat/components/Contacts/components/ContactsItem.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
21 |
22 |
23 |
24 | {{ friendItemKey === ' ' ? '#' : friendItemKey.toUpperCase() }}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
43 |
48 |
49 |
50 | {{ getContactsNickNameById(userId) }}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
109 |
--------------------------------------------------------------------------------
/src/views/Chat/components/Contacts/components/ContactsRemark.vue:
--------------------------------------------------------------------------------
1 |
60 |
61 |
87 |
88 |
89 |
123 |
--------------------------------------------------------------------------------
/src/views/Chat/components/Contacts/components/JoinedGroupsItem.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
25 |
30 |
31 |
32 | {{
33 | `${getGroupNameByGroupId(groupItem.groupId)}(${
34 | groupItem.affiliationsCount
35 | })`
36 | }}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
80 |
--------------------------------------------------------------------------------
/src/views/Chat/components/Conversation/index.vue:
--------------------------------------------------------------------------------
1 |
42 |
43 |
44 |
45 |
46 |
51 |
52 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
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 |
56 |
73 |
74 |
75 |
78 |
--------------------------------------------------------------------------------
/src/views/Chat/components/Message/components/ChatInputBox/components/AudioMessage/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/Chat/components/Message/components/ChatInputBox/components/FileMessage/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/src/views/Chat/components/Message/components/ChatInputBox/components/ImageMessage/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/src/views/Chat/components/Message/components/ChatInputBox/components/VideoMessage/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
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 |
20 |
26 | {{ emoji }}
33 |
34 |
35 |
36 |
67 |
--------------------------------------------------------------------------------
/src/views/Chat/components/Message/components/suit/modifyMessage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
21 |
22 |
23 |
24 |
25 |
91 |
92 |
118 |
--------------------------------------------------------------------------------
/src/views/Chat/components/Message/components/suit/msgQuote.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msgQuote.msgSender || '' }}:
4 |
5 |
6 |
12 | {{ msgQuote.msgPreview }}
13 |
14 |
15 |
16 | {{ msgQuote.msgPreview || '' }}
17 |
18 |
19 |
20 |
21 |
37 |
38 |
39 |
40 |
111 |
112 |
163 |
--------------------------------------------------------------------------------
/src/views/Chat/components/Message/components/suit/previewSendImg.vue:
--------------------------------------------------------------------------------
1 |
99 |
100 |
101 |
102 |
103 | Loading...
104 |
105 |
106 |
107 | {{ imgName }}
108 | {{ fileSizeFormat(imgSize) }}
109 |
110 |
111 |
115 |
116 |
117 |
118 |
119 |
143 |
--------------------------------------------------------------------------------
/src/views/Chat/components/Message/components/suit/reportMessage.vue:
--------------------------------------------------------------------------------
1 |
97 |
98 |
99 |
107 |
114 |
115 |
116 |
122 |
123 |
124 |
125 |
132 |
133 |
134 |
135 |
141 |
142 |
143 |
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 |
20 |
21 |
22 |
23 |
24 |
{{
25 | userInfos.nickname ? userInfos.nickname : loginUserId
26 | }}
27 |
![]()
32 |
33 |
34 |
35 |
36 |
37 | 地区:北京·朝阳区
38 |
39 |
40 |
41 |
42 |
43 |
164 |
--------------------------------------------------------------------------------
/src/views/Chat/components/NavBar/components/ApplyComponents/addFriends.vue:
--------------------------------------------------------------------------------
1 |
77 |
78 |
79 |
80 |
81 |
86 |
87 |
88 |
94 |
95 |
96 |
97 | 添加好友
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
133 |
--------------------------------------------------------------------------------
/src/views/Chat/components/NavBar/components/ApplyComponents/applyJoinGroups.vue:
--------------------------------------------------------------------------------
1 |
98 |
99 |
100 |
101 |
102 |
107 |
108 |
109 |
116 |
117 |
118 |
119 | 申请加入
125 |
126 |
127 |
128 |
129 |
130 |
131 |
154 |
--------------------------------------------------------------------------------
/src/views/Chat/components/NavBar/components/ApplyComponents/index.vue:
--------------------------------------------------------------------------------
1 |
35 |
36 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/views/Chat/components/NavBar/components/Logout.vue:
--------------------------------------------------------------------------------
1 |
31 |
32 |
39 | 确认退出当前登录账号?
40 |
41 |
42 |
43 |
44 |
45 |
53 |
54 |
55 |
56 |
57 |
76 |
--------------------------------------------------------------------------------
/src/views/Chat/components/NavBar/components/PersonalsettingCard/index.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
31 |
32 |
33 |
39 | 新消息提示音
40 |
41 |
42 |
47 |
48 |
49 |
55 | 开启SDK日志
57 |
62 |
63 |
64 | 下载SDK缓存日志
71 |
72 |
73 |
74 |
80 | 会话列表获取方式
81 |
82 |
87 |
88 |
92 |
93 |
94 |
95 |
96 |
109 |
--------------------------------------------------------------------------------
/src/views/Chat/components/NavBar/components/UserOnlineStatusCard.vue:
--------------------------------------------------------------------------------
1 |
26 |
27 |
28 |
29 | -
36 |
37 | {{ item.label }}
38 |
39 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
108 |
--------------------------------------------------------------------------------
/src/views/Chat/index.vue:
--------------------------------------------------------------------------------
1 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
123 |
--------------------------------------------------------------------------------
/src/views/Login/components/CustomImConfig/index.vue:
--------------------------------------------------------------------------------
1 |
63 |
64 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
85 |
89 |
90 |
91 |
92 |
93 |
98 |
102 |
103 |
104 |
105 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/src/views/Login/components/LoginInput/emloginWithPasswordLogin.vue:
--------------------------------------------------------------------------------
1 |
66 |
67 |
68 |
69 |
75 |
76 |
77 |
85 |
86 |
87 |
88 | 登录
95 | 登录
96 |
97 |
98 |
99 |
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 |
--------------------------------------------------------------------------------