Showing preview only (461K chars total). Download the full file or copy to clipboard to get everything.
Repository: fsharechat/vue-chat
Branch: master
Commit: 948ffd3a4082
Files: 146
Total size: 414.9 KB
Directory structure:
gitextract_p2lxtd1d/
├── .babelrc
├── .editorconfig
├── .gitignore
├── .postcssrc.js
├── LICENSE
├── README.md
├── build/
│ ├── build.js
│ ├── check-versions.js
│ ├── dev-client.js
│ ├── dev-server.js
│ ├── utils.js
│ ├── vue-loader.conf.js
│ ├── webpack.base.conf.js
│ ├── webpack.dev.conf.js
│ └── webpack.prod.conf.js
├── config/
│ ├── dev.env.js
│ ├── index.js
│ └── prod.env.js
├── index.html
├── index.md
├── package.json
├── src/
│ ├── App.vue
│ ├── assets/
│ │ └── fonts/
│ │ ├── iconfont.css
│ │ ├── iconfont.js
│ │ └── iconfont.json
│ ├── components/
│ │ ├── chatlist/
│ │ │ └── chatlist.vue
│ │ ├── friendlist/
│ │ │ └── friendlist.vue
│ │ ├── info/
│ │ │ └── info.vue
│ │ ├── menu/
│ │ │ ├── addtip.vue
│ │ │ ├── groupInfo.vue
│ │ │ ├── personalCard.vue
│ │ │ ├── relayMessage.vue
│ │ │ └── rightMenu.vue
│ │ ├── message/
│ │ │ └── message.vue
│ │ ├── mycard/
│ │ │ └── mycard.vue
│ │ ├── search/
│ │ │ └── search.vue
│ │ └── text/
│ │ └── text.vue
│ ├── constant/
│ │ └── index.js
│ ├── main.js
│ ├── page/
│ │ ├── chat/
│ │ │ └── chat.vue
│ │ ├── friend/
│ │ │ ├── friend.vue
│ │ │ └── searchfriend.vue
│ │ ├── group/
│ │ │ ├── creategroup.vue
│ │ │ └── groupVideoCall.vue
│ │ ├── login/
│ │ │ └── login.vue
│ │ └── main.vue
│ ├── permission.js
│ ├── router/
│ │ └── index.js
│ ├── store.js
│ ├── webrtc/
│ │ ├── callEndReason.js
│ │ ├── callSession.js
│ │ ├── callState.js
│ │ ├── engineCallback.js
│ │ ├── groupCallClient.js
│ │ ├── message/
│ │ │ ├── callAnswerMessageContent.js
│ │ │ ├── callAnswerTMessageContent.js
│ │ │ ├── callByeMessageContent.js
│ │ │ ├── callModifyMessageContent.js
│ │ │ ├── callSignalMessageContent.js
│ │ │ └── callStartMessageContent.js
│ │ ├── participant.js
│ │ ├── sessionCallback.js
│ │ └── voipclient.js
│ └── websocket/
│ ├── chatManager.js
│ ├── future/
│ │ ├── futureResult.js
│ │ └── promiseResolve.js
│ ├── handler/
│ │ ├── abstractmessagehandler.js
│ │ ├── addGroupMemberHandler.js
│ │ ├── connectackhandler.js
│ │ ├── createGroupHandler.js
│ │ ├── dismissGroupHandler.js
│ │ ├── friendAddRequestHandler.js
│ │ ├── friendRequestHandler.js
│ │ ├── getGroupInfoHandler.js
│ │ ├── getGroupMemberHandler.js
│ │ ├── getMinioUploadUrlHandler.js
│ │ ├── getUploadtokenHandler.js
│ │ ├── getfriendresultHandler.js
│ │ ├── getuserinfoHandler.js
│ │ ├── handleFriendRequestHandler.js
│ │ ├── kickGroupmemberHandler.js
│ │ ├── loadRemoteMessageHander.js
│ │ ├── messageHandler.js
│ │ ├── modifyMyInfoHandler.js
│ │ ├── notifyFriendHandler.js
│ │ ├── notifyFriendRequestHandler.js
│ │ ├── notifyMessageHandler.js
│ │ ├── notifyRecallMessageHandler.js
│ │ ├── quitGroupHandler.js
│ │ ├── recallMessageHandler.js
│ │ ├── receiveMessageHandler.js
│ │ ├── searchUserResultHandler.js
│ │ ├── sendMessageHandler.js
│ │ └── setFriendAliasRequestHandler.js
│ ├── index.js
│ ├── listener/
│ │ └── onReceiverMessageListener.js
│ ├── message/
│ │ ├── fileMessageContent.js
│ │ ├── imageMessageContent.js
│ │ ├── mediaMessageContent.js
│ │ ├── message.js
│ │ ├── messageConfig.js
│ │ ├── messageContent.js
│ │ ├── messageContentMediaType.js
│ │ ├── messageContentType.js
│ │ ├── messagePayload.js
│ │ ├── messageStatus.js
│ │ ├── modifyGroupInfoType.js
│ │ ├── myInfoType.js
│ │ ├── notification/
│ │ │ ├── addGroupMemberNotification.js
│ │ │ ├── changeGroupNameNotification.js
│ │ │ ├── createGroupNotification.js
│ │ │ ├── dismissGroupNotification.js
│ │ │ ├── groupNotification.js
│ │ │ ├── kickoffGroupMemberNotification.js
│ │ │ ├── notificationMessageContent.js
│ │ │ ├── quitGroupNotification.js
│ │ │ └── recallMessageNotification.js
│ │ ├── persistFlag.js
│ │ ├── protomessage.js
│ │ ├── protomessageContent.js
│ │ ├── sendMessage.js
│ │ ├── textMessageContent.js
│ │ ├── unknownMessageContent.js
│ │ ├── unsupportMessageContent.js
│ │ ├── videoMessageContent.js
│ │ └── websocketprotomessage.js
│ ├── model/
│ │ ├── conversation.js
│ │ ├── conversationInfo.js
│ │ ├── conversationType.js
│ │ ├── groupInfo.js
│ │ ├── groupMember.js
│ │ ├── groupMemberType.js
│ │ ├── groupType.js
│ │ ├── protoConversationInfo.js
│ │ ├── stateConversationInfo.js
│ │ ├── stateSelectChatMessage.js
│ │ ├── unReadCount.js
│ │ └── userInfo.js
│ ├── store/
│ │ └── localstore.js
│ ├── utils/
│ │ ├── StringUtil.js
│ │ ├── aes.js
│ │ ├── logger.js
│ │ └── timeUtils.js
│ └── websocketcli.js
└── static/
├── .gitkeep
└── css/
└── reset.css
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": [
["env", { "modules": false }],
"stage-2"
],
"plugins": ["transform-runtime"],
"comments": false,
"env": {
"test": {
"presets": ["env", "stage-2"],
"plugins": [ "istanbul" ]
}
}
}
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules/
dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
================================================
FILE: .postcssrc.js
================================================
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
"plugins": {
// to edit target browsers: use "browserlist" field in package.json
"autoprefixer": {}
}
}
================================================
FILE: LICENSE
================================================
Creative Commons Attribution-NonCommercial 3.0 Unported
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
License
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
1. Definitions
a. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License.
b. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License.
c. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership.
d. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License.
e. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast.
f. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work.
g. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
h. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images.
i. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium.
2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws.
3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:
a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections;
b. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified.";
c. to Distribute and Publicly Perform the Work including as incorporated in Collections; and,
d. to Distribute and Publicly Perform Adaptations.
The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved, including but not limited to the rights set forth in Section 4(d).
4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:
a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested.
b. You may not exercise any of the rights granted to You in Section 3 above in any manner that is primarily intended for or directed toward commercial advantage or private monetary compensation. The exchange of the Work for other copyrighted works by means of digital file-sharing or otherwise shall not be considered to be intended for or directed toward commercial advantage or private monetary compensation, provided there is no payment of any monetary compensation in connection with the exchange of copyrighted works.
c. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and, (iv) consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties.
d. For the avoidance of doubt:
i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License;
ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License if Your exercise of such rights is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(b) and otherwise waives the right to collect royalties through any statutory or compulsory licensing scheme; and,
iii. Voluntary License Schemes. The Licensor reserves the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License that is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(c).
e. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise.
5. Representations, Warranties and Disclaimer
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. Termination
a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
8. Miscellaneous
a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License.
c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
e. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.
f. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law.
Creative Commons Notice
Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor.
Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of the License.
Creative Commons may be contacted at http://creativecommons.org/.
================================================
FILE: README.md
================================================
[](https://gitee.com/comsince/vue-chat)
[](https://github.com/comsince/vue-chat)
# 飞享

**NOTE:** [飞享]IM系统开始进行商业化探索,欢迎有需要的`个人`,`企业`, `工作室`使用,关于授权合作事项,请咨询QQ `1282212195`
该项目是`飞享`聊天系统客户端源码vue即时通讯web端实现,使用websocket进行消息通讯,支持文本,图片类型发送,支持实时音视频,支持音视频与[android-chat](https://github.com/fsharechat/android-chat)客户端互通
# 项目截图
* 消息提示

* 文字消息

* 图片消息

* 视频消息

# 项目演示
* [项目公测地址](https://chat.comsince.cn)
* 请选择其中任何一个帐号密码进行登录即可
```properties
帐号:13800000000, 13800000001, 13800000002
密码:556677
```
* 暂时停止手机验证码注册登录,后续开通QQ群里面通知
## 版本规划
### V1.0.0
* 登录认证流程
* 实现朋友列表展示,用户信息获取
* 会话信息拉取,会话消息缓存
* 纯文本消息通讯
* 支持图片,视频消息展示
* 群会话功能
### V1.0.1
* 增加全屏幕模式支持,点击用户头像即可切换

### V1.0.2
* 计划增加一对一音视频聊天功能
* 实现与[android](https://github.com/fsharechat/android-chat)客户端音视频互通
### V1.0.3
* 增加好友搜索,好友添加功能,形成功能闭环
### V1.0.4
* 群组用户列表功能
### V1.0.5
* 增加websocket异步回调接口
* 增加创建群组功能
* 退出群聊
* 撤回消息
* 群组踢人与拉人
* 修改群名称

### V1.0.6
* 增加解散群组的功能
* 优化群组退出与解散交互体验
* 对于解散的群组与退出的群组,做删除会话处理
### V1.0.7
* 增加删除消息的功能
* 增加转发消息
### V1.0.8
* 支持缩略图传输,防止android 客户端转发图片报错
### V1.0.9
* 支持缩略图显示
### V1.0.14
* 修复群组管理员撤回其他成员发送消息的问题
### V1.1.0
* 加入群组音视频功能
### V1.1.3
* 增加文件发送功能
* 增加通知短音提示
* 增加音视频通话铃声提示
* 增加截图粘贴发送功能
### V1.1.4
* 限制每条会话的消息条数,发送消息时才会删除过多的消息,接收消息时有可能会删除历史未读消息,所以接收时暂不删除过多的消息
## Build Setup
``` bash
# install dependencies
npm install
# serve with hot reload at localhost:9080
npm run dev
# 运行请先检查如下配置:TCP服务配置,HTTPS配置,是否支持WSS,是否支持HTTPS,HTTP监听端口8081,HTTPS监听端口8443
# build for production with minification
npm run build
# build for production and view the bundle analyzer report
npm run build --report
```
For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
## 参考项目
* [Vue-chat](https://github.com/han960619/Vue-chat/)
## 依赖组件
* [常用的 vue 视频插件](https://wangchaoke.cn/?p=372)
* [西瓜播放器](http://h5player.bytedance.com/gettingStarted)
* [图标Icon支持](https://www.iconfont.cn/manage/index?spm=a313x.7781069.1998910419.11&manage_type=myprojects&projectId=1698562)
## 推荐项目
* [vue-wechat](https://github.com/zhaohaodang/vue-WeChat)
* [vue-chat](https://github.com/aermin/vue-chat)
* [QRCodeLogin](https://github.com/HeyJC/QRCodeLogin/blob/master/Web/auth/src/components/Input.vue) 说明二维码和密码登录的切换操作
## 开源协议
本项目使用非商业性署名协议,禁止演绎[Creative Commons Attribution Non Commercial 3.0 Unported](LICENSE)
## 一次性赞助
但是随着项目的增长,也需要相应的资金支持,你可以通过以下方式来赞助此项目
| 支付宝 | 微信|
| :--------: | :--------:|
|<img src="http://image.comsince.cn/zfb-purse.png" alt="图片替换文本" width="300" height="300" align="center" />|<img src="http://image.comsince.cn/wx-purse.png" alt="图片替换文本" width="300" height="300" align="center" />|
## QQ 群交流
| QQ群 |
| :--------: |
|<img src="http://image.comsince.cn/1-VYVLVL22-1587711095978-/storage/emulated/0/Tencent/QQ_Images/qrcode_1587711062833.jpg" alt="图片替换文本" width="300" height="400" align="center" />|
## 技术支持
如果公司采用本项目或者需要有商业需求,需要二次开发,提供技术支持,联系QQ:`1282212195`
================================================
FILE: build/build.js
================================================
require('./check-versions')()
process.env.NODE_ENV = 'production'
var ora = require('ora')
var rm = require('rimraf')
var path = require('path')
var chalk = require('chalk')
var webpack = require('webpack')
var config = require('../config')
var webpackConfig = require('./webpack.prod.conf')
var spinner = ora('building for production...')
spinner.start()
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
webpack(webpackConfig, function (err, stats) {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')
console.log(chalk.cyan(' Build complete.\n'))
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
))
})
})
================================================
FILE: build/check-versions.js
================================================
var chalk = require('chalk')
var semver = require('semver')
var packageConfig = require('../package.json')
var shell = require('shelljs')
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
var versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
},
]
if (shell.which('npm')) {
versionRequirements.push({
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
})
}
module.exports = function () {
var warnings = []
for (var i = 0; i < versionRequirements.length; i++) {
var mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (var i = 0; i < warnings.length; i++) {
var warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}
================================================
FILE: build/dev-client.js
================================================
/* eslint-disable */
require('eventsource-polyfill')
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
hotClient.subscribe(function (event) {
if (event.action === 'reload') {
window.location.reload()
}
})
================================================
FILE: build/dev-server.js
================================================
require('./check-versions')()
var config = require('../config')
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
}
var opn = require('opn')
var path = require('path')
var express = require('express')
var webpack = require('webpack')
var proxyMiddleware = require('http-proxy-middleware')
var webpackConfig = require('./webpack.dev.conf')
// default port where dev server listens for incoming traffic
var port = process.env.PORT || config.dev.port
// automatically open browser, if not set will be false
var autoOpenBrowser = !!config.dev.autoOpenBrowser
// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware
var proxyTable = config.dev.proxyTable
var app = express()
var compiler = webpack(webpackConfig)
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
quiet: true
})
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
log: () => {}
})
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
// proxy api requests
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context]
if (typeof options === 'string') {
options = { target: options }
}
app.use(proxyMiddleware(options.filter || context, options))
})
// handle fallback for HTML5 history API
app.use(require('connect-history-api-fallback')())
// serve webpack bundle output
app.use(devMiddleware)
// enable hot-reload and state-preserving
// compilation error display
app.use(hotMiddleware)
// serve pure static assets
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
app.use(staticPath, express.static('./static'))
var uri = 'http://localhost:' + port
var _resolve
var readyPromise = new Promise(resolve => {
_resolve = resolve
})
console.log('> Starting dev server...')
devMiddleware.waitUntilValid(() => {
console.log('> Listening at ' + uri + '\n')
// when env is testing, don't need open it
if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
opn(uri)
}
_resolve()
})
var server = app.listen(port)
module.exports = {
ready: readyPromise,
close: () => {
server.close()
}
}
================================================
FILE: build/utils.js
================================================
var path = require('path')
var config = require('../config')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
exports.assetsPath = function (_path) {
var assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders = function (options) {
options = options || {}
var cssLoader = {
loader: 'css-loader',
options: {
minimize: process.env.NODE_ENV === 'production',
sourceMap: options.sourceMap
}
}
// generate loader string to be used with extract text plugin
function generateLoaders (loader, loaderOptions) {
var loaders = [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader',
publicPath: '../../'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
var output = []
var loaders = exports.cssLoaders(options)
for (var extension in loaders) {
var loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
================================================
FILE: build/vue-loader.conf.js
================================================
var utils = require('./utils')
var config = require('../config')
var isProduction = process.env.NODE_ENV === 'production'
module.exports = {
loaders: utils.cssLoaders({
sourceMap: isProduction
? config.build.productionSourceMap
: config.dev.cssSourceMap,
extract: isProduction
})
}
================================================
FILE: build/webpack.base.conf.js
================================================
var path = require('path')
var utils = require('./utils')
var config = require('../config')
var vueLoaderConfig = require('./vue-loader.conf')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src')
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test'), resolve('/node_modules/webrtc-adapter/src')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
}
}
================================================
FILE: build/webpack.dev.conf.js
================================================
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
var path = require('path');
// add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})
module.exports = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
},
// cheap-module-eval-source-map is faster for development
devtool: '#cheap-module-eval-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': config.dev.env
}),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true,
favicon: path.resolve('favicon.ico'),
}),
new FriendlyErrorsPlugin()
]
})
================================================
FILE: build/webpack.prod.conf.js
================================================
var path = require('path')
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var CopyWebpackPlugin = require('copy-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var env = config.build.env
var webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
}),
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css')
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true
}
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
favicon: path.resolve('favicon.ico'),
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
}),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
if (config.build.productionGzip) {
var CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig
================================================
FILE: config/dev.env.js
================================================
var merge = require('webpack-merge')
var prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})
================================================
FILE: config/index.js
================================================
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')
module.exports = {
build: {
env: require('./prod.env'),
index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/',
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
},
dev: {
env: require('./dev.env'),
port: 9080,
autoOpenBrowser: true,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
cssSourceMap: false
}
}
================================================
FILE: config/prod.env.js
================================================
module.exports = {
NODE_ENV: '"production"'
}
================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>飞享IM</title>
<link rel="stylesheet" href="static/css/reset.css">
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
================================================
FILE: index.md
================================================
================================================
FILE: package.json
================================================
{
"name": "wechat",
"version": "1.1.6",
"description": "基于fshare的开源即时通讯web客户端",
"author": "comsince",
"private": true,
"scripts": {
"dev": "node build/dev-server.js",
"start": "node build/dev-server.js",
"build": "node build/build.js"
},
"dependencies": {
"@wcjiang/notify": "^2.0.12",
"axios": "^0.19.0",
"crypto-js": "^3.1.9-1",
"element-ui": "^2.13.0",
"kurento-utils": "^6.14.0",
"pinyin": "^2.9.0",
"qiniu-js": "^2.5.5",
"stylus": "^0.54.5",
"stylus-loader": "^3.0.1",
"uuid-js": "^0.7.5",
"v-viewer": "^1.5.1",
"vue": "^2.5.2",
"vue-axios": "^2.1.4",
"vue-router": "^3.0.1",
"vuex": "^3.0.1",
"webrtc-adapter": "^7.5.1",
"xgplayer": "^2.4.7",
"xgplayer-vue": "^1.1.5"
},
"devDependencies": {
"autoprefixer": "^6.7.2",
"babel-core": "^6.22.1",
"babel-loader": "^6.2.10",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-env": "^1.3.2",
"babel-preset-stage-2": "^6.22.0",
"babel-register": "^6.22.0",
"chalk": "^1.1.3",
"connect-history-api-fallback": "^1.3.0",
"copy-webpack-plugin": "^4.0.1",
"css-loader": "^0.28.0",
"eventsource-polyfill": "^0.9.6",
"express": "^4.14.1",
"extract-text-webpack-plugin": "^2.0.0",
"file-loader": "^0.11.1",
"friendly-errors-webpack-plugin": "^1.1.3",
"html-webpack-plugin": "^2.28.0",
"http-proxy-middleware": "^0.17.3",
"webpack-bundle-analyzer": "^2.2.1",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"opn": "^4.0.2",
"optimize-css-assets-webpack-plugin": "^1.3.0",
"ora": "^1.2.0",
"rimraf": "^2.6.0",
"url-loader": "^0.5.8",
"vue-loader": "^11.3.4",
"vue-style-loader": "^2.0.5",
"vue-template-compiler": "^2.2.6",
"webpack": "^2.3.3",
"webpack-dev-middleware": "^1.10.0",
"webpack-hot-middleware": "^2.18.0",
"webpack-merge": "^4.1.0"
},
"engines": {
"node": ">= 4.0.0",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
================================================
FILE: src/App.vue
================================================
<template>
<div class="layout-container" id="app-chat">
<router-view></router-view>
<searchfriend></searchfriend>
<creategroup></creategroup>
<relayMessage></relayMessage>
</div>
</template>
<script>
import { mapActions } from 'vuex'
import searchfriend from './page/friend/searchfriend'
import creategroup from './page/group/creategroup'
import relayMessage from './components/menu/relayMessage'
export default {
name : 'AppChat',
components: {
searchfriend,
creategroup,
relayMessage,
}
}
</script>
<style scoped>
.layout-container {
width: 100%;
height: 100%;
min-width: 1088px;
min-height: 550px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column
}
</style>
================================================
FILE: src/assets/fonts/iconfont.css
================================================
@font-face {font-family: "iconfont";
src: url('iconfont.eot?t=1592274269382'); /* IE9 */
src: url('iconfont.eot?t=1592274269382#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAy4AAsAAAAAF1wAAAxrAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCGNAqcCJY0ATYCJANgCzIABCAFhG0HgkgbjhMjEaac1Znsrwu4Q2pxlyQy2IObrVRoQS6vgYCib9QxOJH4IiO5qF+ow/eNfD6asz87syHZ3YSNIpaKUxrUQ1PFrWIWapq6UlGHe0epaDhfeu6XbnWqb5qigXvxVnttfwLZhMWW2FkAIGzCGPqKGBjm9r1elL5r9UfHVLmp/Mh2kRVXwMsKwuZGZq3NEiDU5pzCUfHDAgj4/+de7UtZdgxvGFdhembVe0k+PEhPbzk/I0qJX4egajeFx89jXjnD/AGAA9SrmnUzzg8CRpseqIuCj4xV3x0IAAGBCAax9+yXDzUYFCd0GzlsSA3UsUqwkgkE6jJVznUZiAs81NwSrhHAKnf8ydcII2qAA0+h7il7cMZApCi0dTV1/u9EOXWirj8PwPl6AAUQDIABnJy7iQigPgt2hZCnMrQA6AyreJNqhSoqxV8JUtopwUqokqikKg6lUClRBivDlBHKktYuraWtq///v1Zp38KUVQw9dOUTdNalgwtDCm3/hQdoIcMACySYYIYAEWp4gUIDFYzwhAcIdOChBwMHEBXoKgEAaS8bvQCFmiZoAUVlggwoviYYAMXPBAug+JsgAUoQwHQn2wEwI4MBCMhQACIyEYAamYI9vkwFQJEOABpkIRxzkiUAjMjBADyRQwF4IIcBIMgRAHTIJZjAA61dTNADraUmMKB1dQYclPC2r2MD0AtgYQDfDNx1rqDa5SG4BI+BM503teBSvTkQWcdkWTDAn6lWkr8syUxzFuvVVtKw9hLxN8tDzKgykzR+GslDI795b7OfJHkbzWZvKdggyRrewztohu5skz+3/Umk54WnQXRHc3H1uqWWDYTW1Ztr1m8a6wndjHNPAti2pgjd+eZAfufT6Gn8hg0W3bp1JrZ+vdlz48ZSad/RWqvfGalvbOgnPwbFPsRDvyopGdWosbBWSwmqleKswhpUpUCw5vvVmqbMD2pVVQuFNSA2JcaNTY75lOwV/UzZcC8SVDdF+gmw9k2l4yR1AHfjp8PSpmfEodQILz53R2rF2rhNu9PKvdQ60Sq/emdaZe36+C01f1Pj9ZU2DGBOhannK87sci8urTt1PO9lEu5qg6GBDIMsI0SmQUVmud1uo6J75VN6NCY2V0caKBEXcwp729cOq8KuLcKV3/OK+XM2AhF/p24tQgeS7ms/eqX6Am67jM/P2lBtDOMJA93QVVjhdrhFzLYrtpVmwFZvmRMpwhnsk72aSIJQDSF1wxxT0OhOb7S5Ba1ALweRBwT8Dqn8Lvul9xqBN8gUtUFIZfBHZuxM2Q5ckZNrVe6uHZViZEGwPXNyof4Gyc7EZS+fMDqpDxk4JDurzJwhxBxc9SKi6ECLSvpCJIgWyEzewOwortYPzon1dkMr8LJwMOj3+9DAqweg6uJXZxtaweAqzA2qFZHJAzQAa6hvUFVWdnV5Ze0T2ucIhYHhY3DlBbWF8bk0JEoBzNxnPo7Wxr5BtMrdIJ7zarVi/2VEM+1eIDTHpKl2fDnMP1TrAwg/W968IW8TiBoESNvZebCe/xCbZ+/OMffnHiBzBtuCgQjxcdKYbMnnJ3cEvtGF0NU8Sk5updHND/Lk4/8oiVJLcqM8H5ZnZ2X20BZjB1ETDkCMirKstZ3BvW6rw31ZDGbAIYFlhdB8/nJhQEax+0mfwLbYvfJaZFy39hyBqKrmDOzS9/UpzO9/fWUzuF6tCwOiYBdzEYUWKKRxjeGs9zPtdUCn5wotMp7v6exp8Z2IfJpCn7Yv+x0NOO8CR+u8r+6PsNx42pbSV5fIehWjtA1W7Iac14SGenlTpyawOVx5Vj2jUcQuj7Vt8jghd/xC5KPnAXXbw+74Fj9xa3/Qup+44fUz5L0DdLkv5T6eJ15e3ctDXdvFDZENiE7PGRvu8d4jfOx7hrPRjPeaRtd7V6Pm0H7Drve20FCbNSzC53WYfHKzOFncfJLzcm/1U/STzQvd4nf4sFbvq9c661FH/lXFKSkzirdar+VcUWYm3tS28TPo7HH11+N1SatcYC2QW8UwP02IEDbviti1WTjtGG/tFqFxRbg0Ilsk5jkPG6cY6w1TrPXyFFJPCulnB/IUPM+Pjinu634zX7/YIK5LBAtPnM50+nP8P3+9veLQzTZ+1puHDkkGP046dP3wIAPCal/VcmtjF8oLFy1c2mUprNSZ3RDcaDnGFIUd06Aolh3G0LadoD89JXrKzRPdXP3Za6v1NWl2qmCvhdVmr7t9chXt3nZeLd1bQsx49FGU74KRmDbo3/uSSFI8bVwFKSd2HSmOjMf5F9tO3IUawdQk2nRkxCbXJE633byaGyuadHdh/KBxe+I+DCousgNi8yA/mtj54rwgJLniJk0SrlquCs5pDZYGIXZbb89ed7QdlJ9U621vl+1EDjdp+/ZeiU2s9cjNrct5+D3BzX+LR8VGgWreidJ17tebrUxXj8bJJGgyVzN3Xi3RRPbiarjauXNrOQjRODdWlerwvXHAUGqoCOufGXlq1PcPj8yMfybD51y6O3xz/d1OvnccUro2ddjEdClVe/uQz8JR/97crU+IFRMO6trq+Lr6j35XVfYy3rk/bGTzDwU4cdCFDVrDT/dfauNGfGCG1GJRVG62P5/WbBDZP7dbONa1520/P7McQT+n32D//MOs9PUTxZKE9A22N33gSppGV9+ovLyMzBknFZlUEnx+EIk6vaowg6STqjxNKi/94AOJqMvrpgysaHM1rXpcBD2Td12vHPzF14Ls5R7jMpk0bLCSHFwzxN3dLX1XVV22+a6H5uMkS8LontP3s/jv+lsa+C27QGoKD/eP7Tp7ht+MRdK4blVh+RVeVdwrZv2Hv8YuBH6rvZNQwZV6z51YbosLWZR4qU943nZSRvepXjyiktgfle5jR9SfY3enopJ2mQtXFQT27bv6iHXs0fbhfarHFvNl+GLy7YdzWzUjjYOnpzc9vW0LPYr4UUVk4kRSBKG8iINALStsNTyNFBUT0qmSpZ2mFxeTVMLvP6ScpeanVQske8SIbOJym93CqcxMP7FHxqp142JHz5y0yy3kZCxNsU1uezJsQZfY9KguOZWCO9I2lCO7bghiWXi/eSnxBfMj1qTNz+hc6NRk+TSMGia0aDIDHf72YcMyAnoGZIpui1tcQa9e5VZoWhBts6bae5/BYP6V1fpKpnmq+FdhzBocS7cnzJiR7pkXYfe0f6e6z9RqJtL3BSUlQ3AhoMFmt1MHhdv87I124Tvzd4JdO/XjhqbFgY7AxU0NH0/VFog5oWKB0W7Xfi98ZxpSp3o5h5i+N01Zu8bRD1m7dvL8NX/vcptbxL+bjvUtbIYv/nOQykrScyZxUOUgMeRAPRuurMpGX/3Js9ZPuVIwderZzb/jhQt43G/0N/Xxv70pp4ysFf+VIqUfxLXi/yf9T/4vBsIPJw9I3yBuXOHBpS8m9ekUkOc3y2Ptx/CrGsjSAgYW/5JinHfecDhv8f0M/6Zkxl1GOGZt/B7A/8oW6usuOE6baW6bT3bSDlAb6Bzu/3gwJ0GdYsHt6zSp4cPvaFjZhHzuY3Yzj2fIGQzc/eAGvUXtBIcYaCENH++j7ev616i270nLaeCEw5zS8OcRSsSaE1zMX9qhwJpeWqo8E35Vi799T3mry6xwk/OphvoH9moC/F11ah7F7pKqypvd/JumPdoib4yCOuAACLof8Fd1nQEtc3LNtZ0/7q45xBw8EIAp1GhvsPxgzENEBFZBjWQsoBscNxZhxHhQEKYB0BULDphAxgnMQYcbmELGBwbLd2MeFnyLVZAJxQIKiTGmiI4rxMGrAe5AsGjDMKZSGjVu4HX/AuVzbqJJd/4fGItb2XV4lWz+hBJMHxF2qW6ck0waKtjH4d4gz4lVhlKIXZg4V70sFjIvMoypqA3waoA7e3nBRJuSGFMpT+4GqT//Bcrn3JTs97bkHxiLPnP9b8J+axifoqy137modqluOOFJZu5qqGA+hAe5biCmyh8shdiFkhZB9WIhjifrinB8t3hNnAMgQH1E7w0hFVXTDdOyHdfzz+/cvXf/wcNHj3UmJFhhOXeW5WWc+EB6KvNW8WgLaorcJn6L515ZWczWD8SsLVW1/N6Gm/YGC91uNKovIAcHQWTGOXFRWx9YylGMJLdUqkGDiGOvqqhuyF+PN5x0uZdwirJ+W8cfsscxWKIA6kVY2mYgstX/+oGY0BXz2JJ43u1hgI4lbz11nL+nDqvK6rsrKFN3vvJc+IpaDQ==') format('woff2'),
url('iconfont.woff?t=1592274269382') format('woff'),
url('iconfont.ttf?t=1592274269382') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url('iconfont.svg?t=1592274269382#iconfont') format('svg'); /* iOS 4.1- */
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-shipin:before {
content: "\e669";
}
.icon-shanchu-fangkuang:before {
content: "\e791";
}
.icon-zengjiashuzi:before {
content: "\e641";
}
.icon-jianshanchu-xianxingfangkuang:before {
content: "\e75a";
}
.icon-zengjia2:before {
content: "\e689";
}
.icon-yichu:before {
content: "\e656";
}
.icon-zengjia:before {
content: "\e668";
}
.icon-delete-br:before {
content: "\e63d";
}
.icon-loading-solid:before {
content: "\e647";
}
.icon-fasongshibai:before {
content: "\e62c";
}
.icon-pengyou1:before {
content: "\e631";
}
.icon-yaoqinghaoyou:before {
content: "\e61c";
}
.icon-jiahaoyou:before {
content: "\e603";
}
.icon-ai-video:before {
content: "\e66b";
}
.icon-biaoqing:before {
content: "\e666";
}
.icon-dkw_xiaoxi:before {
content: "\e606";
}
.icon-dianhua:before {
content: "\e729";
}
.icon-pengyou:before {
content: "\e61a";
}
.icon-sousuo:before {
content: "\e659";
}
.icon-tuichu:before {
content: "\e61f";
}
.icon-tupian:before {
content: "\e623";
}
.icon-wenjian:before {
content: "\e61b";
}
.icon-guaduan:before {
content: "\e640";
}
================================================
FILE: src/assets/fonts/iconfont.js
================================================
!function(c){var t,i,o,a,l,h,e,s='<svg><symbol id="icon-shipin" viewBox="0 0 1024 1024"><path d="M812.032 102.4H211.968c-60.416 0-109.056 49.152-109.056 110.08v495.104c0 60.928 48.64 110.08 109.056 110.08h600.064c60.416 0 109.056-49.152 109.056-110.08V212.48c0-60.928-48.64-110.08-109.056-110.08zM689.664 483.84L416.768 648.192c-4.608 2.56-9.216 4.096-13.824 4.096-4.608 0-9.216-1.024-13.312-3.584-8.704-4.608-13.824-13.824-13.824-24.064V295.424c0-9.728 5.12-18.944 13.824-24.064s18.944-4.608 27.648 0.512l272.896 164.352c8.192 5.12 13.312 13.824 13.312 23.552-0.512 10.24-5.632 18.944-13.824 24.064z m-535.04 385.024h715.264V921.6H154.624z" fill="#424242" ></path></symbol><symbol id="icon-shanchu-fangkuang" viewBox="0 0 1024 1024"><path d="M884.363636 46.545455c51.2 0 93.090909 41.890909 93.090909 93.090909v744.727272c0 51.2-41.890909 93.090909-93.090909 93.090909H139.636364c-51.2 0-93.090909-41.890909-93.090909-93.090909V139.636364c0-51.2 41.890909-93.090909 93.090909-93.090909h744.727272z m23.272728 837.818181V139.636364c0-12.613818-10.658909-23.272727-23.272728-23.272728H139.636364c-12.613818 0-23.272727 10.658909-23.272728 23.272728v744.727272c0 12.613818 10.658909 23.272727 23.272728 23.272728h744.727272c12.613818 0 23.272727-10.658909 23.272728-23.272728z m-151.272728-407.272727h-488.727272a34.909091 34.909091 0 1 0 0 69.818182h488.727272a34.909091 34.909091 0 1 0 0-69.818182" fill="#797979" ></path></symbol><symbol id="icon-zengjiashuzi" viewBox="0 0 1024 1024"><path d="M823.8 91H194.2C137.3 91 91 137.3 91 194.2v629.6C91 880.7 137.3 927 194.2 927h629.6c56.9 0 103.2-46.3 103.2-103.2V194.2C927 137.3 880.7 91 823.8 91z m41.3 732.8c0 22.8-18.5 41.3-41.3 41.3H194.2c-22.8 0-41.3-18.5-41.3-41.3V194.2c0-22.8 18.5-41.3 41.3-41.3h629.6c22.8 0 41.3 18.5 41.3 41.3v629.6z" ></path><path d="M710.3 478H540V307.7c0-17.1-13.9-31-31-31s-31 13.9-31 31V478H307.7c-17.1 0-31 13.9-31 31s13.9 31 31 31H478v170.3c0 17.1 13.9 31 31 31s31-13.9 31-31V540h170.3c17.1 0 31-13.9 31-31s-13.9-31-31-31z" ></path></symbol><symbol id="icon-jianshanchu-xianxingfangkuang" viewBox="0 0 1024 1024"><path d="M129.5 894.5v-765h765v765h-765z m700-65v-635h-635v635h635z" fill="" ></path><path d="M862.5 862.5h-701v-701h701v701z m-700-1h699v-699h-699v699z" fill="" ></path><path d="M288 480h448c17.5 0 32 14.4 32 32 0 17.5-14.4 32-32 32H288c-17.5 0-32-14.4-32-32s14.4-32 32-32z" fill="" ></path></symbol><symbol id="icon-zengjia2" viewBox="0 0 1024 1024"><path d="M473.60158 550.400226v230.397742c0 21.206823 17.186178 38.402032 38.391195 38.402032 21.21766 0 38.405644-17.197016 38.405645-38.402032V550.400226h230.399548c21.205016 0 38.402032-17.198822 38.402032-38.405645 0-21.205016-17.197016-38.391195-38.402032-38.391194H550.39842V243.205644c0-21.219466-17.187984-38.405644-38.405645-38.405644-21.205016 0-38.391195 17.186178-38.391195 38.405644v230.397743H243.193001c-21.205016 0-38.393001 17.187984-38.393001 38.391194 0 21.205016 17.187984 38.405644 38.393001 38.405645h230.408579z" fill="#231815" ></path><path d="M0 921.594581V102.394581C0 45.838238 45.838238 0 102.394581 0h819.2c56.554538 0 102.405419 45.838238 102.405419 102.394581v819.2c0 56.556344-45.852687 102.405419-102.405419 102.405419H102.394581C45.838238 1024 0 978.150925 0 921.594581z m947.203161 0V102.394581c0-18.098319-7.499423-25.597742-25.60858-25.597742H102.394581c-18.096513 0-25.597742 7.499423-25.597742 25.597742v819.2c0 18.109156 7.499423 25.610386 25.597742 25.610386h819.2c18.10735 0 25.60858-7.501229 25.60858-25.610386z" fill="#231815" ></path></symbol><symbol id="icon-yichu" viewBox="0 0 1024 1024"><path d="M981.475083 0H42.524917a42.524917 42.524917 0 0 0-42.524917 42.524917v935.548173a42.524917 42.524917 0 0 0 42.524917 42.524917h935.548173a42.524917 42.524917 0 0 0 42.524917-42.524917V42.524917a42.524917 42.524917 0 0 0-39.122924-42.524917zM935.548173 935.548173H85.049834V85.049834h850.498339z" fill="#666666" ></path><path d="M340.199336 554.524917h340.199335a42.524917 42.524917 0 0 0 42.524917-42.524917 41.674419 41.674419 0 0 0-5.953488-20.41196 42.524917 42.524917 0 0 0-36.571429-26.365449H340.199336a42.524917 42.524917 0 0 0-42.524917 42.524917 41.674419 41.674419 0 0 0 5.953488 20.41196 42.524917 42.524917 0 0 0 36.571429 26.365449z" fill="#666666" ></path></symbol><symbol id="icon-zengjia" viewBox="0 0 1024 1024"><path d="M66.56 66.56v890.88h890.88V66.56H66.56z m827.24352 827.24352H130.18624V130.19648h763.6224v763.60704z" fill="#333333" ></path><path d="M480.23552 480.25088H311.13728c-17.5616 0-32.01024 14.208-32.01024 31.74912 0 17.65888 14.33088 31.75936 32.01024 31.75936h169.09824v169.09824c0 17.5616 14.22336 32.01024 31.75936 32.01024 17.664 0 31.75936-14.33088 31.75936-32.01024v-169.09824h169.10848c17.5616 0 32-14.21824 32-31.75936 0-17.65888-14.32064-31.74912-32-31.74912h-169.10848V311.1424c0-17.56672-14.21312-32.02048-31.75936-32.02048-17.65376 0-31.75936 14.34112-31.75936 32.02048v169.10848z" fill="#333333" ></path></symbol><symbol id="icon-delete-br" viewBox="0 0 1024 1024"><path d="M22.286336 78.303232v865.705984h983.525376V78.303232H22.286336z m942.563328 824.745984H63.246336V119.263232h901.605376v783.785984z" fill="" ></path><path d="M316.83584 510.312448h394.422272v40.96H316.83584z" fill="" ></path></symbol><symbol id="icon-loading-solid" viewBox="0 0 1024 1024"><path d="M512 0a51.2 51.2 0 0 1 0 102.4C285.7728 102.4 102.4 285.7728 102.4 512s183.3728 409.6 409.6 409.6 409.6-183.3728 409.6-409.6c0-82.6112-24.448-161.4592-69.5808-228.4544A51.2 51.2 0 0 1 936.96 226.3552 509.7984 509.7984 0 0 1 1024 512c0 282.7776-229.2224 512-512 512S0 794.7776 0 512 229.2224 0 512 0z" ></path></symbol><symbol id="icon-fasongshibai" viewBox="0 0 1024 1024"><path d="M512 0C229.052632 0 0 229.052632 0 512s229.052632 512 512 512 512-229.052632 512-512S794.947368 0 512 0z m0 808.421053c-29.642105 0-53.894737-24.252632-53.894737-53.894737s24.252632-53.894737 53.894737-53.894737 53.894737 24.252632 53.894737 53.894737-24.252632 53.894737-53.894737 53.894737z m26.947368-202.105264h-53.894736L458.105263 215.578947h107.789474l-26.947369 390.736842z" fill="#E93A0F" ></path></symbol><symbol id="icon-pengyou1" viewBox="0 0 1024 1024"><path d="M633.6 537.6c64-38.4 102.4-102.4 102.4-172.8 0-115.2-96-204.8-217.6-204.8S300.8 249.6 300.8 364.8c0 70.4 38.4 134.4 102.4 172.8-140.8 44.8-224 166.4-224 294.4 0 12.8 12.8 32 32 32 12.8 0 32-12.8 32-32-6.4-140.8 115.2-256 268.8-256 153.6 0 275.2 115.2 275.2 262.4 0 12.8 12.8 32 32 32 12.8 0 32-12.8 32-32-6.4-134.4-89.6-256-217.6-300.8z m38.4-172.8C672 448 601.6 518.4 512 518.4S352 448 352 364.8c0-83.2 70.4-153.6 160-153.6 89.6 6.4 160 70.4 160 153.6z m0 0" ></path></symbol><symbol id="icon-yaoqinghaoyou" viewBox="0 0 1024 1024"><path d="M1000.727273 837.818182h-116.363637v-116.363637c0-13.963636-9.309091-23.272727-23.272727-23.272727s-23.272727 9.309091-23.272727 23.272727v116.363637h-116.363637c-13.963636 0-23.272727 9.309091-23.272727 23.272727s9.309091 23.272727 23.272727 23.272727h116.363637v116.363637c0 13.963636 9.309091 23.272727 23.272727 23.272727s23.272727-9.309091 23.272727-23.272727v-116.363637h116.363637c13.963636 0 23.272727-9.309091 23.272727-23.272727s-11.636364-23.272727-23.272727-23.272727z m-172.218182-190.836364c6.981818-9.309091 4.654545-25.6-4.654546-32.581818-55.854545-41.890909-118.690909-72.145455-183.854545-88.436364 90.763636-46.545455 151.272727-139.636364 151.272727-246.690909 0-153.6-125.672727-279.272727-279.272727-279.272727S232.727273 125.672727 232.727273 279.272727c0 109.381818 62.836364 202.472727 153.6 249.018182C172.218182 584.145455 11.636364 770.327273 0 998.4c0 13.963636 9.309091 23.272727 23.272727 23.272727h2.327273c11.636364 0 23.272727-9.309091 23.272727-23.272727 11.636364-249.018182 214.109091-442.181818 465.454546-442.181818 102.4 0 202.472727 32.581818 283.927272 93.090909 9.309091 9.309091 23.272727 6.981818 30.254546-2.327273zM279.272727 279.272727c0-128 104.727273-232.727273 232.727273-232.727272s232.727273 104.727273 232.727273 232.727272-104.727273 232.727273-232.727273 232.727273-232.727273-104.727273-232.727273-232.727273z" fill="#666666" ></path></symbol><symbol id="icon-jiahaoyou" viewBox="0 0 1024 1024"><path d="M438 526.7c-3.7 0-7.4-0.6-11.1-2-47.5-17.1-87.6-48.3-116-90.2-35.6-52.5-48.6-115.8-36.6-178s47.5-116.2 100-151.8C426.8 69.1 490 56 552.3 68.1c62.3 12 116.2 47.5 151.8 100 35.6 52.5 48.6 115.8 36.6 178-12 62.3-47.5 116.2-100 151.8-16 10.8-33.2 19.7-51.2 26.3-17 6.3-35.8-2.4-42.1-19.4-6.3-17 2.4-35.8 19.4-42.1 13-4.8 25.5-11.2 37.1-19.1 38.1-25.8 63.8-64.8 72.5-109.9 8.7-45.1-0.8-90.9-26.5-128.9-25.8-38-64.8-63.8-109.9-72.4-45-8.6-90.9 0.7-128.9 26.5-78.5 53.2-99.1 160.3-45.9 238.8 20.6 30.4 49.6 52.9 84 65.3 17 6.1 25.9 24.9 19.7 41.9-4.9 13.5-17.5 21.8-30.9 21.8zM130.8 952c-18.1 0-32.8-14.7-32.8-32.8 0-164.7 71-361.1 409.5-361.1 62.8 0 119.1 6.7 167.3 19.9 17.4 4.8 27.7 22.8 22.9 40.2-4.8 17.4-22.8 27.7-40.2 22.9-42.6-11.6-93.1-17.6-150-17.6-234.7 0-343.9 93.9-343.9 295.6-0.1 18.2-14.7 32.9-32.8 32.9zM643.7 753h314.1v98.3H643.7z" fill="#009FE8" ></path><path d="M751.6 645.1h98.3v314.1h-98.3z" fill="#009FE8" ></path></symbol><symbol id="icon-ai-video" viewBox="0 0 1024 1024"><path d="M647.725 840.897c0 0 93.091 0 93.091-93.091L740.816 282.353c0-93.091-93.091-93.091-93.091-93.091L93.091 189.262c0 0-93.091 0-93.091 93.091l0 465.453c0 93.091 93.091 93.091 93.091 93.091L647.725 840.897zM795.041 371.115 795.041 660.72l228.956 180.177L1023.997 189.263 795.041 371.115z" ></path></symbol><symbol id="icon-biaoqing" viewBox="0 0 1024 1024"><path d="M510.54537 60.915371c-248.305249 0-449.610044 201.296609-449.610044 449.610044s201.304795 449.609021 449.610044 449.609021c248.304226 0 449.610044-201.295586 449.610044-449.609021S758.849596 60.915371 510.54537 60.915371zM236.869291 393.236106c0-43.181475 35.011398-78.192873 78.192873-78.192873 43.182498 0 78.192873 35.011398 78.192873 78.192873s-35.010375 78.192873-78.192873 78.192873C271.879666 471.428979 236.869291 436.417581 236.869291 393.236106zM510.54537 823.296909c-119.408577 0-217.989803-89.227184-232.689596-204.644867-1.221828-3.225461-1.89107-6.729262-1.89107-10.384511 0-16.198937 13.134135-29.321816 29.322839-29.321816 16.187681 0 29.321816 13.122879 29.321816 29.321816l1.069355 0c9.735735 87.966471 84.302011 156.385746 174.865633 156.385746 89.035826 0 162.608471-66.129116 174.312024-151.948691-0.210801-1.449002-0.324388-2.929726-0.324388-4.437056 0-16.198937 13.114692-29.321816 29.321816-29.321816 16.187681 0 29.321816 13.122879 29.321816 29.321816l1.146103 0C734.395648 728.676901 633.523239 823.296909 510.54537 823.296909zM706.027553 471.428979c-43.181475 0-78.192873-35.011398-78.192873-78.192873s35.011398-78.192873 78.192873-78.192873 78.192873 35.011398 78.192873 78.192873S749.209028 471.428979 706.027553 471.428979z" ></path></symbol><symbol id="icon-dkw_xiaoxi" viewBox="0 0 1024 1024"><path d="M288 426.666667a53.333333 53.333333 0 1 0 53.333333 53.333333 53.333333 53.333333 0 0 0-53.333333-53.333333z m0 64a10.666667 10.666667 0 1 1 10.666667-10.666667 10.666667 10.666667 0 0 1-10.666667 10.666667zM522.666667 426.666667a53.333333 53.333333 0 1 0 53.333333 53.333333 53.333333 53.333333 0 0 0-53.333333-53.333333z m0 64a10.666667 10.666667 0 1 1 10.666666-10.666667 10.666667 10.666667 0 0 1-10.666666 10.666667zM757.333333 426.666667a53.333333 53.333333 0 1 0 53.333334 53.333333 53.333333 53.333333 0 0 0-53.333334-53.333333z m0 64a10.666667 10.666667 0 1 1 10.666667-10.666667 10.666667 10.666667 0 0 1-10.666667 10.666667z" fill="#666666" ></path><path d="M512 42.666667C224.853333 42.666667 0 229.333333 0 469.333333a413.44 413.44 0 0 0 166.826667 327.04l-42.666667 126.72a43.946667 43.946667 0 0 0 0 50.773334 29.653333 29.653333 0 0 0 21.333333 8.533333 97.066667 97.066667 0 0 0 42.666667-14.506667c31.146667-17.066667 66.986667-35.626667 95.786667-50.773333L322.133333 896a673.066667 673.066667 0 0 0 189.866667 21.333333c282.24 0 512-200.96 512-448C1024 229.333333 799.146667 42.666667 512 42.666667z m0 832a604.586667 604.586667 0 0 1-185.173333-21.333334 21.333333 21.333333 0 0 0-17.28 1.493334c-9.6 5.333333-25.813333 13.653333-45.226667 23.893333-29.226667 15.146667-65.28 33.92-96.853333 51.2l-2.346667 1.28 46.72-135.893333a21.333333 21.333333 0 0 0-7.68-24.32A373.12 373.12 0 0 1 42.666667 469.333333C42.666667 253.226667 248.746667 85.333333 512 85.333333s469.333333 168.746667 469.333333 384c0 222.72-210.56 405.333333-469.333333 405.333334z" fill="#666666" ></path></symbol><symbol id="icon-dianhua" viewBox="0 0 1024 1024"><path d="M418.368 344.192l-88.416 89.92C376 536.32 473.632 650.144 586.912 688.416l76.416-76.192s181.184 31.616 221.92 51.392c35.296 17.12 46.976 55.072 41.44 101.888-5.92 50.304-71.84 161.248-190.624 162.464-118.784 1.216-293.696-88.256-419.008-220.288C191.744 575.616 80 408.288 97.92 266.144c17.888-142.08 141.696-167.52 172.672-169.792 31.296-2.304 56.16 6.88 75.04 30.272 24.32 30.144 72.768 217.568 72.768 217.568z" fill="#333333" ></path></symbol><symbol id="icon-pengyou" viewBox="0 0 1024 1024"><path d="M572.2 530c-114.3 0-207.3-93-207.3-207.3s93-207.3 207.3-207.3c114.3 0 207.3 93 207.3 207.3S686.5 530 572.2 530z m0-364.7c-86.8 0-157.3 70.6-157.3 157.3S485.4 480 572.2 480s157.3-70.6 157.3-157.3S659 165.3 572.2 165.3zM279.1 515.8c-3 0-6-0.5-9-1.7-35.4-13.7-65.7-37.5-87.5-68.7-22.4-32-34.2-69.7-34.2-108.8 0-104.9 85.4-190.3 190.3-190.3 13.8 0 25 11.2 25 25s-11.2 25-25 25c-77.4 0-140.3 62.9-140.3 140.3 0 57.6 36.1 110.2 89.7 130.9 12.9 5 19.3 19.5 14.3 32.3-3.8 9.9-13.3 16-23.3 16zM932 888.6c-13.8 0-25-11.2-25-25 0-106.8-49.2-204.8-135-268.7-11.1-8.2-13.4-23.9-5.1-35 8.2-11.1 23.9-13.4 35-5.1C849 589.9 888 636 914.7 688.1c28.1 54.6 42.3 113.7 42.3 175.5 0 13.8-11.2 25-25 25z" ></path><path d="M92 784.5c-1 0-2.1-0.1-3.1-0.2-13.7-1.7-23.4-14.2-21.7-27.9 7.6-61.4 29-118.2 63.5-169 33-48.4 77.3-89.3 128.4-118.4 12-6.8 27.3-2.6 34.1 9.4 6.8 12 2.6 27.3-9.4 34.1-93 52.9-153.9 144-167 250-1.6 12.7-12.3 22-24.8 22zM212.4 889.8c-13.8 0-25-11.2-25-25 0-51.9 10.2-102.3 30.3-149.8 19.4-45.8 47.1-87 82.5-122.3 35.3-35.3 76.5-63.1 122.3-82.5 47.5-20.1 97.9-30.3 149.8-30.3 13.8 0 25 11.2 25 25s-11.2 25-25 25c-184.6 0-334.8 150.2-334.8 334.8-0.1 13.9-11.3 25.1-25.1 25.1z" ></path></symbol><symbol id="icon-sousuo" viewBox="0 0 1024 1024"><path d="M883.2 864C970.666667 772.266667 1024 648.533333 1024 512 1024 230.4 793.6 0 512 0S0 230.4 0 512s230.4 512 512 512c130.133333 0 249.6-49.066667 341.333333-130.133333l123.733334 123.733333c6.4 6.4 19.2 6.4 25.6 0l4.266666-4.266667c6.4-6.4 6.4-19.2 0-25.6l-123.733333-123.733333zM512 981.333333C253.866667 981.333333 42.666667 770.133333 42.666667 512S253.866667 42.666667 512 42.666667s469.333333 211.2 469.333333 469.333333-211.2 469.333333-469.333333 469.333333z" ></path></symbol><symbol id="icon-tuichu" viewBox="0 0 1024 1024"><path d="M696.4 926H237.3c-67.5 0-122.4-54.9-122.4-122.4V221c0-67.5 54.9-122.4 122.4-122.4h459.1c67.5 0 122.4 54.9 122.4 122.4v67.6c0 14-11.3 25.3-25.3 25.3s-25.3-11.3-25.3-25.3V221c0-39.6-32.2-71.8-71.8-71.8H237.3c-39.6 0-71.8 32.2-71.8 71.8v582.7c0 39.6 32.2 71.8 71.8 71.8h459.1c39.6 0 71.8-32.2 71.8-71.8v-85.2c0-14 11.3-25.3 25.3-25.3s25.3 11.3 25.3 25.3v85.2c0 67.4-54.9 122.3-122.4 122.3z" fill="#383838" ></path><path d="M734.2 685.1c-6.5 0-12.9-2.5-17.9-7.4-9.9-9.9-9.9-25.9 0-35.8L846 512.3 716.3 382.6c-9.9-9.9-9.9-25.9 0-35.8 9.9-9.9 25.9-9.9 35.8 0l147.6 147.6c9.9 9.9 9.9 25.9 0 35.8L752.1 677.7c-5 5-11.4 7.4-17.9 7.4z" fill="#383838" ></path><path d="M876.7 537.6h-507c-14 0-25.3-11.3-25.3-25.3s11.3-25.3 25.3-25.3h507c14 0 25.3 11.3 25.3 25.3s-11.3 25.3-25.3 25.3z" fill="#383838" ></path></symbol><symbol id="icon-tupian" viewBox="0 0 1024 1024"><path d="M769.3 154.8H258.7c-92.9 0-168.5 75.6-168.5 168.5v379.8c0 92.9 75.6 168.5 168.5 168.5h510.5c92.9 0 168.5-75.6 168.5-168.5V323.3c0.1-92.9-75.5-168.5-168.4-168.5z m-372 146c48.4 0 87.7 39.2 87.7 87.7 0 48.4-39.2 87.7-87.7 87.7-48.4 0-87.7-39.2-87.7-87.7 0.1-48.4 39.3-87.7 87.7-87.7z m-117 407l120.2-187.3 85.8 121L606.6 460l154.5 247.8H280.3z" ></path></symbol><symbol id="icon-wenjian" viewBox="0 0 1024 1024"><path d="M928.426667 354.986667L213.333333 687.786667 102.4 450.56 781.653333 134.826667c20.48-10.24 44.373333 0 52.906667 18.773333l93.866667 201.386667z" fill="#333333" opacity=".5" ></path><path d="M459.093333 262.826667l-6.826666-51.2c-1.706667-15.36-13.653333-25.6-29.013334-25.6H114.346667c-15.36 0-29.013333 13.653333-29.013334 29.013333v658.773333c0 15.36 13.653333 29.013333 29.013334 29.013334h795.306666c15.36 0 29.013333-13.653333 29.013334-29.013334V317.44c0-15.36-13.653333-29.013333-29.013334-29.013333H488.106667c-15.36 0-27.306667-10.24-29.013334-25.6z" fill="#333333" ></path></symbol><symbol id="icon-guaduan" viewBox="0 0 1024 1024"><path d="M934.528 554.325333c-14.506667 54.229333-51.925333 69.674667-113.493333 60.970667-72.362667-10.24-133.589333-29.653333-129.109334-92.16 0 0 5.717333-67.285333-170.325333-72.405333-205.44-5.290667-196.693333 74.410667-196.693333 74.410666-0.085333 73.386667-50.773333 77.44-122.965334 88.192-72.234667 10.752-116.394667-7.168-125.269333-87.381333-17.152-154.624 265.386667-194.944 389.333333-196.48 0 0 189.013333-2.858667 279.594667 23.765333l48.768 13.866667c67.968 21.973333 128.981333 61.952 143.146667 118.826667 0 0 8.362667 25.898667-2.986667 68.394666z" fill="#595959" ></path></symbol></svg>',n=(t=document.getElementsByTagName("script"))[t.length-1].getAttribute("data-injectcss");if(n&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(c){console&&console.log(c)}}function d(){h||(h=!0,a())}i=function(){var c,t,i,o,a,l=document.createElement("div");l.innerHTML=s,s=null,(c=l.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",t=c,(i=document.body).firstChild?(o=t,(a=i.firstChild).parentNode.insertBefore(o,a)):i.appendChild(t))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(i,0):(o=function(){document.removeEventListener("DOMContentLoaded",o,!1),i()},document.addEventListener("DOMContentLoaded",o,!1)):document.attachEvent&&(a=i,l=c.document,h=!1,(e=function(){try{l.documentElement.doScroll("left")}catch(c){return void setTimeout(e,50)}d()})(),l.onreadystatechange=function(){"complete"==l.readyState&&(l.onreadystatechange=null,d())})}(window);
================================================
FILE: src/assets/fonts/iconfont.json
================================================
{
"id": "1698562",
"name": "vue-chat",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "5100253",
"name": "视频",
"font_class": "shipin",
"unicode": "e669",
"unicode_decimal": 58985
},
{
"icon_id": "4425816",
"name": "删除-方框",
"font_class": "shanchu-fangkuang",
"unicode": "e791",
"unicode_decimal": 59281
},
{
"icon_id": "4770755",
"name": "增加数字",
"font_class": "zengjiashuzi",
"unicode": "e641",
"unicode_decimal": 58945
},
{
"icon_id": "6129045",
"name": "9减、删除-线性方框",
"font_class": "jianshanchu-xianxingfangkuang",
"unicode": "e75a",
"unicode_decimal": 59226
},
{
"icon_id": "8358855",
"name": "增加4",
"font_class": "zengjia2",
"unicode": "e689",
"unicode_decimal": 59017
},
{
"icon_id": "5253606",
"name": "移除",
"font_class": "yichu",
"unicode": "e656",
"unicode_decimal": 58966
},
{
"icon_id": "1301395",
"name": "增加",
"font_class": "zengjia",
"unicode": "e668",
"unicode_decimal": 58984
},
{
"icon_id": "1272671",
"name": "移除",
"font_class": "delete-br",
"unicode": "e63d",
"unicode_decimal": 58941
},
{
"icon_id": "4581221",
"name": "发送中",
"font_class": "loading-solid",
"unicode": "e647",
"unicode_decimal": 58951
},
{
"icon_id": "3398713",
"name": "发送失败",
"font_class": "fasongshibai",
"unicode": "e62c",
"unicode_decimal": 58924
},
{
"icon_id": "6659620",
"name": "朋友",
"font_class": "pengyou1",
"unicode": "e631",
"unicode_decimal": 58929
},
{
"icon_id": "3315144",
"name": "邀请好友",
"font_class": "yaoqinghaoyou",
"unicode": "e61c",
"unicode_decimal": 58908
},
{
"icon_id": "6683016",
"name": "加好友",
"font_class": "jiahaoyou",
"unicode": "e603",
"unicode_decimal": 58883
},
{
"icon_id": "795513",
"name": "视频",
"font_class": "ai-video",
"unicode": "e66b",
"unicode_decimal": 58987
},
{
"icon_id": "842937",
"name": "表情",
"font_class": "biaoqing",
"unicode": "e666",
"unicode_decimal": 58982
},
{
"icon_id": "2078817",
"name": "dkw_消息 ",
"font_class": "dkw_xiaoxi",
"unicode": "e606",
"unicode_decimal": 58886
},
{
"icon_id": "2967254",
"name": "符号-电话",
"font_class": "dianhua",
"unicode": "e729",
"unicode_decimal": 59177
},
{
"icon_id": "4166140",
"name": "朋友",
"font_class": "pengyou",
"unicode": "e61a",
"unicode_decimal": 58906
},
{
"icon_id": "5582330",
"name": "搜索",
"font_class": "sousuo",
"unicode": "e659",
"unicode_decimal": 58969
},
{
"icon_id": "5893499",
"name": "退出",
"font_class": "tuichu",
"unicode": "e61f",
"unicode_decimal": 58911
},
{
"icon_id": "7588121",
"name": "图片",
"font_class": "tupian",
"unicode": "e623",
"unicode_decimal": 58915
},
{
"icon_id": "10099699",
"name": "文件",
"font_class": "wenjian",
"unicode": "e61b",
"unicode_decimal": 58907
},
{
"icon_id": "10515201",
"name": "挂断",
"font_class": "guaduan",
"unicode": "e640",
"unicode_decimal": 58944
}
]
}
================================================
FILE: src/components/chatlist/chatlist.vue
================================================
<!-- 聊天列表 -->
<template>
<div class="conversationlist" :style="{height: (appHeight-60) + 'px'}">
<ul v-loading="isEmptyConversation" style="min-height: 60px">
<li v-bind:key = index v-for="(item,index) in searchedConversationList" class="sessionlist" :class="{ active: item.conversationInfo.target === selectTarget }" @click="selectConversation(item.conversationInfo.target)">
<div class="list-left">
<img class="avatar" width="42" height="42" alt="static/images/vue.jpg" :src="item.img" onerror="this.src='static/images/vue.jpg'">
</div>
<div class="list-right">
<div class="title-info">
<p class="name">{{item.name ? item.name : ""}}</p>
<span class="time">{{item.conversationInfo.timestamp | getTimeStringAutoShort2}}</span>
</div>
<div class="lastmsg-info">
<p class="lastmsg">{{processageGroupMessage(item)}}</p>
<span v-if="item.conversationInfo.unreadCount && item.conversationInfo.unreadCount.unread > 0" class="unread-num">
<span class="unread-num-show">{{item.conversationInfo.unreadCount ? item.conversationInfo.unreadCount.unread : 0}}</span>
</span>
</div>
</div>
</li>
</ul>
</div>
</template>
<script>
import { mapState, mapActions ,mapGetters } from 'vuex'
import ConversationType from '../../websocket/model/conversationType';
import LocalStore from '../../websocket/store/localstore';
import TimeUtils from '../../websocket/utils/timeUtils';
import MessageConfig from '../../websocket/message/messageConfig';
import NotificationMessageContent from '../../websocket/message/notification/notificationMessageContent';
import Logger from '../../websocket/utils/logger';
import webSocketClient from '../../websocket/websocketcli';
export default {
data(){
return {
loading: true
}
},
computed: {
...mapState([
'selectId',
'selectTarget',
'searchText',
'appHeight',
'userInfoList',
'emptyMessage'
]),
...mapGetters([
'searchedConversationList'
]),
isEmptyConversation(){
return this.searchedConversationList.length == 0 && !this.emptyMessage;
}
},
methods: {
...mapActions([
'selectSession',
'selectConversation',
]),
processageGroupMessage(item){
var protoConversationInfo = item.conversationInfo;
var displayContent;
if(protoConversationInfo.lastMessage){
var messageContent = MessageConfig.convert2MessageContent(protoConversationInfo.lastMessage.from,protoConversationInfo.lastMessage.content);
if(messageContent && messageContent instanceof NotificationMessageContent){
displayContent = messageContent.formatNotification();
} else {
displayContent = protoConversationInfo.lastMessage.content.searchableContent;
if(protoConversationInfo.lastMessage.content.type === 400){
displayContent = '[网络电话]';
}
var isCurrentUser = protoConversationInfo.lastMessage.from === LocalStore.getUserId();
if(protoConversationInfo.conversationType == ConversationType.Group && !isCurrentUser){
var from = protoConversationInfo.lastMessage.from;
var displayName = this.getDisplayName(from);
displayContent = displayName +":"+protoConversationInfo.lastMessage.content.searchableContent;
}
}
}
return displayContent;
},
getDisplayName(from){
var displayName = from;
displayName = webSocketClient.getDisplayName(from);
return displayName;
}
},
filters: {
// 将日期过滤为 hour:minutes
time (date) {
if (typeof date === 'string') {
date = new Date(date);
}
if( typeof date === 'number'){
date = new Date(date);
}
if(date.getMinutes()<10){
return date.getHours() + ':0' +date.getMinutes();
}else{
return date.getHours() + ':' + date.getMinutes();
}
},
getTimeStringAutoShort2(timestamp){
return TimeUtils.getTimeStringAutoShort2(timestamp,false);
}
},
}
</script>
<style lang="stylus" scoped>
.conversationlist
height: 87%
overflow-y: auto
overflow-x: hidden
box-sizing: border-box
border-top: 1px solid #e7e7e7
border-right: 1px solid #e7e7e7
background: #f2f2f2
.sessionlist
display: flex
padding: 12px
transition: background-color .1s
font-size: 0
&:hover
background-color: rgb(220,220,220)
&.active
background-color: #c4c4c4
.avatar
border-radius: 2px
margin-right: 12px
.list-right
position: relative
flex: 1
margin-top: 4px
.title-info
display: flex
.name
flex: 1 1 auto
display: inline-block
width: 130px
height: 15px
line-height: 15px
font-size: 14px
overflow: hidden
white-space:nowrap
text-overflow:ellipsis
.time
flex: 0 0 auto
float: right
line-height: 15px
color: #999
font-size: 10px
vertical-align: center
.lastmsg-info
display: flex
margin-top: 8px
.lastmsg
flex: 1 1 auto
font-size: 12px
width: 130px
height: 15px
line-height: 15px
color: #999
bottom: 4px
overflow: hidden
white-space:nowrap
text-overflow:ellipsis
.unread-num
flex: 0 0 auto
vertical-align:bottom
margin-top: 0px
display: inline-block;
min-width: 16px;
height: 16px;
background-color: red;
border-radius: 8px;
text-align: center;
font-size: 12px;
color: #fff;
line-height: 16px;
.unread-num-show
text-align: center;
font-size:10px;
-webkit-transform:scale(0.8);
display:block;
</style>
================================================
FILE: src/components/friendlist/friendlist.vue
================================================
<!-- 好友列表 -->
<template>
<div class="friendlist" :style="{height: (appHeight-60) + 'px'}">
<ul>
<li v-bind:key = index v-for="(item, index) in searchedFriendlist" class="frienditem" :class="{ noborder: !item.initial}">
<div class="list_title" v-if="item.initial">{{item.initial}}</div>
<div class="friend-info" :class="{ active: item.id === selectFriendId }" @click="selectFriend(item.id)">
<div class="friend-item">
<span v-if="newFriendRequestCount > 0 && item.id === 0" class="unread-friend-request-num">
<span class="unread-num-show">{{newFriendRequestCount}}</span>
</span>
<img class="avatar" width="36" height="36" :src="item.img" onerror="this.src='static/images/vue.jpg'">
</div>
<div class="remark">{{item.remark}}</div>
</div>
</li>
</ul>
</div>
</template>
<script>
import { mapState, mapActions ,mapGetters } from 'vuex'
export default {
computed: {
...mapState([
'selectFriendId',
'searchText',
'appHeight',
'newFriendRequestCount'
]),
...mapGetters([
'searchedFriendlist',
])
},
methods: {
...mapActions([
'selectFriend',
])
}
}
</script>
<style lang="stylus" scoped>
.friendlist
height: 87%
overflow-y: auto
border-top: 1px solid #e7e7e7
border-right: 1px solid #e7e7e7
background: #f2f2f2
.frienditem
border-top: 1px solid #dadada
&:first-child,&.noborder
border-top: none
.list_title
box-sizing: border-box
width: 100%
font-size: 12px
padding: 15px 0 3px 12px
color: #999
.friend-info
display: flex
padding: 12px
transition: background-color .1s
font-size: 0
&:hover
background-color: rgb(220,220,220)
&.active
background-color: #c4c4c4
.avatar
border-radius: 2px
margin-right: 12px
.remark
font-size: 14px
line-height: 36px
.friend-item
position:relative
.unread-friend-request-num
display: inline-block
min-width: 16px
height: 16px
background-color: red
border-radius: 8px
text-align: center
font-size: 12px
color: #fff
line-height: 16px
position:absolute
top: -5px
right: 5px
z-index: 10
.unread-num-show
font-size:10px;
-webkit-transform:scale(0.8);
display:block;
</style>
================================================
FILE: src/components/info/info.vue
================================================
<!-- 好友信息 -->
<template>
<div class="Info-wrapper">
<div class="newfriend">
<div class="nickname">{{selectedFriend.nickname}}</div>
</div>
<div class="friendrequest" v-show="selectedFriend.id === 0" :style="{height: (appHeight -60) + 'px'}">
<el-table
:data="friendRequests"
:show-header="false"
:max-height="appHeight - 61"
style="width: 100%;height:100%;">
<el-table-column
prop="image"
label="头像"
width="50">
<template slot-scope="scope">
<el-avatar :src="friendPortrait(scope.row.from)"></el-avatar>
</template>
</el-table-column>
<el-table-column
prop="name"
label="姓名"
width="280">
<template slot-scope="scope">
<div>
<p>{{friendName(scope.row.from)}}</p>
<p>{{scope.row.reason}}</p>
</div>
</template>
</el-table-column>
<el-table-column
label="加好友"
align="right"
>
<template slot-scope="scope">
<!-- <el-button
size="mini"
type="info"
@click="handleEdit(scope.$index, scope.row)">拒绝</el-button> -->
<el-button
size="mini"
type="success"
:disabled="scope.row.status === 1"
@click="handleFriendRequest(scope.row)">{{requestBtnMessage(scope.row)}}</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="friendInfo" v-show="selectedFriend.id > 0">
<div class="esInfo" >
<div class="left">
<div class="people">
<div class="nickname">{{selectedFriend.nickname}}</div>
<div :class="[selectedFriend.sex===1?'gender-male':'gender-female']"></div>
</div>
<div class="signature">{{selectedFriend.signature}}</div>
</div>
<div class="right">
<img class="avatar" width="60" height="60" :src="selectedFriend.img">
</div>
</div>
<div class="detInfo">
<div class="remark">
<label class="title" for="">备注:</label>
<p class="value" contenteditable="true" @keydown.enter="modifyRemark" @blur="modifyRemarkBlur">{{selectedFriend.remark}}</p>
</div>
<div class="area"><span>地   区</span>{{selectedFriend.area}}</div>
<div class="wxid"><span>微信号</span>{{selectedFriend.wxid}}</div>
</div>
<div class="send" @click="send">
<span>发消息</span>
</div>
</div>
</div>
</template>
<script>
import router from '../../router'
import { mapGetters,mapState } from 'vuex'
import LocalStore from '../../websocket/store/localstore'
import webSocketCli from '../../websocket/websocketcli'
import { SUCCESS_CODE } from '../../constant'
export default {
computed: {
...mapGetters([
'selectedFriend'
]),
...mapState([
'friendRequests',
'appHeight',
'userInfoList'
]),
// friendRemark:{
// get(){
// console.log("remark test")
// return "test"
// //return this.selectedFriend.remark
// },
// set(value){
// console.log("remark vaule "+value)
// // this.selectedFriend.remark = value;
// }
// }
},
methods: {
send () {
this.$store.dispatch('send')
this.$store.dispatch('search', '')
},
requestBtnMessage(request){
if(request.status === 1){
return "已添加";
} else if(request.status === 2){
return "已拒绝"
} else if(request.status === 0){
return "接受"
}
},
handleFriendRequest(request){
this.$store.dispatch('handleFriendRequest', {
targetUid: request.from,
status: 1
})
},
friendName(target){
var user = this.userInfoList.find(user => user.uid == target);
var displayName = target;
if(user){
displayName = user.displayName;
if(!displayName){
displayName = user.mobile;
}
}
return displayName;
},
friendPortrait(target){
var user = this.userInfoList.find(user => user.uid == target);
var portrait;
if(user){
portrait = user.portrait;
}
if(!portrait){
portrait = "static/images/vue.jpg";
}
return portrait;
},
modifyRemark(e){
if(e.keyCode == 13){
e.preventDefault();
this.modifyRemarkBlur(e)
}
},
modifyRemarkBlur(e){
var remark = e.target.innerText;
if(remark == this.selectedFriend.remark){
return
}
this.selectedFriend.remark = remark;
if(remark != '' && remark.length < 15){
webSocketCli.modifyFriendAlias(this.selectedFriend.wxid,remark).then(data =>{
if(data.code == SUCCESS_CODE){
this.$message.success("修改好友备注成功");
} else {
this.$message.error("修改好友备注失败");
}
})
} else {
this.$message.error("备注过长最好不要超过15个字符");
}
}
}
}
</script>
<style lang="stylus" scoped>
.newfriend
height: 60px
padding: 28px 0 0 30px
box-sizing: border-box
.nickname
font-size: 18px
.friendrequest
border-top: 1px solid #e7e7e7
.friendInfo
padding: 0 90px
border-top: 1px solid #e7e7e7
.esInfo
display: flex
align-items: center
padding: 100px 0 45px 0
.left
flex: 1
.people
.nickname
display: inline-block
font-size: 20px
margin-bottom: 16px
.gender-male,.gender-female
display: inline-block
width: 18px
height: 18px
vertical-align: top
margin-top: 2px
.gender-male
background-image: url(man.png)
background-size: cover
.gender-female
background-image: url(woman.png)
background-size: cover
.signature
font-size: 14px
color: rgba(153,153,153,.8)
.right
.avatar
border-radius: 3px
.detInfo
padding: 40px 0
border-top: 1px solid #e7e7e7
border-bottom: 1px solid #e7e7e7
.remark,.area,.wxid
font-size: 14px
margin-top: 20px
span
font-size: 14px
color: rgba(153,153,153,.8)
margin-right: 40px
.remark
.title
float: left;
font-size: 14px;
line-height: 1.6
color: rgba(153,153,153,.8);
margin-right: 10px;
.value
font-size: 14px;
width: 200px;
line-height: 1.6
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-wrap: normal;
padding-left: 5px;
padding-right: 5px;
margin-top: 0
.send
position: relative
text-align: center
width: 140px
height: 36px
left: 115px
top: 50px
line-height: 36px
font-size: 14px
color: #fff
background-color: #1aad19
cursor: pointer
border-radius: 2px
&:hover
background: rgb(18,150,17)
</style>
================================================
FILE: src/components/menu/addtip.vue
================================================
<template>
<div class="add-content">
<div>
<a @click="showSearchFriendDialog" class="iconsize wx-chat-icon"><i class="iconfont icon-jiahaoyou iconhover"></i> 添加好友 </a>
</div>
<div>
<a @click="showCreateGroupDialog" class="iconsize wx-chat-icon"><i class="iconfont icon-pengyou iconhover"></i> 创建群聊 </a>
</div>
</div>
</template>
<script>
import Logger from '../../websocket/utils/logger';
export default {
name: 'addtip',
methods: {
showSearchFriendDialog(){
this.$store.state.showSearchFriendDialog = true;
},
showCreateGroupDialog(){
Logger.log("show create group dialog");
this.$store.state.groupOperateState = 0;
this.$store.state.showCreateGroupDialog = true;
}
},
}
</script>
<style scoped>
.add-content {
position: absolute;
background: #fff;
width: 112px;
text-align: center;
top: 200px;
left: 60px;
z-index: 20;
padding: 8px 0;
box-shadow: 0 2px 6px 0 rgba(0,0,0,0.2);
border-radius: 4px
}
.add-content a {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
position: relative;
cursor: pointer;
font-size: 14px;
color: #333
}
.add-content a .iconfont {
font-size: 16px;
color: #333;
margin-right: 8px
}
.add-content a:hover {
color: rgb(0,220,65)
}
.add-content a:hover i {
color: rgb(0,220,65)
}
.iconhover:hover {
color: rgb(0,220,65)
}
.iconhover.on:hover {
color: rgb(0,220,65)
}
</style>
================================================
FILE: src/components/menu/groupInfo.vue
================================================
<template>
<div id="group-info-id" class="group-content" :style="{height: (appHeight-61) + 'px'}">
<div class="flex-layout">
<div class="group-info-title" v-if="!isSingleConversation">
<div class="group-name-info">
<div class="group-name-title">群名</div>
<p class="group-name" contenteditable="true" @blur="modifyGroupNameBlur" @keydown.enter="modifyGroupName">{{groupName}}</p>
</div>
<div class="group-board-info">
<div class="group-board-title">群公告</div>
<div class="group-board-content">暂无公告</div>
</div>
</div>
<div class="friend-info-list" :style="{height: (appHeight-221) + 'px'}">
<ul>
<li v-bind:key = index v-for="(item, index) in memberList" class="frienditem">
<div class="friend-info">
<i title="个人用户添加成员" class="icon iconfont icon-zengjia add" v-if="item.type == 1002" @click="addGroupMemberFromSingle"></i>
<i title="添加成员" class="icon iconfont icon-zengjia add" v-if="item.type == 1000" @click="addGroupMember"></i>
<i title="移除成员" class="icon iconfont icon-shanchu-fangkuang add" v-if="item.type == 1001" @click="kickGroupMember"></i>
<img class="avatar" :src="item.avatarUrl" onerror="this.src='static/images/vue.jpg'" v-if="item.type == 0 || item.type ==2">
<p class="nickName">{{item.displayName}}</p>
</div>
</li>
</ul>
</div>
<div class="flex-bottom" v-if="!isSingleConversation">
<p class="quit-group" v-if="!isGroupOwner" @click="quitGroup">退出群聊</p>
<p class="dismiss-group" v-if="isGroupOwner" @click="dismissGroup">解散群聊</p>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapActions ,mapGetters } from 'vuex'
import LocalStore from '../../websocket/store/localstore';
//注意websocketClient 不是常量引入
import webSocketClient from '../../websocket/websocketcli';
import { SUCCESS_CODE } from '../../constant';
import GroupMember from '../../websocket/model/groupMember';
import ModifyGroupInfoType from '../../websocket/message/modifyGroupInfoType'
import Logger from '../../websocket/utils/logger';
export default {
name: 'groupInfoMenu',
props: ['targetId'],
data(){
return {
isGroupOwner: false,
groupMembers: [],
addGroupMemberType: 1000,
deleteGroupMemberType: 1001,
addGroupMemberSingleType: 1002,
}
},
mounted() {
console.log("targetId "+this.targetId)
document.addEventListener("click", e => {
var isString = typeof(e.target.className) == 'string'
let groupInfoDom = document.getElementById("group-info-id");
// console.log("isString "+isString +" show "+e.target.className.search('show-group-info'))
//注意点击显示按钮事件的处理,防止状态发生反转
if (isString && e.target.className.search('show-group-info') == -1 && groupInfoDom && !groupInfoDom.contains(e.target) && this.$store.state.showGroupInfo) {
this.$store.state.showGroupInfo = false;
}
});
},
destroyed() {
this.groupMembers = []
},
computed: {
...mapState([
'appHeight',
'groupInfoList',
'userInfoList',
'groupMemberMap'
]),
...mapGetters([
'isSingleConversation'
]),
memberList(){
var groupMembers = [];
//add member item
if(!this.isSingleConversation){
var addGroupMember = new GroupMember()
addGroupMember.type = this.addGroupMemberType;
addGroupMember.displayName = '添加成员';
groupMembers.push(addGroupMember);
//delete member
var deleteGroupMember = new GroupMember()
deleteGroupMember.type = this.deleteGroupMemberType;
deleteGroupMember.displayName = '移除成员';
groupMembers.push(deleteGroupMember);
if(this.groupMemberMap.get(this.targetId)){
for(var groupMember of this.groupMemberMap.get(this.targetId)){
if(groupMember.memberId == LocalStore.getUserId()){
this.isGroupOwner = groupMember.type == 2 ? true : false;
}
groupMember.displayName = webSocketClient.getDisplayName(groupMember.memberId);
groupMember.avatarUrl = webSocketClient.getPortrait(groupMember.memberId);
groupMembers.push(groupMember);
}
}
if(!this.isGroupOwner){
groupMembers.splice(1,1);
}
} else {
var addGroupMember = new GroupMember()
addGroupMember.type = this.addGroupMemberSingleType;
addGroupMember.displayName = '添加成员';
groupMembers.push(addGroupMember);
var currentUser = new GroupMember();
currentUser.displayName = webSocketClient.getDisplayName(LocalStore.getUserId());
currentUser.avatarUrl = webSocketClient.getPortrait(LocalStore.getUserId());
currentUser.memberId = LocalStore.getUserId();
groupMembers.push(currentUser)
var targetUser = new GroupMember();
targetUser.displayName = webSocketClient.getDisplayName(this.targetId);
targetUser.avatarUrl = webSocketClient.getPortrait(this.targetId);
targetUser.memberId = this.targetId;
groupMembers.push(targetUser);
}
return groupMembers;
},
groupName: {
get() {
var groupName = "";
var groupInfo = this.groupInfoList.find(groupInfo => groupInfo.target == this.targetId);
if(groupInfo){
groupName = groupInfo.name;
}
return groupName;
},
set(value){
var groupInfo = this.groupInfoList.find(groupInfo => groupInfo.target == this.targetId);
if(groupInfo){
groupInfo.name = value;
}
}
}
},
methods: {
quitGroup(){
this.$confirm('你将退出此群组, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
webSocketClient.quitGroup(this.targetId).then(data => {
if(data.code == SUCCESS_CODE){
this.$message({
type: 'success',
message: '退出群组成功!'
});
this.$store.state.showGroupInfo = false;
setTimeout(() => this.$store.dispatch('deleteConversation',this.targetId), 500)
}
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消退出群组'
});
});
},
dismissGroup(){
this.$confirm('此操作将永久解散群组, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
webSocketClient.dismissGroup(this.targetId).then(data => {
if(data.code == SUCCESS_CODE){
this.$message({
type: 'success',
message: '解散群组成功!'
});
this.$store.state.showGroupInfo = false;
setTimeout(() => this.$store.dispatch('deleteConversation',this.targetId), 500)
}
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消解散群组'
});
});
},
addGroupMemberFromSingle(){
this.$store.state.groupOperateState = 3;
//触发groupMap以是vue相应变更
this.$store.state.groupMemberTracker += 1;
this.$store.state.showCreateGroupDialog = true;
},
addGroupMember(){
this.$store.state.groupOperateState = 1;
//触发groupMap以是vue相应变更
this.$store.state.groupMemberTracker += 1;
this.$store.state.showCreateGroupDialog = true;
},
kickGroupMember(){
this.$store.state.groupOperateState = 2;
//触发groupMap以是vue相应变更
this.$store.state.groupMemberTracker += 1;
this.$store.state.showCreateGroupDialog = true;
},
modifyGroupNameBlur(e){
var inputName = e.target.innerText;
if(this.groupName === inputName){
return
}
this.groupName = inputName;
if(this.groupName && this.groupName.length < 15){
webSocketClient.modifyGroupInfo({
groupId: this.targetId,
type: ModifyGroupInfoType.Modify_Group_Name,
value: this.groupName
});
} else {
this.$message.error("群组名称过长最好不要超过15个字符");
}
},
modifyGroupName(e){
if(e.keyCode == 13){
this.modifyGroupNameBlur(e);
e.preventDefault();
}
}
},
}
</script>
<style lang="stylus" scoped>
.group-content
position: absolute
background: #fff
height: 200px
width: 220px
top: 61px
right: 0px
z-index: 2000
border-left: 1px solid #e7e7e7
.flex-layout
width: 100%
height: 100%
display: flex
flex-flow: row wrap
.group-info-title
margin-left: 10px
width: 100%
height: 120px
padding 10px 5px 10px 5px
border-bottom: 1px solid #e7e7e7
.group-name-info
width: 100%
height: 50%
margin-bottom: 5px
.group-name-title
font-size: 13px
line-height: 1.6
color: #888
.group-name
font-size: 14px
line-height: 1.6
width: 200px
overflow: hidden
white-space: nowrap
text-overflow: ellipsis
.group-board-info
width: 100%
height: 50%
.group-board-title
font-size: 13px
line-height: 1.6
color: #888
.group-board-content
font-size: 14px
line-height: 1.6
.friend-info-list
width: 100%
margin-left: 10px
overflow-y: auto
.frienditem
padding: 5px 0px 5px 0px
.friend-info
display: flex
padding: 5px
height: 40px
.avatar
border-radius: 2px
margin-right: 12px
width: 32px
height: 32px
.add
margin-right: 12px
font-size: 32px
cursor: pointer
.nickName
flex: 1 1 auto
width: 106px
font-size: 14px
line-height: 32px
overflow: hidden
white-space: nowrap
text-overflow: ellipsis
color: black
.flex-bottom
width: 100%
height: 40px
align-self: flex-end
display: flex
align-items: center
padding: 15px 0px 15px 0px
border-top: 1px solid #e7e7e7
.quit-group
width: 100%
color: rgb(0,220,65);
font-size: 16px
cursor: pointer
text-align: center
.dismiss-group
width: 100%
color: red
font-size: 16px
cursor: pointer
text-align: center
</style>
================================================
FILE: src/components/menu/personalCard.vue
================================================
<template>
<div class="personal-card" id="personal-card">
<div class="profile_mini">
<div class="profile_mini_hd">
<div class="avatar">
<el-tooltip class="item" effect="dark" content="点击更换头像" placement="top">
<div>
<a href="javascript:void(0)" id="add" @click="openfile">
<img class="img" :src="user.img" alt="点击更换头像">
</a>
<input type="file" accept="image/*" style="display:none" ref="changeAvatar" @change="changeAvatar"/>
</div>
</el-tooltip>
</div>
</div>
<div class="profile_mini_bd">
<div class="nickname_area">
<a class="opt">
<i class="web_wechat_tab_launch-chat"></i>
</a>
<el-tooltip class="item" effect="dark" content="点击修改昵称,最长10个字符" placement="top-start">
<div class="nickname_item">
<p class="nickname" contenteditable="true" @keydown.enter="modifyNickName" @blur="modifyNickNameBlur">{{displayName}}</p>
<i class="web_wechat_men" v-show="false"></i>
</div>
</el-tooltip>
</div>
<div class="meta_area">
<div class="meta_item">
<label class="label" for="">备注:</label>
<p class="value" contenteditable="true" @keydown.enter="modifyExtra">{{extra}}</p>
</div>
<div class="meta_item">
<label class="label">手机号:</label>
<p class="value">{{mobile}}</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Logger from '../../websocket/utils/logger';
import MyInfoType from '../../websocket/message/myInfoType'
import MessageContentMediaType from '../../websocket/message/messageContentMediaType';
import LocalStore from '../../websocket/store/localstore';
import * as qiniu from 'qiniu-js'
import { mapGetters, mapState } from 'vuex'
import { UPLOAD_BY_QINIU,SUCCESS_CODE } from '../../constant';
import webSocketCli from '../../websocket/websocketcli'
export default {
name: 'persionCard',
props: ['userId'],
data() {
return {
extra: '备注无',
address: '',
}
},
methods: {
modifyNickName(e){
if(e.keyCode ==13){
this.modifyNickNameBlur(e);
e.preventDefault();
}
},
modifyNickNameBlur(e){
Logger.log("modify displayName "+e.target.innerText);
var inputName = e.target.innerText;
this.displayName = inputName;
if(inputName.length < 10){
this.$store.dispatch("modifyMyInfo",{
type: MyInfoType.Modify_DisplayName,
value: inputName
});
}
},
modifyExtra(e){
if(e.keyCode ==13){
e.preventDefault();
}
},
openfile(){
this.$refs.changeAvatar.click();
},
changeAvatar(e){
var _this = this;
var store = this.$store;
if(UPLOAD_BY_QINIU){
store.dispatch('getUploadToken', MessageContentMediaType.Image);
console.log("changeAvatar "+e.target.value);
var file = e.target.files[0];
var key = MessageContentMediaType.Image +"-"+LocalStore.getUserId()+"-"+new Date().getTime()+"-"+file.name;
setTimeout(()=> {
var token = LocalStore.getImageUploadToken();
console.log("upload avatar key "+key+" token "+token);
if(token){
var observable = qiniu.upload(file, key, token, null, null);
var observer = {
next(res){
console.log('uploading '+res.total.percent);
},
error(err){
console.log("upload error "+err.code +" message "+err.message);
},
complete(res){
console.log("upload complete "+res);
var localPath = e.target.value;
var remotePath = "http://image.comsince.cn/"+key;
_this.$store.state.user.img = remotePath;
store.dispatch("modifyMyInfo",{
type: MyInfoType.Modify_Portrait,
value: remotePath
});
}
}
observable.subscribe(observer);
}
},200);
} else {
var file = e.target.files[0];
var key = MessageContentMediaType.Image +"-"+LocalStore.getUserId()+"-"+new Date().getTime()+"-"+file.name;
webSocketCli.getMinioUploadUrl(MessageContentMediaType.Image,key).then(data => {
if(data.code == SUCCESS_CODE){
console.log("domain "+data.result.domain+" url "+data.result.url)
fetch(data.result.url, {
method: 'PUT',
body: file
}).then(() => {
var remotePath = data.result.domain+"/"+key;
_this.$store.state.user.img = remotePath;
store.dispatch("modifyMyInfo",{
type: MyInfoType.Modify_Portrait,
value: remotePath
});
}).catch((e) => {
console.error(e);
});
}
})
}
this.$refs.changeAvatar.value = null;
}
},
computed: {
...mapState([
'user',
'userInfoList'
]),
personImg: {
get() {
var userInfo = this.userInfoList.find(user => user.uid == this.userId);
var portrait = '';
if(userInfo){
portrait = userInfo.portrait;
}
if(portrait === ''){
portrait = 'static/images/vue.jpg';
}
return portrait;
},
set(value) {
var userInfo = this.userInfoList.find(user => user.uid == this.userId);
if(userInfo){
userInfo.portrait = value;
}
}
},
displayName: {
get() {
var userInfo = this.userInfoList.find(user => user.uid == this.userId);
var displayName = '';
if(userInfo){
displayName = userInfo.displayName == '' ? userInfo.mobile : userInfo.displayName;
}
return displayName;
},
set(value) {
var userInfo = this.userInfoList.find(user => user.uid == this.userId);
if(userInfo){
userInfo.displayName = value;
}
}
},
mobile() {
var userInfo = this.userInfoList.find(user => user.uid == this.userId);
if(userInfo){
return userInfo.mobile;
}else {
return '';
}
}
}
}
</script>
<style scoped>
.personal-card {
position: absolute;
background: #fff;
width: 250px;
top: 20px;
left: 60px;
z-index: 20;
box-shadow: 0 2px 6px 0 rgba(0,0,0,0.2);
border-radius: 4px
}
.profile_mini_hd .avatar .img {
width: 250px;
height: 220px;
display: block;
border-top-left-radius:4px;
border-top-right-radius:4px;
cursor: pointer;
}
.profile_mini_bd {
padding: 20px;
min-height: 74px;
}
.profile_mini_bd .nickname_item {
overflow: hidden;
}
.profile_mini_bd .nickname_area {
margin-bottom: 8px;
line-height: 1.6;
}
.profile_mini_bd .opt {
float: right;
}
.web_wechat_tab_launch-chat {
display: inline-block;
vertical-align: middle;
width: 22px;
height: 22px;
background: url(https://res.wx.qq.com/a/wx_fed/webwx/res/static/css/5af37c4a880a95586cd41c5b251d5562@1x.png) no-repeat;
background-position: -223px -432px;
-webkit-background-size: 487px 462px;
background-size: 487px 462px;
}
.profile_mini_bd .nickname {
font-weight: 400;
font-size: 18px;
vertical-align: middle;
width: 115px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-wrap: normal;
line-height: 1.6;
margin-left: 5px;
}
.web_wechat_men {
display: inline-block;
vertical-align: middle;
}
.web_wechat_men {
width: 16px;
height: 16px;
background: url(https://res.wx.qq.com/a/wx_fed/webwx/res/static/css/5af37c4a880a95586cd41c5b251d5562@1x.png) no-repeat;
background-position: -384px -304px;
-webkit-background-size: 487px 462px;
background-size: 487px 462px;
}
.profile_mini_bd .meta_area {
line-height: 1.6;
margin-left: 5px;
}
.profile_mini_bd .meta_item {
overflow: hidden;
}
.profile_mini_bd .meta_item .label {
float: left;
font-size: 12px;
color: #888;
margin-right: 10px;
}
.profile_mini_bd .meta_item .value {
font-size: 12px;
color: #888;
width: 105px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-wrap: normal;
padding-left: 5px;
padding-right: 5px;
}
</style>
================================================
FILE: src/components/menu/relayMessage.vue
================================================
<template>
<div class="relay-message">
<el-dialog
title="转发"
:visible.sync="showRelayMessageDialog"
width="30%"
:show-close="true"
@close="handleClose"
center>
<el-dialog
width="30%"
title="转发"
:visible.sync="innerVisible"
append-to-body>
<div v-if="isTextMessage">
<el-input
type="textarea"
:rows="3"
v-model="waitRelayMessage"></el-input>
</div>
<div v-if="isImageMessage" style="text-align:center">
<img :src="waitRelayImageUrl" class="preview-image">
</div>
<div v-if="isVideoMessage && innerVisible" style="text-align:center">
<Xgplayer :config="videoConfig" @player="Player = $event"/>
</div>
<span slot="footer" class="dialog-footer">
<el-button size="medium" type="info" plain round @click="innerVisible=false">取 消</el-button>
<el-button size="medium" type="success" plain round @click="sendRelayMessage">确 定</el-button>
</span>
</el-dialog>
<el-input v-model="conversationInput" prefix-icon="el-icon-search" placeholder="请输入会话名称" @keydown.enter.native="searchConversation"></el-input>
<el-table
:data="conversationList"
:show-header="false"
:fit="true"
:highlight-current-row="false"
max-height="300"
@row-click="showRelayMessageRequestDialog"
style="width: 100%;margin-top:10px">
<el-table-column
prop="image"
label="会话头像"
width="50"
>
<template slot-scope="scope">
<el-avatar :src="scope.row.image"></el-avatar>
</template>
</el-table-column>
<el-table-column
prop="name"
label="会话名称">
<template slot-scope="scope">
<div>
<p class="conversation-name">{{scope.row.name}}</p>
</div>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script>
import { mapGetters, mapState } from 'vuex'
import MessageConfig from '../../websocket/message/messageConfig';
import TextMessageContent from '../../websocket/message/textMessageContent';
import ImageMessageContent from '../../websocket/message/imageMessageContent';
import SendMessage from '../../websocket/message/sendMessage'
import Xgplayer from 'xgplayer-vue';
import VideoMessageContent from '../../websocket/message/videoMessageContent';
export default {
components:{
Xgplayer
},
data() {
return {
conversationInput: '',
waitRelayMessage: '',
waitRelayImageUrl: '',
innerVisible: false,
waitSendTarget: null,
currentSendMessage: null,
videoConfig: null,
Player: null,
}
},
methods: {
changeRelayMessageDialog(){
this.$store.state.showRelayMessageDialog = false;
},
searchConversation(e){
console.log("search conversation ");
if(e.keyCode === 13 && this.conversationInput != ""){
//this.$store.dispatch('searchUser', this.conversationInput);
this.conversationInput = '';
}
},
handleClose(){
//this.$store.dispatch('updateSearchUser', []);
},
showRelayMessageRequestDialog(row, event, column){
var target = row.target;
this.innerVisible=true;
if(this.currentRightMenuMessage){
console.log("conent type "+this.currentRightMenuMessage.content.type)
var messageContent = MessageConfig.convert2MessageContent(this.currentRightMenuMessage.from,this.currentRightMenuMessage.content)
if(messageContent instanceof TextMessageContent){
this.waitRelayMessage = messageContent.digest();
} else if(messageContent instanceof ImageMessageContent){
this.waitRelayImageUrl = messageContent.remotePath;
} else if(messageContent instanceof VideoMessageContent){
var posterBase64 = "data:image/png;base64,"+messageContent.thumbnail;
this.videoConfig = {
id: 'vs'+new Date().getTime(),
// url 为空,可能导致不显示,这里强制写入poster
url: messageContent.remotePath == ''? posterBase64: messageContent.remotePath,
// height: 330,
// width: 250,
fluid: true,
// fitVideoSize: 'fixHeight',
poster: posterBase64,
autoplay: false,
download: true
}
}
this.currentSendMessage = new SendMessage(target,messageContent);
}
},
sendRelayMessage(){
this.innerVisible=false;
this.$store.dispatch('sendMessage', this.currentSendMessage)
},
},
computed: {
...mapState([
'currentRightMenuMessage'
]),
...mapGetters([
'searchedConversationList',
]),
showRelayMessageDialog : {
get () {
return this.$store.state.showRelayMessageDialog;
},
set(val) {
this.$store.state.showRelayMessageDialog = val;
}
},
conversationList(){
var conversationList = [];
if(this.searchedConversationList){
for(var conversationInfo of this.searchedConversationList){
conversationList.push({
image : conversationInfo.img,
name : conversationInfo.name,
target : conversationInfo.conversationInfo.target
})
}
}
return conversationList;
},
isTextMessage(){
var flag = false
if(this.currentRightMenuMessage){
flag = this.currentRightMenuMessage.content.type == 1
}
console.log("flag "+flag)
return flag
},
isImageMessage(){
var flag = false
if(this.currentRightMenuMessage){
flag = this.currentRightMenuMessage.content.type == 3
}
console.log("flag "+flag)
return flag
},
isVideoMessage(){
var flag = false
if(this.currentRightMenuMessage){
flag = this.currentRightMenuMessage.content.type == 6
}
console.log("flag "+flag)
return flag
}
}
}
</script>
<style scoped>
.preview-image{
max-width : 115px;
max-height : 330px;
text-align: center;
border-radius: 3px
}
.relay-message .icon {
display: inline-block;
font-size: 26px;
color: rgb(0,220,65);
}
.conversation-name {
line-height: 15px;
font-size: 14px;
width: 300px;
overflow: hidden;
white-space:nowrap;
text-overflow:ellipsis;
}
.layout-container {
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column
}
.el-dialog__wrapper {
display: flex;
align-items: center;
justify-content: center;
}
</style>
================================================
FILE: src/components/menu/rightMenu.vue
================================================
<template>
<div v-bind:class="menuStyle" @contextmenu.prevent="">
<div @click="recallMessage" v-if="isFrom">
<a> 撤回消息 </a>
</div>
<div @click="deleteMessage">
<a> 删除消息 </a>
</div>
<div @click="relayMessage">
<a> 转发 </a>
</div>
</div>
</template>
<script>
import { mapGetters, mapState } from 'vuex'
import webSocketClient from '../../websocket/websocketcli';
import { SUCCESS_CODE } from '../../constant';
import RecallMessageNotification from '../../websocket/message/notification/recallMessageNotification';
import LocalStore from '../../websocket/store/localstore';
export default {
name: 'rightmenu',
props:['message'],
computed:{
...mapState([
'showMessageRightMenu',
]),
menuStyle(){
return {
"right-menu-content": this.message.direction == 0,
"right-menu-content-left": this.message.direction != 0
}
},
isFrom(){
// return this.message.direction == 0
return true
}
},
mounted() {
document.addEventListener("click", e => {
var isString = typeof(e.target.className) == 'string'
let rightMenuDom = document.getElementById("right-menu-content");
//注意点击显示按钮事件的处理,防止状态发生反转
var menuSetting = this.showMessageRightMenu.find(setting => setting.messageId == this.message.messageId)
var show = menuSetting ? menuSetting.show : false
// console.log("isString "+isString +" show " +e.target.className.search('right-menu-content')+" show "+show+" contain "+rightMenuDom)
if (isString && e.target.className.search('right-menu-content') == -1 && show) {
this.noShowMenu();
}
});
},
methods:{
recallMessage(){
this.$store.state.currentRightMenuMessage = this.message
webSocketClient.recallMessage(this.message.messageUid).then(data => {
if(data.code == SUCCESS_CODE){
if(data.result == 200){
var recallMessageContent = new RecallMessageNotification(LocalStore.getUserId(),this.message.messageUid);
this.message.content = recallMessageContent.encode();
console.log("recall message "+this.message.messageId);
} else {
this.$message.error('非管理员只能撤回自己发送的消息');
}
}
});
this.noShowMenu();
},
deleteMessage(){
this.$store.state.currentRightMenuMessage = this.message
console.log("delete message id "+this.message.messageId)
this.$store.dispatch('deleteMessage',this.message.messageId)
this.noShowMenu();
},
relayMessage(){
this.$store.state.currentRightMenuMessage = this.message
this.$store.state.showRelayMessageDialog = true;
this.noShowMenu();
},
noShowMenu(){
var menuSetting = this.showMessageRightMenu.find(setting => setting.messageId == this.message.messageId)
if(menuSetting){
menuSetting.show = false;
}
}
}
}
</script>
<style scoped>
div {
-o-user-select: none;
-moz-user-select: none; /*火狐 firefox*/
-webkit-user-select: none; /*webkit浏览器*/
-ms-user-select: none; /*IE10+*/
-khtml-user-select :none; /*早期的浏览器*/
user-select: none;
}
.right-menu-content {
position: absolute;
background: #fff;
width: 80px;
text-align: center;
right: 0px;
bottom: 40px;
z-index: 2000;
padding: 2px 0;
box-shadow: 0 2px 6px 0 rgba(0,0,0,0.2);
border-radius: 4px
}
.right-menu-content-left {
position: absolute;
background: #fff;
width: 80px;
text-align: center;
left: 0px;
bottom: 40px;
z-index: 2000;
padding: 2px 0;
box-shadow: 0 2px 6px 0 rgba(0,0,0,0.2);
border-radius: 4px
}
div a {
display: flex;
align-items: center;
justify-content: center;
height: 30px;
position: relative;
cursor: pointer;
font-size: 12px;
color: #333
}
div a:hover {
color: rgb(0,220,65)
}
div a:hover i {
color: rgb(0,220,65)
}
.iconhover:hover {
color: rgb(0,220,65)
}
.iconhover.on:hover {
color: rgb(0,220,65)
}
</style>
================================================
FILE: src/components/message/message.vue
================================================
<!-- 消息框 -->
<template>
<div class="message">
<header class="header">
<div class="friendname">{{selectedChat.name}}</div>
<div class="friend-group-info">
<i title="用户信息" class="icon iconfont icon-pengyou1 show-group-info" v-if="isSingleConversation" @click="changeShowCreateGroup"></i>
<i title="群组信息" class="icon iconfont icon-pengyou show-group-info" v-if="!isSingleConversation" @click="changeShowGroupInfo"></i>
<groupInfo v-bind:targetId="selectedChat.target" v-if="showGroupInfo"></groupInfo>
</div>
</header>
<div class="message-wrapper" ref="list" @scroll="scrollEvent" @click="messageBoxClick" :style="{height: (appHeight * 0.75-60) + 'px'}">
<div v-if="isLoading" class="loading"><img src="../../assets/img/loading.gif" />{{loadingDes}}</div>
<ul v-if="selectedChat">
<li v-bind:key = index v-for="(item, index) in selectedChat.protoMessages" class="message-item">
<div v-if="isShowTime(index,selectedChat.protoMessages)" class="time"><span>{{item.timestamp | getTimeStringAutoShort2}}</span></div>
<div v-if="isGroupNotification(item)" class="time"><span>{{notificationContent(item)}}</span></div>
<div v-if="isRecallNotification(item)" class="time"><span>{{notificationContent(item)}}</span></div>
<div v-if="item.content.type === 90" class="time"><span>{{item.content.content}}</span></div>
<div v-if="!isNotification(item.content.type)" class="main" :class="{ self: item.direction == 0 ? true : false }">
<img class="avatar" width="36" height="36" :src="avatarSrc(item)"
onerror="this.src='static/images/vue.jpg'"/>
<div class="content">
<div class="display-name" v-if="!isSingleConversation && item.direction != 0">{{showUserName(item.from)}}</div>
<div class="content-message-right-menu">
<div class="send-status" v-if="item.direction == 0">
<i title = "发送中" class="icon iconfont icon-loading-solid" v-if="isSending(item)"></i>
<i title = "发送失败" class="icon iconfont icon-fasongshibai" v-if="isSendFail(item)"></i>
</div>
<div class="content-message" @contextmenu.prevent="messageRigthClick(item.messageId)">
<div v-if="item.content.type === 1 && isfaceMessage(item.content.searchableContent)" class="text" v-html="replaceFace(item.content.searchableContent)"></div>
<div v-if="item.content.type === 1 && !isfaceMessage(item.content.searchableContent)" class="text" v-text="item.content.searchableContent"></div>
<div v-if="item.content.type === 2">
[语音消息]
</div>
<div v-if="item.content.type === 3" v-viewer="options">
<img :src="imageThumnailSrc(item)" :data-src="item.content.remoteMediaUrl" class="receive-image">
</div>
<div v-if="item.content.type === 4">
[位置消息]
</div>
<div v-if="item.content.type === 5">
<div class="attachment">
<div class="flexbox flex-alignc">
<i class="ico-bg"></i>
<div class="file-info flex1">
<p class="name">{{fileMessageConfig(item).name}}</p>
<p class="size">{{fileMessageConfig(item).size}}</p>
</div>
<a class="btn-down" :href="fileMessageConfig(item).remotePath" target="_blank"></a>
</div>
</div>
</div>
<div v-if="item.content.type === 6" >
<Xgplayer :config="videoConfig(item,false,imageThumnailSrc(item))" @player="Player = $event"/>
</div>
<div v-if="item.content.type === 7">
[表情消息]
</div>
<div v-if="item.content.type === 8">
[图片表情]
</div>
<div v-if="item.content.type === 400">
[网络电话]
</div>
</div>
<rightMenu v-if="isShowMessageMenu(item)" v-bind:message="item"></rightMenu>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</template>
<script>
import { mapGetters,mapActions, mapState } from 'vuex'
import TimeUtils from '../../websocket/utils/timeUtils'
import Xgplayer from 'xgplayer-vue';
import 'viewerjs/dist/viewer.css'
import Viewer from 'v-viewer'
import Vue from 'vue'
Vue.use(Viewer)
import CryptoJS from 'crypto-js'
import groupInfo from '../menu/groupInfo'
import rightMenu from '../menu/rightMenu'
import MessageConfig from '../../websocket/message/messageConfig';
import NotificationMessageContent from '../../websocket/message/notification/notificationMessageContent';
import GroupNotificationContent from '../../websocket/message/notification/groupNotification';
import MessageStatus from '../../websocket/message/messageStatus';
import RecallMessageNotification from '../../websocket/message/notification/recallMessageNotification';
import webSocketClient from '../../websocket/websocketcli';
import { SUCCESS_CODE } from '../../constant';
import LocalStore from '../../websocket/store/localstore';
import Conversation from '../../websocket/model/conversation';
import ConversationType from '../../websocket/model/conversationType';
import ProtoMessage from '../../websocket/message/protomessage'
export default {
components:{
Xgplayer,
groupInfo,
rightMenu
},
data(){
return {
Player: null,
options: {
url: 'data-src'
},
messageDatalistHeight:0,
isLoading: false,
loadingDes: '数据载入中...'
}
},
computed: {
...mapGetters([
'selectedChat',
'messages',
'isSingleConversation'
]),
...mapState([
'user',
'emojis',
'appHeight',
'userInfoList',
'showGroupInfo',
'showMessageRightMenu',
'groupMemberMap',
'isLoadRemoteMessage'
]),
showGroupInfo: {
get() {
return this.$store.state.showGroupInfo;
},
set(value){
this.$store.state.showGroupInfo = value
}
},
},
mounted() {
// 在页面加载时让信息滚动到最下面
setTimeout(() => this.$refs.list.scrollTop = this.$refs.list.scrollHeight, 0);
// document.addEventListener("click", e => {
// var isString = typeof(e.target.className) == 'string'
// let groupInfoDom = document.getElementById("group-info-id");
// //注意点击显示按钮事件的处理,防止状态发生反转
// if (isString && e.target.className.search('show-group-info') == -1 && groupInfoDom && !groupInfoDom.contains(event.target) && this.showGroupFriendInfo) {
// this.showGroupFriendInfo = false;
// }
// });
},
watch: {
// 发送信息后,让信息滚动到最下面
messages() {
setTimeout(() => {
if(!this.isLoadRemoteMessage){
this.$refs.list.scrollTop = this.$refs.list.scrollHeight
} else {
var currentListheight = this.$refs.list.scrollHeight;
console.log("currentListheight "+currentListheight+" messageDatalistHeight "+this.messageDatalistHeight)
this.$refs.list.scrollTop = currentListheight - this.messageDatalistHeight
this.messageDatalistHeight = currentListheight
}
}, 0)
}
},
methods: {
...mapActions([
'addOldMessage',
]),
changeShowGroupInfo(){
if(this.showGroupInfo){
this.showGroupInfo = false
return
}
webSocketClient.getGroupMember(this.selectedChat.target).then(data => {
var isGroupMember = false;
if(data.code == SUCCESS_CODE){
if(data.result.length == 0){
this.$message.error("该群组已经解散,即将删除该会话");
this.$store.dispatch('deleteConversation',this.selectedChat.target)
} else {
this.groupMemberMap.set(this.selectedChat.target,data.result);
for(var groupMember of data.result){
if(groupMember.memberId == LocalStore.getUserId()){
isGroupMember = true;
break;
}
}
if(!isGroupMember){
this.$message.error("您不是群组成员,无法查看群组信息,即将删除该会话");
this.$store.dispatch('deleteConversation',this.selectedChat.target)
} else {
this.showGroupInfo = !this.showGroupInfo ;
if(this.showGroupInfo){
this.$store.dispatch("getGroupInfo",this.selectedChat.target);
}
}
}
}
})
},
changeShowCreateGroup(){
this.showGroupInfo = !this.showGroupInfo;
},
avatarSrc(item){
var avarimgUrl = 'static/images/vue.jpg';
if(item.direction == 0){
avarimgUrl = this.user.img;
} else {
var user = this.userInfoList.find(user => user.uid == item.from);
if(user){
avarimgUrl = user.portrait;
}
}
return avarimgUrl;
},
showUserName(from){
var displayName = webSocketClient.getDisplayName(from);;
return displayName;
},
// 在发送信息之后,将输入的内容中属于表情的部分替换成emoji图片标签
// 再经过v-html 渲染成真正的图片
replaceFace (con) {
if(con.includes('/:')) {
var emojis=this.emojis;
for(var i=0;i<emojis.length;i++){
con = con.replace(emojis[i].reg, '<img src="static/emoji/' + emojis[i].file +'" alt="" style="vertical-align: middle; width: 24px; height: 24px" />');
}
return con;
}
return con;
},
isfaceMessage(con){
return con.includes('/:');
},
isShowTime(index,protoMessages){
var msgTime = protoMessages[index].timestamp;
if(index > 0){
var preProtoMessage = protoMessages[index - 1];
var preMsgTime = preProtoMessage.timestamp;
if(msgTime - preMsgTime > ( 5 * 60 * 1000)){
return true;
}
}
return false;
},
videoConfig(protoMessage,paly = false,posterBase64){
return {
id: 'vs'+protoMessage.messageId,
// url 为空,可能导致不显示,这里强制写入poster
url: protoMessage.content.remoteMediaUrl == ''? posterBase64: protoMessage.content.remoteMediaUrl,
height: 330,
width: 250,
// fitVideoSize: 'auto',
poster:posterBase64,
autoplay: false,
download: true
}
},
// 参考资料 https://blog.csdn.net/qq449736038/article/details/80769507
scrollEvent(e){
let listheight= this.$refs.list.offsetHeight;
//console.log('scroll event top->'+e.srcElement.scrollTop+ ' scrollheight '+e.srcElement.scrollHeight+" list height->"+listheight);
if(e.srcElement.scrollHeight - e.srcElement.scrollTop > listheight){
this.$store.dispatch('clearUnreadStatus', '')
}
if(e.srcElement.scrollTop == 0){
this.isLoading = true
this.loadingDes = "数据载入中..."
this.messageDatalistHeight = e.srcElement.scrollHeight;
console.log("scroll height "+listheight)
var conversationType = this.isSingleConversation ? ConversationType.Single : ConversationType.Group
var conversation = new Conversation(conversationType,this.selectedChat.target,0)
var beforeUid = 0;
if(this.selectedChat.protoMessages && this.selectedChat.protoMessages.length > 0){
beforeUid = this.selectedChat.protoMessages[0].messageId
console.log("beforeUid "+beforeUid)
}
webSocketClient.getRemoteMessages(conversation,beforeUid,20).then(data => {
this.isLoading = false;
//console.log('code '+data.code+' result '+data.result)
if(data.result){
var remoteMessage = JSON.parse(data.result)
var count = remoteMessage.count
console.log("message count "+count)
if(count > 0){
var messageList = remoteMessage.messageResponseList;
for(var originProtoMessage of messageList ){
var protoMessage = ProtoMessage.toProtoMessage(originProtoMessage);
this.addOldMessage(protoMessage)
}
}
}
})
}
},
messageBoxClick(e){
this.$store.dispatch('clearUnreadStatus', '')
console.log('message box click');
},
isGroupNotification(protoMessage){
var contentClass = MessageConfig.getMessageContentClazz(protoMessage.content.type);
var content = new contentClass();
return content instanceof GroupNotificationContent;
},
isRecallNotification(protoMessage){
var contentClass = MessageConfig.getMessageContentClazz(protoMessage.content.type);
var content = new contentClass();
return content instanceof RecallMessageNotification;
},
notificationContent(protoMessage){
var displayContent;
var messageContent = MessageConfig.convert2MessageContent(protoMessage.from,protoMessage.content);
return messageContent.formatNotification();
},
fileMessageConfig(protoMessage){
var fileMessageContent = MessageConfig.convert2MessageContent(protoMessage.from,protoMessage.content)
return fileMessageContent;
},
isNotification(type){
return type >= 80 && type <= 117
},
isSending(protoMessage){
//兼容以前没有更新messageUid的发送消息
return protoMessage.status == MessageStatus.Sending
// && protoMessage.messageUid != 0;
},
isSendFail(protoMessage){
return protoMessage.status == MessageStatus.SendFailure
},
messageRigthClick(messageId){
var menuSetting = this.showMessageRightMenu.find(setting => setting.messageId == messageId)
if(menuSetting){
menuSetting.show = true
} else {
this.showMessageRightMenu.push({
messageId: messageId,
show: true
})
}
},
isShowMessageMenu(item){
var menuSetting = this.showMessageRightMenu.find(setting => setting.messageId == item.messageId)
if(menuSetting){
return menuSetting.show;
} else {
return false;
}
},
imageThumnailSrc(item){
var thumbnail = item.content.binaryContent;
if(thumbnail && thumbnail != ''){
thumbnail = "data:image/png;base64," +item.content.binaryContent;
} else {
thumbnail = ''
}
return thumbnail
},
},
filters: {
// 将日期过滤为 hour:minutes
time (date) {
if (typeof date === 'string') {
date = new Date(date);
}
if(typeof date === 'number'){
date = new Date(date);
}
if(date.getMinutes()<10){
return date.getHours() + ':0' +date.getMinutes();
}else{
return date.getHours() + ':' + date.getMinutes();
}
},
getTimeStringAutoShort2(timestamp){
return TimeUtils.getTimeStringAutoShort2(timestamp,true);
}
}
}
</script>
<style lang="stylus" scoped>
.receive-image
max-width : 115px;
max-height : 330px;
text-align: center
border-radius: 3px
.message
position: relative
width: 100%
height: 75%
.header
height: 14%
max-height: 60px
min-height: 60px
padding: 0px 0 0 30px
box-sizing: border-box
display:flex
.friendname
display: flex
align-items: center
font-size: 18px
.friend-group-info
padding: 0px 20px 0px 0px
display: flex
align-items: center
margin-left : auto
.icon
font-size: 24px
cursor: pointer
.message-wrapper
height: 86%
padding: 10px 15px
box-sizing: border-box
overflow-y: auto
border-top: 1px solid #e7e7e7
border-bottom: 1px solid #e7e7e7
background: #f2f2f2
.loading
color: #9ea0a3;
font-size: 12px;
font-family: arial;
text-align: center;
line-height: 30px;
img
display: inline-block;
vertical-align: top;
margin: 6px 3px 0 0;
height: 18px;
.message
margin-bottom: 15px
.time
width: 100%
font-size: 12px
margin: 7px auto
text-align: center
span
display: inline-block
padding: 4px 6px
color: #fff
border-radius: 3px
background-color: #dcdcdc
.main
margin-top: 10px
.avatar
float: left
margin-left: 15px
border-radius: 3px
.content
display:inline-block
width: 65%
.display-name
margin-left: 10px
margin-bottom: 5px
font-size: 8px
color: #999
.content-message-right-menu
position: relative
.content-message
display: inline-block
margin-left: 10px
position: relative
padding: 6px 10px
max-width: 90%
min-height: 36px
line-height: 24px
box-sizing: border-box
font-size: 14px
text-align: left
word-break: break-all
background-color: #fafafa
border-radius: 4px
.attachment
min-width: 200px
max-width: 350px
.ico-bg
background: url(/static/images/icon__attachment-white.png) no-repeat center #3aa4dd;
background-size: 20px;
display: inline-block;
vertical-align: top;
height: 40px;
width: 40px;
.file-info
font-size: 14px;
font-family: "Micrsofot Yahei";
margin-left: 10px;
.name
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 248px;
.size
color: #666;
font-size: 12px;
.btn-down
background: url(/static/images/icon__download.png) no-repeat center;
background-size: 15px;
display: inline-block;
vertical-align: top;
height: 30px;
width: 20px;
.text
white-space: pre-wrap;
&:before
content: " "
position: absolute
top: 12px
right: 100%
border: 6px solid transparent
border-right-color: #fafafa
.self
text-align: right
.avatar
float: right
margin:0 15px
.content
.send-status
height: 100%
display: inline-block
.icon
display: block
font-size: 16px
.icon-fasongshibai
color: red
.icon-loading-solid
animation: changeright 1s linear infinite
.content-message-right-menu
.content-message
background-color: #b2e281
&:before
right: -12px
vertical-align: middle
border-right-color: transparent
border-left-color: #b2e281
@keyframes changeright
0%
-webkit-transform:rotate(0deg)
50%
-webkit-transform:rotate(180deg)
100%
-webkit-transform:rotate(360deg)
</style>
================================================
FILE: src/components/mycard/mycard.vue
================================================
<!-- 最左边的选择框 -->
<template>
<div class="mycard" ref="mycardRef">
<header>
<img :src="user.img" class="avatar" @click="showPersonCard" @dblclick="changeFullScreenMode">
<personcard v-if="showPersonalCard" v-bind:userId="userId"></personcard>
</header>
<div class="navbar" @click="clearSearch">
<div class="conversation-item">
<span v-if="unreadTotalCount > 0" class="unread-num">
<span class="unread-num-show">{{unreadTotalCount}}</span>
</span>
<router-link to="/conversation" class="icon iconfont icon-dkw_xiaoxi" >
</router-link>
</div>
<div class="friend-item">
<span v-if="newFriendRequestCount > 0" class="unread-friend-request-num">
</span>
<router-link to="/friend" class="icon iconfont icon-pengyou">
</router-link>
</div>
<div class="icon iconfont icon-jiahaoyou" @click="showAddRequestTip = !showAddRequestTip">
<addtip v-show="showAddRequestTip"></addtip>
</div>
</div>
<footer>
<i title = "退出" class="icon iconfont icon-tuichu" @click="loginOut"></i>
</footer>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
import addtip from '../../components/menu/addtip'
import personcard from '../../components/menu/personalCard'
import webSocketCli from '../../websocket/websocketcli'
import Logger from '../../websocket/utils/logger'
export default {
components: {
addtip,
personcard
},
data() {
return {
showAddRequestTip: false,
showPersonalCard: false
}
},
computed: {
...mapState([
'user',
'userId',
'newFriendRequestCount'
]),
...mapGetters([
'unreadTotalCount',
])
},
methods: {
showPersonCard(){
this.showPersonalCard = !this.showPersonalCard
},
clearSearch() {
this.$store.dispatch('search', '')
},
loginOut(){
this.$store.dispatch('loginOut','');
},
changeFullScreenMode(){
var fullscreen = this.$store.state.changeFullScreenMode;
console.log('change screen mode '+fullscreen);
var _this = this;
_this.$store.state.changeFullScreenMode = !fullscreen;
setTimeout(() => {
let mycardHeight= _this.$refs.mycardRef.offsetHeight;
console.log('resize mycard height '+mycardHeight);
_this.$store.state.appHeight = mycardHeight;
},200);
}
},
mounted(){
document.addEventListener("click",e=>{
var isString = typeof(e.target.className) == 'string'
if (isString && e.target.className.search('icon-jiahaoyou') == -1 && e.target.className !== 'add-content' && this.showAddRequestTip) {
this.showAddRequestTip = false;
}
let personCardDom = document.getElementById("personal-card");
if (isString && e.target.className.search('avatar') == -1 && personCardDom && !personCardDom.contains(event.target) && this.showPersonalCard) {
this.showPersonalCard = false;
}
});
}
}
</script>
<style lang="stylus" scoped>
@import '../../assets/fonts/iconfont.css'
.mycard
position: relative
width: 100%
height: 100%
.avatar
width: 36px
height: 36px
margin: 20px 12px 0 12px
border-radius: 2px
.navbar
width: 100%
text-align: center
.icon
display: inline-block
font-size: 26px
margin-top: 28px
padding: 0 16px
box-sizing: border-box
color: rgb(173,174,175)
opacity: 1
cursor: pointer
&.active
color: rgb(0,220,65)
&:hover
opacity: 1;
.icon-msg,.icon-more
font-size: 22px
.icon-msg
padding: 0 19px
.friend-item
position:relative
.unread-friend-request-num
display: inline-block
min-width: 5px
height: 5px
background-color: red
border-radius: 8px
text-align: center
font-size: 12px
color: #fff
line-height: 16px
position:absolute
top: 25px
right: 17px
z-index: 10
.conversation-item
position:relative
.unread-num
display: inline-block
min-width: 16px
height: 16px
background-color: red
border-radius: 8px
text-align: center
font-size: 12px
color: #fff
line-height: 16px
position:absolute
top: 20px
right: 17px
z-index: 10
.unread-num-show
font-size:10px;
-webkit-transform:scale(0.8);
display:block;
footer
position: absolute
bottom: 20px
width: 100%
text-align: center
</style>
================================================
FILE: src/components/search/search.vue
================================================
<!-- 搜索框 -->
<template>
<div class="wrapper">
<div class="padding-wrapper">
<div class="search-wrapper">
<input type="text" class="searchInput" v-model="search" @keyup="change" placeholder="搜索">
<i class="icon iconfont icon-sousuo" v-show="noText"></i>
<div class="searchInput-delete" v-show="haveText" @click="del"></div>
</div>
</div>
</div>
</template>
<script>
export default {
methods: {
change () {
this.$store.dispatch('search', this.search)
},
del () {
this.search= ''
this.change()
}
},
data () {
return {
search: '',
active: false
}
},
computed: {
noText () {
if(this.search === '') return true
return false
},
haveText () {
if(this.search === '') return false
return true
}
}
}
</script>
<style lang="stylus" scoped>
.wrapper
max-height: 60px
min-height: 60px
height: 60px
.padding-wrapper
border-right: 1px solid #e7e7e7
padding: 22px 12px 12px 12px
.search-wrapper
position: relative
display: flex
box-sizing: border-box
height: 100%
max-height: 26px
width: 100%
background-color: #e5e3e2
border: 1px solid #d9d7d6
border-radius: 2px
.searchInput
flex: 1
font-size: 12px
padding: 6px
background-color: #e5e3e2
outline: none
&:focus
background-color: #f2efee
.icon-sousuo
display: inline-block
width: 24px
height: 24px
font-size: 14px
line-height: 24px
text-align: center
.searchInput-delete
display: block
position: absolute
outline: none
top: 0;
right: 0;
width: 24px
height: 100%
background-image: url(delete.png)
background-size: 26px
background-position: center
background-repeat: no-repeat
cursor: pointer
</style>
================================================
FILE: src/components/text/text.vue
================================================
<!-- 文本输入框 -->
<template>
<div class="text">
<div class="emoji">
<i class="icon iconfont icon-biaoqing" @click="showEmoji=!showEmoji"></i>
<i title="语音聊天" class="icon iconfont icon-dianhua" v-show="isSingleConversation" @click="sendAudio"></i>
<i title="视频聊天" class="icon iconfont icon-ai-video" @click="sendVideo"></i>
<i title="发送图片" class="icon iconfont icon-tupian" >
<input type="file" accept="image/*" id="chat-send-img" ref="uploadPic" @change="sendPic">
</i>
<i title="发送视频" class="icon iconfont icon-shipin" >
<input type="file" accept="video/*" id="chat-send-video" ref="uploadVideo" @change="sendVideoMessage">
</i>
<i title="发送文件" class="icon iconfont icon-wenjian">
<input type="file" accept="*" id="chat-send-file" ref="uploadFile" @change="sendFile">
</i>
<transition name="showbox">
<div class="emojiBox" v-show="showEmoji">
<li v-bind:key= index v-for="(item, index) in emojis">
<img :src="'static/emoji/'+item.file" :data="item.code" @click="content +=item.code">
</li>
</div>
</transition>
<transition name="voice-video-chat-box">
<div class="chat-modal" v-show="false">
<div class="chat-box">
<video id="video-remote" playsinline autoplay muted></video>
<video id="video-local" playsinline autoplay muted></video>
</div>
</div>
</transition>
<div class="callContent" v-show="showChatBox">
<div class="">
<div class="callercontent callshow" style="">
<div class="exchange-content">
<div class="playcontent left-big-content">
<img id="wxCallRemoteImg" class="bigavatar" :src="callRemoteImg" v-show="showCallRemoteImg"/>
<p id="wxCallTips" class="calltips" v-text="videoTextCallTips" v-show="showCallTips"> 接通中... </p>
<video id="wxCallRemoteVideo" autoplay="autoplay" playsinline="" style="display: none;" v-show="showCallRemoteVideo"></video>
</div>
<div class="playcontent right-sml-content">
<img id = "wxCallLocalImg" :src="callLocalImg" class="bigavatar" v-show="showCallLocalImg"/>
<video id="wxCallLocalVideo" autoplay="autoplay" muted="muted" playsinline="" style="display: none;" v-show="showCallLocalVideo"></video>
</div>
</div>
<div class="opera-content flexbox">
<img class="calleravatar" :src="callRemoteImg" />
<span class="flexauto overell callnick" v-text="callDisplayName"></span>
<span class="flexbox">
<span class="operabtn canclecall btnopacity" v-show="cancelCall" @click="cancel">取消</span>
<span class="operabtn canclecall btnopacity" style="display: none;" v-show="rejectCall" @click="reject">拒绝</span>
<span class="operabtn upcall btnopacity" style="display: none;" v-show="acceptCall" @click="accept">接听</span>
</span>
<span class="talktime flexbox" style="display: none;" v-show="showTalkTime"><i class="iconfont icon-ai-video"></i> <span v-text="talkTime">00:00</span></span>
<span class="operabtn canclecall btnopacity" style="display: none;" v-show="hangUpCall" @click="hangUp"><i class="iconfont icon-guaduan"></i>挂断 </span>
<button class="screenbtn"><i class="iconfont icon-quanping iconfull" style="display: none;"></i></button>
<button class="screenbtn" style="display: none;"><i class="iconfont icon-tuichuquanping iconfull"></i></button>
</div>
</div>
</div>
</div>
<div class="audioContent" v-show="showAudioBox">
<div class="audioBody callshow" style="">
<div class="audioBg">
<img class="callavatar" :src="callRemoteImg" />
<div class="blackbg"></div>
</div>
<div class="audiomain">
<img class="audio-avatar" :src="callRemoteImg" />
<p class="callnick" v-text="callDisplayName"></p>
<p class="call-time" style="display: none;" v-show="showTalkTime" v-text="talkTime">00:00</p>
<p class="waiting-msg" v-show="waitingMsg" v-text="waitingMsgTips"> 接通中... </p>
<div class="call-opera flexbox">
<span class="cancleaudio btnopacity" style="display: none;" v-show="hangUpCall" @click="hangUp"><i class="iconfont icon-guaduan"></i>挂断 </span>
<div class="loadingcall flexbox">
<span class="cancleaudio callercanle btnopacity" style="display: none;" v-show="cancelCall" @click="cancel"><i class="iconfont icon-guaduan"></i>取消 </span>
<span class="cancleaudio btnopacity" style="display: none;" v-show="rejectCall" @click="reject">拒绝</span>
<span class="upcall btnopacity" style="display: none;" v-show="acceptCall" @click="accept">接听</span>
</div>
</div>
</div>
<div style="display: none;">
<audio id="wxCallRemoteAudio" autoplay="autoplay"></audio>
</div>
<div style="display: none;">
<audio id="wxCallLocalAudio" autoplay="autoplay" muted="muted"></audio>
</div>
</div>
</div>
</div>
<textarea id="sendText" ref="text" v-model="content" @keydown.enter.exact="sendMessage" @keydown.ctrl.enter="newline" @focus="onFocus" @click="showEmoji=false"></textarea>
<div class="send" @click="send" ref="sendBtn" v-bind:class="{disable : sendBtnDisabled}">
<span>发送</span>
</div>
<transition name="appear">
<div class="warn" v-show="warn">
<div class="description">不能发送空白信息</div>
</div>
</transition>
</div>
</template>
<script>
import adapter from 'webrtc-adapter'
import { mapGetters, mapState } from 'vuex'
import TextMessageContent from '../../websocket/message/textMessageContent'
import ImageMessageContent from '../../websocket/message/imageMessageContent'
import * as qiniu from 'qiniu-js'
import MessageContentMediaType from '../../websocket/message/messageContentMediaType'
import LocalStore from '../../websocket/store/localstore'
import SessionCallback from '../../webrtc/sessionCallback'
import EngineCallback from '../../webrtc/engineCallback'
import SendMessage from '../../websocket/message/sendMessage'
import CallState from '../../webrtc/callState'
import {UPLOAD_BY_QINIU, SUCCESS_CODE } from '../../constant'
import webSocketCli from '../../websocket/websocketcli'
import Message from '../../websocket/message/message'
import ProtoMessage from '../../websocket/message/protomessage'
import VideoMessageContent from '../../websocket/message/videoMessageContent'
import FileMessageContent from '../../websocket/message/fileMessageContent'
export default {
data () {
return {
content: '',
sendBtnDisabled: true,
reply: '未找到',
frequency: 0,
warn: false,
showEmoji: false,
videoTextCallTips: '',
voipClient: null,
rejectCall: false,
cancelCall: false,
acceptCall: false,
hangUpCall: false,
showCallLocalImg: true,
showCallLocalVideo: false,
showCallRemoteImg: true,
showCallRemoteVideo: false,
showCallTips: true,
callRemoteImg: 'static/images/vue.jpg',
callLocalImg: 'static/images/vue.jpg',
callDisplayName: '',
waitingMsg: false,
isAudioOnly: false,
waitingMsgTips: '',
showTalkTime: false,
talkInterval: 0,
talkTime: '00:00',
talkTimerInterval: null,
};
},
computed: {
...mapState([
'selectId',
'emojis',
'showChatBox',
'showAudioBox',
'userInfoList',
'inCommingNotify',
'outGoingNotify'
]),
...mapGetters([
'selectedChat',
'isSingleConversation',
])
},
methods: {
sendPic(e){
var store = this.$store;
var file = e.target.files[0];
var localPath = e.target.value
if(UPLOAD_BY_QINIU){
store.dispatch('getUploadToken', MessageContentMediaType.Image);
console.log("sendpic "+e.target.value);
var key = MessageContentMediaType.Image +"-"+LocalStore.getUserId()+"-"+new Date().getTime()+"-"+file.name;
setTimeout(()=> {
var token = LocalStore.getImageUploadToken();
console.log("upload file key "+key+" token "+token);
if(token){
var observable = qiniu.upload(file, key, token, null, null);
var observer = {
next(res){
console.log('uploading '+res.total.percent);
},
error(err){
console.log("upload error "+err.code +" message "+err.message);
},
complete(res){
console.log("upload complete "+res);
var localPath = e.target.value;
var remotePath = "http://image.comsince.cn/"+key;
var imageMessageContent = new ImageMessageContent(localPath,remotePath,null);
store.dispatch('sendMessage', new SendMessage(null,imageMessageContent))
}
}
observable.subscribe(observer);
}
},200);
} else {
this.sendImage(file)
}
this.$refs.uploadPic.value = null;
},
sendImage(file){
var store = this.$store;
var key = MessageContentMediaType.Image +"-"+LocalStore.getUserId()+"-"+new Date().getTime()+"-"+file.name;
webSocketCli.getMinioUploadUrl(MessageContentMediaType.Image,key).then(data => {
if(data.code == SUCCESS_CODE){
console.log("domain "+data.result.domain+" url "+data.result.url)
var messageId;
var thunmbanilwithoutDesc;
//获取缩略图,同时也为了适配android 端适配的问题,防止转发图片报错
var reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = (e) => {
var result = e.target.result
this.canvasDataURL(result,{
},base64Img => {
thunmbanilwithoutDesc = base64Img.split(',')[1]
//添加缩略消息
var imageMessageContent = new ImageMessageContent(file,null,thunmbanilwithoutDesc);
var message = Message.conert2Message(new SendMessage(null,imageMessageContent));
var protoMessage = ProtoMessage.convertToProtoMessage(message);
messageId = protoMessage.messageId
store.dispatch('preAddProtoMessage', protoMessage);
})
}
fetch(data.result.url, {
method: 'PUT',
body: file
}).then(() => {
var remotePath = data.result.domain+"/"+key;
console.log("remote path "+remotePath)
var imageMessageContent = new ImageMessageContent(file,remotePath,thunmbanilwithoutDesc);
store.dispatch('updateSendMessage', {messageId: messageId,messageContent:imageMessageContent})
}).catch((e) => {
console.error(e);
});
}
})
},
/*** js 图片压缩上传(纯js的质量压缩,非长宽压缩)
* https://www.cnblogs.com/xiangsj/p/8932525.html
*/
canvasDataURL(path, obj, callback){
var img = new Image();
img.src = path;
img.onload = function(){
var that = this;
// 默认按比例压缩
var w = that.width,
h = that.height,
scale = w / h;
w = w * 0.5 > 113 ? 113 : w * 0.5;
h = w / scale;
console.log("scale "+scale+" transfer size "+w+":"+h)
var quality = 0.5; // 默认图片质量为0.7
//生成canvas
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
// 创建属性节点
var anw = document.createAttribute("width");
anw.nodeValue = w;
var anh = document.createAttribute("height");
anh.nodeValue = h;
canvas.setAttributeNode(anw);
canvas.setAttributeNode(anh);
ctx.drawImage(that, 0, 0, w, h);
// 图像质量
if(obj.quality && obj.quality <= 1 && obj.quality > 0){
quality = obj.quality;
}
// quality值越小,所绘制出的图像越模糊
var base64 = canvas.toDataURL('image/jpeg', quality);
// 回调函数返回base64的值
callback(base64);
}
},
//https://github.com/metroluffy/blog/issues/18
sendVideoMessage(e){
var store = this.$store;
var file = e.target.files[0];
var localPath = e.target.value
var key = MessageContentMediaType.Video +"-"+LocalStore.getUserId()+"-"+new Date().getTime()+"-"+file.name;
webSocketCli.getMinioUploadUrl(MessageContentMediaType.Video,key).then(data => {
if(data.code == SUCCESS_CODE){
console.log("domain "+data.result.domain+" url "+data.result.url)
var messageId;
var thunmbanilwithoutDesc;
//获取缩略图,同时也为了适配android 端适配的问题,防止转发图片报错
var url = URL.createObjectURL(file);
console.log("video url "+url)
this.getVideoImage(url,base64Img =>{
console.log("base64Img "+base64Img)
thunmbanilwithoutDesc = base64Img.split(',')[1]
//添加缩略消息
var videoMessageContent = new VideoMessageContent(localPath,'',thunmbanilwithoutDesc);
var message = Message.conert2Message(new SendMessage(null,videoMessageContent));
var protoMessage = ProtoMessage.convertToProtoMessage(message);
messageId = protoMessage.messageId
store.dispatch('preAddProtoMessage', protoMessage);
},2)
fetch(data.result.url, {
method: 'PUT',
body: file
}).then(() => {
var remotePath = data.result.domain+"/"+key;
console.log("remote path "+remotePath)
var imageMessageContent = new VideoMessageContent(localPath,remotePath,thunmbanilwithoutDesc);
store.dispatch('updateSendMessage', {messageId: messageId,messageContent:imageMessageContent})
}).catch((e) => {
console.error(e);
});
}
})
this.$refs.uploadVideo.value = null;
},
getVideoImage(path, callback, secs = 1) {
var me = this,
video = document.createElement('video');
video.onloadedmetadata = function () {
if ('function' === typeof secs) {
secs = secs(this.duration);
}
this.currentTime = Math.min(Math.max(0, (secs < 0 ? this.duration : 0) + secs), this.duration);
console.log("secs "+secs+" currentTime "+this.currentTime)
};
video.onseeked = function (e) {
var canvas = document.createElement('canvas');
var w = video.videoHeight,
h = video.videoWidth,
scale = w / h;
w = w > 250 ? 250 : w;
h = w / scale;
console.log("scale "+scale+" transfer size "+w+":"+h)
canvas.height = h;
canvas.width = w;
var ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
var imgBase64 = canvas.toDataURL('image/jpeg', 0.7);
callback(imgBase64);
};
video.onerror = function (e) {
console.log("excption"+e)
callback.call(me, undefined, undefined, e);
};
video.src = path;
},
sendFile(e){
var store = this.$store;
var file = e.target.files[0];
var localPath = e.target.value
var key = MessageContentMediaType.File +"-"+LocalStore.getUserId()+"-"+new Date().getTime()+"-"+file.name;
webSocketCli.getMinioUploadUrl(MessageContentMediaType.File,key).then(data => {
if(data.code == SUCCESS_CODE){
console.log("domain "+data.result.domain+" url "+data.result.url)
var messageId;
var fileMessageContent = new FileMessageContent(file,'');
var message = Message.conert2Message(new SendMessage(null,fileMessageContent));
var protoMessage = ProtoMessage.convertToProtoMessage(message);
messageId = protoMessage.messageId
store.dispatch('preAddProtoMessage', protoMessage);
fetch(data.result.url, {
method: 'PUT',
body: file
}).then(() => {
var remotePath = data.result.domain+"/"+key;
console.log("remote path "+remotePath)
var message = new FileMessageContent(file,remotePath);
store.dispatch('updateSendMessage', {messageId: messageId,messageContent:message})
}).catch((e) => {
console.error(e);
});
}
})
this.$refs.uploadFile.value = null;
},
// 按回车发送信息
sendMessage (e) {
console.log("send code "+e.keyCode);
if(e.keyCode === 13){
this.send();
//阻止回车换行
e.preventDefault();
}
},
newline(e){
this.content = this.content + '\n';
e.preventDefault();
},
onBlur(){
console.log('send text onblur');
},
onFocus(){
console.log('send text onfoucs');
this.$store.dispatch('clearUnreadStatus', '');
},
// 点击发送按钮发送信息
send () {
console.log("send message "+this.content);
if(this.content.length < 1){
this.warn = true
this.content = ''
setTimeout(() => {
this.warn = false;
}, 1000)
}else{
//进行消息类型包装
var textMessageContent = new TextMessageContent(this.content);
this.sendMessageToStore(new SendMessage(null,textMessageContent));
this.content = '';
this.$refs.text.focus();
}
},
//发送语音聊天
sendAudio(){
this.$store.state.showAudioBox = true;
this.waitingMsg = true;
this.rejectCall = false;
this.acceptCall = false;
this.hangUpCall = false;
this.cancelCall = true;
this.initCallUserInfo(this.$store.state.selectTarget);
this.isAudioOnly = true;
this.voipClient.startCall(this.$store.state.selectTarget,this.isAudioOnly);
},
//发送视频聊天
sendVideo(){
if(this.isSingleConversation){
this.$store.state.showChatBox = true;
this.rejectCall = false;
this.acceptCall = false;
this.hangUpCall = false;
this.cancelCall = true;
this.showCallRemoteVideo = false;
this.showCallRemoteImg = true;
this.showCallTips = true;
this.videoTextCallTips = "正在接通,请稍候...";
this.initCallUserInfo(this.$store.state.selectTarget);
this.isAudioOnly = false;
this.voipClient.startCall(this.$store.state.selectTarget,this.isAudioOnly);
} else {
this.$store.state.groupOperateState = 4;
//触发groupMap以是vue相应变更
this.$store.state.groupMemberTracker += 1;
this.$store.state.showCreateGroupDialog = true;
}
},
initCallUserInfo(target){
var portrait = this.getUserPortrait(target);
if(portrait){
this.callRemoteImg = portrait;
}
this.callLocalImg = this.$store.state.user.img;
this.callDisplayName = this.getDisplayName(target);
},
getUserPortrait(target){
var userInfo = this.userInfoList.find(userInfo => userInfo.uid == target);
if(userInfo){
return userInfo.portrait;
}
return null;
},
getDisplayName(target){
var userInfo = this.userInfoList.find(userInfo => userInfo.uid == target);
return userInfo.displayName;
},
hangUp(){
this.voipClient.cancelCall();
},
reject(){
this.voipClient.cancelCall();
},
accept(){
this.rejectCall = false;
this.acceptCall = false;
this.hangUpCall = true;
this.voipClient.answerCall(this.isAudioOnly);
},
cancel(){
this.voipClient.cancelCall();
},
sendMessageToStore(sendMessage){
this.$store.dispatch('sendMessage', sendMessage)
}
},
// 在进入的时候 聚焦输入框
mounted() {
this.$refs.text.focus()
var sessionCallback = new SessionCallback();
var engineCallback = new EngineCallback();
engineCallback.onReceiveCall = session => {
this.isAudioOnly = session.audioOnly;
console.log("receive isAudioOnly "+session.audioOnly);
if(!this.isAudioOnly){
this.$store.state.showChatBox = true;
this.videoTextCallTips = '对方邀请您进行视频通话';
this.rejectCall = true;
this.cancelCall = false;
this.acceptCall = true;
this.hangUpCall = false;
this.showCallLocalImg = true;
this.showCallLocalVideo = false;
this.showCallRemoteImg = true;
this.showCallRemoteVideo = false;
this.showCallTips = true;
this.initCallUserInfo(session.clientId);
} else {
this.$store.state.showAudioBox = true;
this.waitingMsg = true;
this.waitingMsgTips = '对方邀请你进行语音通话';
this.rejectCall = true;
this.acceptCall = true;
this.cancelCall = false;
this.hangUpCall = false;
this.initCallUserInfo(session.clientId);
}
//取消textarea焦点聚焦
try {
document.getElementById('sendText').blur();
} catch(error){
console.error("get sendText error "+error);
}
}
engineCallback.shouldStartRing = isIncomming => {
if(isIncomming){
this.inCommingNotify.loopPlay()
} else {
this.outGoingNotify.loopPlay();
}
}
engineCallback.shouldSopRing = () => {
this.inCommingNotify.stopPlay();
this.outGoingNotify.stopPlay();
}
sessionCallback.didChangeState = state => {
if(state === CallState.STATUS_CONNECTED){
if(this.isAudioOnly){
this.cancelCall = false;
this.rejectCall = false;
this.acceptCall = false;
this.hangUpCall = true;
this.waitingMsg = false;
} else {
this.cancelCall = false;
this.acceptCall = false;
this.rejectCall = false;
this.hangUpCall = true;
}
this.showTalkTime = true;
this.talkTimerInterval = setInterval(() => {
this.talkInterval += 1;
var min = Math.floor(this.talkInterval / 60 % 60);
var sec = Math.floor(this.talkInterval % 60);
sec = sec < 10 ? "0"+sec : sec;
min = min < 10 ? "0"+min : min;
this.talkTime = min + ":"+ sec;
},1000)
}
}
sessionCallback.didReceiveRemoteAudioTrack = stream => {
document.getElementById("wxCallRemoteAudio").srcObject = stream;
}
sessionCallback.didCallEndWithReason = callEndReason => {
if(this.isAudioOnly){
this.$store.state.showAudioBox = false;
} else {
this.$store.state.showChatBox = false;
}
if(this.talkTimerInterval){
this.showTalkTime = false;
this.talkInterval = 0;
clearInterval(this.talkTimerInterval);
}
}
sessionCallback.didCreateLocalVideoTrack = (stream) => {
this.showCallLocalImg = false;
this.showCallLocalVideo = true;
document.getElementById("wxCallLocalVideo").srcObject = stream;
}
sessionCallback.didReceiveRemoteVideoTrack = stream => {
this.showCallRemoteImg = false;
this.showCallTips = false;
this.showCallRemoteVideo = true;
document.getElementById("wxCallRemoteVideo").srcObject = stream;
}
this.voipClient = this.$store.state.voipClient;
this.voipClient.setCurrentSessionCallback(sessionCallback);
this.voipClient.setCurrentEngineCallback(engineCallback);
var _this = this
document.getElementById('sendText').addEventListener('paste',function(e){
var cbd = e.clipboardData;
var ua = window.navigator.userAgent;
// 没有数据
if (!(e.clipboardData && e.clipboardData.items)) { return;
}
// Mac平台下Chrome49版本以下 复制Finder中的文件的Bug Hack掉
if(cbd.items && cbd.items.length === 2 && cbd.items[0].kind === "string" && cbd.items[1].kind === "file" &&
cbd.types && cbd.types.length === 2 && cbd.types[0] === "text/plain" && cbd.types[1] === "Files" &&
ua.match(/Macintosh/i) && Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49){
return;
}
for(var i = 0; i < cbd.items.length; i++){
var item = cbd.items[i];
var blob;
if(item.kind == "file"){
blob = item.getAsFile(); if(blob.size === 0){ return;
}
// 插入图片记录
_this.sendImage(blob)
}
}
});
},
watch: {
// 在选择其它对话的时候 聚焦输入框
selectId() {
setTimeout(() => {
this.$refs.text.focus()
}, 0)
},
// 当输入框中的值为空时 弹出提示 并在一秒后消失
content() {
if(this.content === ''){
this.$refs.sendBtn.style.background = "#f5f5f5";
this.$refs.sendBtn.style.color = '#7c7c7c';
this.sendBtnDisabled = true;
} else {
this.$refs.sendBtn.style.background = "rgb(18,150,17)";
this.$refs.sendBtn.style.color = '#fff';
this.sendBtnDisabled = false;
}
}
}
}
</script>
<style lang="stylus" scoped>
.text
position: relative
height: 25%
background: #fff
.emoji
position: relative
width: 100%
height: 26%
line-height: 40px
font-size: 12px
padding: 0 30px
box-sizing: border-box
color: #7c7c7c
i
font-size: 20px;
margin-right : 10px;
cursor: pointer
position: relative
&:hover
color: #1aad19
input
opacity: 0;
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 11;
font-size: 0;
cursor: pointer;
.emojiBox
position: absolute
display: flex
flex-wrap: wrap
top: -210px
left: -100px
width: 300px
height: 200px
padding: 5px
background-color: #fff
border: 1px solid #d1d1d1
border-radius: 2px
box-shadow:0 1px 2px 1px #d1d1d1
&.showbox-enter-active, &.showbox-leave-active
transition: all .5s
&.showbox-enter,&.showbox-leave-active
opacity: 0
.chat-modal
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 1001;
-webkit-overflow-scrolling: touch;
outline: 0;
overflow: hidden;
margin: 30/@rate auto;
background-color: rgba(0,0,0,.3);
.chat-box
position: relative;
left: 50%;
top: 5%;
transform: translate(-50%,0);
padding: 50/@rate 40/@rate;
background: #fff;
height: 800px;
width: 480px;
.video-local
width: 50px;
height: 100px;
vertical-align: middle;
.video-remote
width: 480px;
height: 800px;
vertical-align: middle;
.btnopacity:hover
opacity: .8
.callContent
.callercontent
width: 664px;
height: 414px;
position: absolute;
left: 0;
right: 0;
margin: auto;
top: 10;
bottom: 0;
z-index: 2000
.callercontent video
width: 100%;
background: #000
.callercontent.callnone
display: none
.callercontent.callshow
display: block
.left-big-content
width: 480px;
height: 360px;
position: absolute;
left: 0;
top: 0
.left-big-content .bigavatar
width: 100%;
height: 100%;
filter: blur(6px)
.left-big-content video
width: 100%;
height: 100%;
background: #000
.right-sml-content
width: 160px;
height: 120px;
box-shadow: 0 6px 20px 0 rgba(48,52,58,0.5);
border-radius: 4px;
position: absolute;
right: 0;
bottom: 0
.right-sml-content .bigavatar
width: 100%;
height: 100%;
border-radius: 4px
.opera-content
padding: 10px 16px;
box-shadow: 0 6px 20px 0 rgba(48,52,58,0.5);
height: 56px;
width: 480px;
background: #fff;
position: absolute;
bottom: 0;
left: 0
.opera-content .calleravatar
width: 36px;
height: 36px;
margin-right: 16px;
flex-shrink: 0;
border-radius: 100%
.opera-content .callnick
color: #30343a
.opera-content .operabtn
width: 72px;
height: 32px;
border-radius: 6px;
color: #fff;
text-align: center;
font-size: 12px;
line-height: 32px;
cursor: pointer
.opera-content .canclecall
background: #ff6161
.opera-content .canclecall .iconfont
color: #fff;
font-size: 16px;
margin-right: 8px
.opera-content .upcall
margin-left: 16px;
background: #39ba70
.calltips
position: absolute;
margin: auto;
text-align: center;
color: #fff;
font-size: 16px;
z-index: 10;
left: 0;
right: 0;
top: 0;
line-height: 360px
.flexshrink
flex-shrink: 0
.iconfull
margin-left: 16px;
font-size: 16px;
cursor: pointer
.screenbtn
background: #fff;
border: 0
.talktime span
font-size: 12px;
color: #30343a;
margin-left: 8px;
margin-right: 16px
.flexbox
display: flex;
align-items: center
.flexauto
flex: 1
.audioContent
.flexbox
display: flex;
align-items: center
.callshow
display: block
.audioBody
width: 280px;
height: 344px;
position: absolute;
left: 0;
right: 0;
margin: auto;
top: 10;
bottom: 0;
z-index: 2000;
border-radius: 3px
.audioBody .audioBg
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
border-radius: 3px
.audioBody .audioBg .callavatar
width: 100%;
height: 100%;
filter: blur(4px)
.audioBody .audioBg .blackbg
width: 100%;
height: 100%;
position: absolute;
z-index: 5;
top: 0;
left: 0;
background: #000;
opacity: .5
.audioBody .audiomain
position: relative;
z-index: 6;
text-align: center;
color: #fff
.audioBody .audiomain .audio-avatar
width: 73px;
height: 73px;
position: relative;
border-radius: 50%;
overflow: hidden;
margin-top: 64px;
margin-bottom: 12px
.audioBody .audiomain .callnick
font-size: 16px;
line-height: 22px
.audioBody .audiomain .call-time
line-height: 20px;
margin-top: 4px
.audioBody .audiomain .call-opera
justify-content: center
.audioBody .audiomain .call-opera span
width: 96px;
height: 32px;
margin: 66px 12px 0;
line-height: 32px;
border-radius: 6px;
cursor: pointer
.audioBody .audiomain .call-opera span .iconfont
&:hover
pointer-events: none
font-size: 16px;
margin-right: 8px
.audioBody .audiomain .call-opera .nomuted
border: 1px solid #fff
.audioBody .audiomain .call-opera .muted
background: #fff;
color: #30343a
.audioBody .audiomain .call-opera .cancleaudio
background: #ff6161
.audioBody .audiomain .call-opera .callercanle
width: 128px
.audioBody .loadingcall
justify-content: center
.audioBody .loadingcall .upcall
background: #39ba70
textarea
box-sizing: border-box
padding: 0 30px
height: 74%
width: 100%
border: none
outline: none
font-family: "Micrsofot Yahei"
font-size: 13px
resize: none
.send
position: absolute
bottom: 10px
right: 30px
width: 75px
height: 28px
line-height: 28px
box-sizing: border-box
text-align: center
border: 1px solid #e5e5e5
border-radius: 3px
background: #f5f5f5
font-size: 14px
color: #7c7c7c
&:hover
background: rgb(18,150,17)
color: #fff
.warn
position: absolute
bottom: 50px
right: 10px
width: 110px
height: 30px
line-height: 30px
font-size: 12px
text-align: center
border: 1px solid #bdbdbd
border-radius: 4px
box-shadow:0 1px 5px 1px #bdbdbd
&.appear-enter-active, &.appear-leave-active
transition: all 1s
&.appear-enter,&.appear-leave-active
opacity: 0
&:before
content: " "
position: absolute
top: 100%
right: 20px
border: 7px solid transparent
border-top-color: #fff
filter:drop-shadow(1px 3px 2px #bdbdbd)
.disable
pointer-events: none;
</style>
================================================
FILE: src/constant/index.js
================================================
export const WS_PROTOCOL = '
gitextract_p2lxtd1d/
├── .babelrc
├── .editorconfig
├── .gitignore
├── .postcssrc.js
├── LICENSE
├── README.md
├── build/
│ ├── build.js
│ ├── check-versions.js
│ ├── dev-client.js
│ ├── dev-server.js
│ ├── utils.js
│ ├── vue-loader.conf.js
│ ├── webpack.base.conf.js
│ ├── webpack.dev.conf.js
│ └── webpack.prod.conf.js
├── config/
│ ├── dev.env.js
│ ├── index.js
│ └── prod.env.js
├── index.html
├── index.md
├── package.json
├── src/
│ ├── App.vue
│ ├── assets/
│ │ └── fonts/
│ │ ├── iconfont.css
│ │ ├── iconfont.js
│ │ └── iconfont.json
│ ├── components/
│ │ ├── chatlist/
│ │ │ └── chatlist.vue
│ │ ├── friendlist/
│ │ │ └── friendlist.vue
│ │ ├── info/
│ │ │ └── info.vue
│ │ ├── menu/
│ │ │ ├── addtip.vue
│ │ │ ├── groupInfo.vue
│ │ │ ├── personalCard.vue
│ │ │ ├── relayMessage.vue
│ │ │ └── rightMenu.vue
│ │ ├── message/
│ │ │ └── message.vue
│ │ ├── mycard/
│ │ │ └── mycard.vue
│ │ ├── search/
│ │ │ └── search.vue
│ │ └── text/
│ │ └── text.vue
│ ├── constant/
│ │ └── index.js
│ ├── main.js
│ ├── page/
│ │ ├── chat/
│ │ │ └── chat.vue
│ │ ├── friend/
│ │ │ ├── friend.vue
│ │ │ └── searchfriend.vue
│ │ ├── group/
│ │ │ ├── creategroup.vue
│ │ │ └── groupVideoCall.vue
│ │ ├── login/
│ │ │ └── login.vue
│ │ └── main.vue
│ ├── permission.js
│ ├── router/
│ │ └── index.js
│ ├── store.js
│ ├── webrtc/
│ │ ├── callEndReason.js
│ │ ├── callSession.js
│ │ ├── callState.js
│ │ ├── engineCallback.js
│ │ ├── groupCallClient.js
│ │ ├── message/
│ │ │ ├── callAnswerMessageContent.js
│ │ │ ├── callAnswerTMessageContent.js
│ │ │ ├── callByeMessageContent.js
│ │ │ ├── callModifyMessageContent.js
│ │ │ ├── callSignalMessageContent.js
│ │ │ └── callStartMessageContent.js
│ │ ├── participant.js
│ │ ├── sessionCallback.js
│ │ └── voipclient.js
│ └── websocket/
│ ├── chatManager.js
│ ├── future/
│ │ ├── futureResult.js
│ │ └── promiseResolve.js
│ ├── handler/
│ │ ├── abstractmessagehandler.js
│ │ ├── addGroupMemberHandler.js
│ │ ├── connectackhandler.js
│ │ ├── createGroupHandler.js
│ │ ├── dismissGroupHandler.js
│ │ ├── friendAddRequestHandler.js
│ │ ├── friendRequestHandler.js
│ │ ├── getGroupInfoHandler.js
│ │ ├── getGroupMemberHandler.js
│ │ ├── getMinioUploadUrlHandler.js
│ │ ├── getUploadtokenHandler.js
│ │ ├── getfriendresultHandler.js
│ │ ├── getuserinfoHandler.js
│ │ ├── handleFriendRequestHandler.js
│ │ ├── kickGroupmemberHandler.js
│ │ ├── loadRemoteMessageHander.js
│ │ ├── messageHandler.js
│ │ ├── modifyMyInfoHandler.js
│ │ ├── notifyFriendHandler.js
│ │ ├── notifyFriendRequestHandler.js
│ │ ├── notifyMessageHandler.js
│ │ ├── notifyRecallMessageHandler.js
│ │ ├── quitGroupHandler.js
│ │ ├── recallMessageHandler.js
│ │ ├── receiveMessageHandler.js
│ │ ├── searchUserResultHandler.js
│ │ ├── sendMessageHandler.js
│ │ └── setFriendAliasRequestHandler.js
│ ├── index.js
│ ├── listener/
│ │ └── onReceiverMessageListener.js
│ ├── message/
│ │ ├── fileMessageContent.js
│ │ ├── imageMessageContent.js
│ │ ├── mediaMessageContent.js
│ │ ├── message.js
│ │ ├── messageConfig.js
│ │ ├── messageContent.js
│ │ ├── messageContentMediaType.js
│ │ ├── messageContentType.js
│ │ ├── messagePayload.js
│ │ ├── messageStatus.js
│ │ ├── modifyGroupInfoType.js
│ │ ├── myInfoType.js
│ │ ├── notification/
│ │ │ ├── addGroupMemberNotification.js
│ │ │ ├── changeGroupNameNotification.js
│ │ │ ├── createGroupNotification.js
│ │ │ ├── dismissGroupNotification.js
│ │ │ ├── groupNotification.js
│ │ │ ├── kickoffGroupMemberNotification.js
│ │ │ ├── notificationMessageContent.js
│ │ │ ├── quitGroupNotification.js
│ │ │ └── recallMessageNotification.js
│ │ ├── persistFlag.js
│ │ ├── protomessage.js
│ │ ├── protomessageContent.js
│ │ ├── sendMessage.js
│ │ ├── textMessageContent.js
│ │ ├── unknownMessageContent.js
│ │ ├── unsupportMessageContent.js
│ │ ├── videoMessageContent.js
│ │ └── websocketprotomessage.js
│ ├── model/
│ │ ├── conversation.js
│ │ ├── conversationInfo.js
│ │ ├── conversationType.js
│ │ ├── groupInfo.js
│ │ ├── groupMember.js
│ │ ├── groupMemberType.js
│ │ ├── groupType.js
│ │ ├── protoConversationInfo.js
│ │ ├── stateConversationInfo.js
│ │ ├── stateSelectChatMessage.js
│ │ ├── unReadCount.js
│ │ └── userInfo.js
│ ├── store/
│ │ └── localstore.js
│ ├── utils/
│ │ ├── StringUtil.js
│ │ ├── aes.js
│ │ ├── logger.js
│ │ └── timeUtils.js
│ └── websocketcli.js
└── static/
├── .gitkeep
└── css/
└── reset.css
SYMBOL INDEX (494 symbols across 101 files)
FILE: build/check-versions.js
function exec (line 5) | function exec (cmd) {
FILE: build/utils.js
function generateLoaders (line 24) | function generateLoaders (loader, loaderOptions) {
FILE: build/webpack.base.conf.js
function resolve (line 6) | function resolve (dir) {
FILE: src/assets/fonts/iconfont.js
function d (line 1) | function d(){h||(h=!0,a())}
FILE: src/constant/index.js
constant WS_PROTOCOL (line 1) | const WS_PROTOCOL = 'wss';
constant WS_IP (line 2) | const WS_IP = 'backend-websocket.fsharechat.cn/ws';
constant HTTP_IP (line 3) | const HTTP_IP = 'backend-http.fsharechat.cn';
constant WS_PORT (line 5) | const WS_PORT = 9326;
constant HEART_BEAT_INTERVAL (line 6) | const HEART_BEAT_INTERVAL = 25 * 1000;
constant RECONNECT_INTERVAL (line 7) | const RECONNECT_INTERVAL = 30 * 1000;
constant BINTRAY_TYPE (line 8) | const BINTRAY_TYPE = 'blob';
constant CONNECT (line 11) | const CONNECT = 'CONNECT';
constant DISCONNECT (line 12) | const DISCONNECT = 'DISCONNECT';
constant CONNECT_ACK (line 13) | const CONNECT_ACK = 'CONNECT_ACK';
constant PUBLISH (line 14) | const PUBLISH = 'PUBLISH';
constant PUB_ACK (line 15) | const PUB_ACK = 'PUB_ACK';
constant FRP (line 17) | const FRP = 'FRP';
constant FALS (line 19) | const FALS = 'FALS';
constant UPUI (line 20) | const UPUI = 'UPUI';
constant GPGI (line 21) | const GPGI = 'GPGI';
constant GPGM (line 22) | const GPGM = 'GPGM';
constant GAM (line 23) | const GAM = 'GAM';
constant GMI (line 25) | const GMI = 'GMI';
constant GKM (line 26) | const GKM = 'GKM';
constant RMN (line 33) | const RMN = "RMN";
constant GQNUT (line 34) | const GQNUT = "GQNUT";
constant GMURL (line 35) | const GMURL = "GMURL";
constant FAR (line 37) | const FAR = "FAR";
constant FRN (line 38) | const FRN = "FRN";
constant FHR (line 39) | const FHR = "FHR";
constant MMI (line 41) | const MMI = "MMI";
constant LRM (line 42) | const LRM = "LRM";
constant HTTP_HOST (line 44) | const HTTP_HOST = "https://"+HTTP_IP + "/"
constant LOGIN_API (line 45) | const LOGIN_API = HTTP_HOST + "login";
constant SNED_VERIFY_CODE_API (line 46) | const SNED_VERIFY_CODE_API = HTTP_HOST + "send_code";
constant KEY_VUE_DEVICE_ID (line 48) | const KEY_VUE_DEVICE_ID = 'vue-device-id';
constant KEY_VUE_USER_ID (line 49) | const KEY_VUE_USER_ID = 'vue-user-id';
constant KEY_VUE_TOKEN (line 50) | const KEY_VUE_TOKEN = 'vue-token';
constant USER_ID (line 53) | const USER_ID = 'TYTzTz33';
constant CLINET_ID (line 54) | const CLINET_ID = 'bccdb58cfdb34d861576810441000';
constant TOKEN (line 56) | const TOKEN = '6Yz2rQDrtRPRc3j9PesLy0De17uX2RlVcvkxU/UmGEaMamd/kaagwWNTh...
constant UPLOAD_BY_QINIU (line 59) | const UPLOAD_BY_QINIU = false;
constant ERROR_CODE (line 61) | const ERROR_CODE = 400;
constant SUCCESS_CODE (line 62) | const SUCCESS_CODE = 200;
constant CONVERSATION_MAX_MESSAGE_SIZE (line 65) | const CONVERSATION_MAX_MESSAGE_SIZE = 50;
FILE: src/store.js
method initData (line 161) | initData (state) {
method search (line 208) | search (state, value) {
method selectSession (line 212) | selectSession (state, value) {
method selectConversation (line 215) | selectConversation(state,value){
method clearUnreadStatus (line 224) | clearUnreadStatus(state){
method selectFriend (line 231) | selectFriend (state, value) {
method updateFriendList (line 244) | updateFriendList(state,value){
method updateConversationBrief (line 301) | updateConversationBrief(state){
method updateMessageBrief (line 318) | updateMessageBrief(state){
method updateUserInfos (line 333) | updateUserInfos(state,userInfos){
method updateGroupInfos (line 359) | updateGroupInfos(state,groupInfos){
method getGroupInfo (line 381) | getGroupInfo(state,target){
method getGroupMember (line 385) | getGroupMember(state,groupId){
method quitGroup (line 389) | quitGroup(state,groupId){
method deleteConversation (line 393) | deleteConversation(state,groupId){
method updateTempGroupMember (line 412) | updateTempGroupMember(state,groupMembers){
method sendMessage (line 417) | sendMessage (state, sendMessage){
method preAddProtoMessage (line 450) | preAddProtoMessage(state,protoMessage){
method updateSendMessage (line 478) | updateSendMessage(state,updateMessage){
method send (line 494) | send (state) {
method updateConversationInfo (line 522) | updateConversationInfo(state,protoConversationInfo){
method updateConversationIntro (line 603) | updateConversationIntro(state,groupInfos){
method addOldMessage (line 623) | addOldMessage(state,protoMessage){
method addProtoMessage (line 636) | addProtoMessage(state,protoMessage){
method updateProtoMessageUid (line 686) | updateProtoMessageUid(state,updateMessage){
method updateMessageStatus (line 706) | updateMessageStatus(state,updateMessageStatus){
method deleteMessage (line 725) | deleteMessage(state,messageId){
method updateMessageContent (line 742) | updateMessageContent(state,notifyMessage){
method loginOut (line 759) | loginOut(state,message){
method changetFirstLogin (line 787) | changetFirstLogin(state,value){
method getUploadToken (line 792) | getUploadToken(state,value){
method visibilityChange (line 796) | visibilityChange(state,value){
method searchUser (line 800) | searchUser(state,value){
method updateSearchUser (line 804) | updateSearchUser(state,value){
method sendFriendAddRequest (line 814) | sendFriendAddRequest(state,value){
method updateFriendRequest (line 818) | updateFriendRequest(state,value){
method handleFriendRequest (line 836) | handleFriendRequest(state,value){
method updateFriendIds (line 841) | updateFriendIds(state,friendList){
method modifyMyInfo (line 851) | modifyMyInfo(state,value){
method getUserInfos (line 854) | getUserInfos(state,value){
method changeEmptyMessageState (line 857) | changeEmptyMessageState(state,value){
method currentGroupMembers (line 863) | currentGroupMembers(){
method searchedConversationList (line 871) | searchedConversationList(){
method isSingleConversation (line 875) | isSingleConversation(){
method searchedFriendlist (line 883) | searchedFriendlist () {
method onlyFriendlist (line 933) | onlyFriendlist(){
method selectedChat (line 950) | selectedChat (state) {
method selectedFriend (line 973) | selectedFriend (state) {
method messages (line 977) | messages (state) {
method unreadTotalCount (line 984) | unreadTotalCount(state){
FILE: src/webrtc/callEndReason.js
class CallEndReason (line 1) | class CallEndReason {
FILE: src/webrtc/callSession.js
class CallSession (line 2) | class CallSession{
method constructor (line 12) | constructor(voipClient){
method setState (line 17) | setState(state){
method endCall (line 45) | endCall(endCallReason,sender=''){
FILE: src/webrtc/callState.js
class CallState (line 1) | class CallState {
FILE: src/webrtc/engineCallback.js
class EngineCallback (line 1) | class EngineCallback{
method onReceiveCall (line 2) | onReceiveCall(callSession){}
method shouldStartRing (line 4) | shouldStartRing(isIncomming){}
method shouldSopRing (line 6) | shouldSopRing(){}
FILE: src/webrtc/groupCallClient.js
class GroupCallClient (line 16) | class GroupCallClient extends OnReceiverMessageListener {
method constructor (line 24) | constructor(store){
method setCurrentSessionCallback (line 30) | setCurrentSessionCallback(sessionCallback){
method setCurrentEngineCallback (line 34) | setCurrentEngineCallback(engineCallback){
method startCall (line 44) | startCall(target,tos,isAudioOnly){
method answerCall (line 57) | answerCall(audioOnly){
method endCall (line 70) | endCall(tos){
method closeCall (line 83) | closeCall(){
method sendByeMessage (line 89) | sendByeMessage(tos){
method sendSignalMessage (line 94) | sendSignalMessage(msg){
method onReceiveMessage (line 101) | onReceiveMessage(protoMessage){
method handleSignalMsg (line 149) | async handleSignalMsg(payload){
method newSession (line 180) | newSession(clientId, audioOnly, callId){
method sendMessage (line 189) | sendMessage(target,messageConent,tos = ''){
method rejectOtherCall (line 193) | rejectOtherCall(callId,clientId){
method onReceiverAnswer (line 200) | onReceiverAnswer(result){
method onExistingParticipants (line 207) | onExistingParticipants(msg) {
method receiveVideo (line 251) | receiveVideo(sender) {
method onNewParticipant (line 276) | onNewParticipant(name){
method onParticipantLeft (line 280) | onParticipantLeft(name) {
FILE: src/webrtc/message/callAnswerMessageContent.js
class CallAnswerMessageContent (line 5) | class CallAnswerMessageContent extends MessageContent {
method constructor (line 9) | constructor(mentionedType = 0, mentionedTargets = []) {
method digest (line 13) | digest() {
method encode (line 17) | encode() {
method decode (line 31) | decode(payload) {
FILE: src/webrtc/message/callAnswerTMessageContent.js
class CallAnswerTMessageContent (line 6) | class CallAnswerTMessageContent extends MessageContent {
method constructor (line 10) | constructor(mentionedType = 0, mentionedTargets = []) {
method digest (line 14) | digest() {
method encode (line 18) | encode() {
method decode (line 32) | decode(payload) {
FILE: src/webrtc/message/callByeMessageContent.js
class CallByeMessageContent (line 4) | class CallByeMessageContent extends MessageContent {
method constructor (line 7) | constructor(mentionedType = 0, mentionedTargets = []) {
method digest (line 11) | digest() {
method encode (line 15) | encode() {
method decode (line 21) | decode(payload) {
FILE: src/webrtc/message/callModifyMessageContent.js
class CallModifyMessageContent (line 4) | class CallModifyMessageContent extends MessageContent {
method constructor (line 8) | constructor(mentionedType = 0, mentionedTargets = []) {
method digest (line 12) | digest() {
method encode (line 16) | encode() {
method decode (line 30) | decode(payload) {
FILE: src/webrtc/message/callSignalMessageContent.js
class CallSignalMessageContent (line 4) | class CallSignalMessageContent extends MessageContent {
method constructor (line 8) | constructor(mentionedType = 0, mentionedTargets = []) {
method digest (line 12) | digest() {
method encode (line 16) | encode() {
method decode (line 23) | decode(payload) {
FILE: src/webrtc/message/callStartMessageContent.js
class CallStartMessageContent (line 4) | class CallStartMessageContent extends MessageContent {
method constructor (line 12) | constructor(callId, targetId, audioOnly){
method digest (line 19) | digest() {
method encode (line 27) | encode() {
method decode (line 42) | decode(payload) {
FILE: src/webrtc/participant.js
class Participant (line 3) | class Participant {
method constructor (line 9) | constructor(target,sender,groupCallClient){
method getVideoElement (line 16) | getVideoElement(){
method onIceCandidate (line 20) | onIceCandidate(candidate, wp) {
method offerToReceiveVideo (line 31) | offerToReceiveVideo(error, offerSdp, wp){
method dispose (line 42) | dispose() {
FILE: src/webrtc/sessionCallback.js
class SessionCallback (line 1) | class SessionCallback{
method didCallEndWithReason (line 2) | didCallEndWithReason(callEndReason,sender = ''){}
method didChangeState (line 4) | didChangeState(callState){}
method didChangeMode (line 6) | didChangeMode(mode){}
method didCreateLocalVideoTrack (line 8) | didCreateLocalVideoTrack(stream){}
method didReceiveRemoteVideoTrack (line 10) | didReceiveRemoteVideoTrack(stream,sender = ''){}
method didReceiveRemoteAudioTrack (line 12) | didReceiveRemoteAudioTrack(stream){}
method didError (line 14) | didError(error){}
method didGetStats (line 16) | didGetStats(stats){}
FILE: src/webrtc/voipclient.js
class VoipClient (line 14) | class VoipClient extends OnReceiverMessageListener{
method constructor (line 30) | constructor(store){
method setCurrentSessionCallback (line 36) | setCurrentSessionCallback(sessionCallback){
method setCurrentEngineCallback (line 40) | setCurrentEngineCallback(engineCallback){
method startCall (line 44) | startCall(target,isAudioOnly){
method cancelCall (line 58) | cancelCall(){
method answerCall (line 67) | answerCall(audioOnly){
method newSession (line 78) | newSession(clientId, audioOnly, callId){
method rejectOtherCall (line 87) | rejectOtherCall(callId,clientId){
method offerMessage (line 94) | offerMessage(target,messageConent){
method offerMessageByType (line 98) | offerMessageByType(type){
method onReceiveMessage (line 112) | onReceiveMessage(protoMessage){
method handleSignalMsg (line 171) | async handleSignalMsg(payload){
method handleOfferMessage (line 211) | async handleOfferMessage(){
method startPreview (line 245) | async startPreview(){
method createPeerConnection (line 289) | async createPeerConnection() {
method closeCall (line 417) | closeCall() {
method handleGetUserMediaError (line 484) | handleGetUserMediaError(e) {
method reportError (line 503) | reportError(errMessage) {
method log_error (line 507) | log_error(text) {
method log (line 512) | log(text) {
FILE: src/websocket/chatManager.js
class ChatManager (line 1) | class ChatManager {
method addReceiveMessageListener (line 4) | static addReceiveMessageListener(listener){
method onReceiveMessage (line 8) | static onReceiveMessage(protoMessage){
method removeOnReceiveMessageListener (line 14) | static removeOnReceiveMessageListener(){
FILE: src/websocket/future/futureResult.js
class FutureResult (line 1) | class FutureResult {
method constructor (line 5) | constructor(code, result){
FILE: src/websocket/future/promiseResolve.js
class PromiseResolve (line 1) | class PromiseResolve {
method constructor (line 6) | constructor(resolve,timeoutId){
FILE: src/websocket/handler/abstractmessagehandler.js
class AbstractMessageHandler (line 10) | class AbstractMessageHandler extends MessageHandler{
method constructor (line 12) | constructor(vueWebsocket){
method vueWebsocketClient (line 17) | get vueWebsocketClient(){
method processMessage (line 21) | processMessage(proto){
method notifyContent (line 35) | notifyContent(content){
FILE: src/websocket/handler/addGroupMemberHandler.js
class AddGroupMemberHandler (line 4) | class AddGroupMemberHandler extends AbstractMessageHandler {
method match (line 5) | match(proto){
FILE: src/websocket/handler/connectackhandler.js
class ConnectAckHandler (line 5) | class ConnectAckHandler extends AbstractMessageHandler{
method constructor (line 6) | constructor(vueWebsocket){
method match (line 9) | match(protoObj){
method processMessage (line 13) | processMessage(data){
FILE: src/websocket/handler/createGroupHandler.js
class CreateGroupHandler (line 4) | class CreateGroupHandler extends AbstractMessageHandler {
method match (line 5) | match(proto){
FILE: src/websocket/handler/dismissGroupHandler.js
class DismissGroupHandler (line 4) | class DismissGroupHandler extends AbstractMessageHandler{
method match (line 5) | match(proto){
FILE: src/websocket/handler/friendAddRequestHandler.js
class FriendAddRequestHandler (line 5) | class FriendAddRequestHandler extends AbstractMessageHandler{
method match (line 6) | match(proto){
method processMessage (line 10) | processMessage(proto){
FILE: src/websocket/handler/friendRequestHandler.js
class FriendRequestHandler (line 5) | class FriendRequestHandler extends AbstractMessageHandler {
method match (line 6) | match(proto){
method processMessage (line 11) | processMessage(proto){
FILE: src/websocket/handler/getGroupInfoHandler.js
class GetGroupInfoHandler (line 5) | class GetGroupInfoHandler extends AbstractMessageHandler{
method match (line 7) | match(proto){
method processMessage (line 11) | processMessage(proto){
FILE: src/websocket/handler/getGroupMemberHandler.js
class GetGroupMemberHandler (line 5) | class GetGroupMemberHandler extends AbstractMessageHandler {
method match (line 6) | match(proto){
method notifyContent (line 10) | notifyContent(content){
FILE: src/websocket/handler/getMinioUploadUrlHandler.js
class GetMinioUploadUrlHandler (line 4) | class GetMinioUploadUrlHandler extends AbstractMessageHandler {
method match (line 5) | match(proto){
method notifyContent (line 8) | notifyContent(content){
FILE: src/websocket/handler/getUploadtokenHandler.js
class UploadTokenHandler (line 5) | class UploadTokenHandler extends AbstractMessageHandler{
method match (line 6) | match(proto){
method processMessage (line 10) | processMessage(proto){
FILE: src/websocket/handler/getfriendresultHandler.js
class GetFriendResultHandler (line 5) | class GetFriendResultHandler extends AbstractMessageHandler{
method match (line 6) | match(proto){
method processMessage (line 10) | processMessage(proto){
FILE: src/websocket/handler/getuserinfoHandler.js
class GetUserInfoHandler (line 8) | class GetUserInfoHandler extends AbstractMessageHandler{
method match (line 9) | match(proto){
method processMessage (line 13) | processMessage(proto){
FILE: src/websocket/handler/handleFriendRequestHandler.js
class HandleFriendRequestHandler (line 5) | class HandleFriendRequestHandler extends AbstractMessageHandler {
method match (line 6) | match(proto){
method processMessage (line 10) | processMessage(proto){
FILE: src/websocket/handler/kickGroupmemberHandler.js
class KickGroupMemberHandler (line 4) | class KickGroupMemberHandler extends AbstractMessageHandler{
method match (line 5) | match(proto){
FILE: src/websocket/handler/loadRemoteMessageHander.js
class LoadRemoteMessageHandler (line 4) | class LoadRemoteMessageHandler extends AbstractMessageHandler {
method match (line 5) | match(proto){
FILE: src/websocket/handler/messageHandler.js
class MessageHandler (line 1) | class MessageHandler{
method match (line 2) | match(signal){
method processMessage (line 6) | processMessage(data){
FILE: src/websocket/handler/modifyMyInfoHandler.js
class ModifyInfoHandler (line 5) | class ModifyInfoHandler extends AbstractMessageHandler{
method match (line 6) | match(proto){
method processMessage (line 10) | processMessage(proto){
FILE: src/websocket/handler/notifyFriendHandler.js
class NotifyFriendHandler (line 5) | class NotifyFriendHandler extends AbstractMessageHandler {
method match (line 6) | match(proto){
method processMessage (line 10) | processMessage(proto){
FILE: src/websocket/handler/notifyFriendRequestHandler.js
class NotifyFriendRequestHandler (line 6) | class NotifyFriendRequestHandler extends AbstractMessageHandler {
method match (line 7) | match(proto){
method processMessage (line 12) | processMessage(proto){
FILE: src/websocket/handler/notifyMessageHandler.js
class NotifyMessageHandler (line 5) | class NotifyMessageHandler extends AbstractMessageHandler{
method match (line 6) | match(proto){
method processMessage (line 10) | processMessage(proto){
FILE: src/websocket/handler/notifyRecallMessageHandler.js
class NotifyRecallMessageHandler (line 4) | class NotifyRecallMessageHandler extends AbstractMessageHandler {
method match (line 5) | match(proto){
method processMessage (line 9) | processMessage(proto){
FILE: src/websocket/handler/quitGroupHandler.js
class QuitGroupHandler (line 4) | class QuitGroupHandler extends AbstractMessageHandler {
method match (line 5) | match(proto){
FILE: src/websocket/handler/recallMessageHandler.js
class RecallMessageHandler (line 4) | class RecallMessageHandler extends AbstractMessageHandler {
method match (line 5) | match(proto){
method notifyContent (line 9) | notifyContent(content){
FILE: src/websocket/handler/receiveMessageHandler.js
class ReceiveMessageHandler (line 12) | class ReceiveMessageHandler extends AbstractMessageHandler {
method match (line 15) | match(proto){
method processMessage (line 19) | processMessage(proto){
method addProtoMessage (line 38) | addProtoMessage(protoMessage){
FILE: src/websocket/handler/searchUserResultHandler.js
class SearchUserResultHandler (line 6) | class SearchUserResultHandler extends AbstractMessageHandler{
method match (line 7) | match(proto){
method processMessage (line 11) | processMessage(proto){
FILE: src/websocket/handler/sendMessageHandler.js
class SendMessageHandler (line 7) | class SendMessageHandler extends AbstractMessageHandler{
method match (line 9) | match(proto){
method processMessage (line 13) | processMessage(proto){
FILE: src/websocket/handler/setFriendAliasRequestHandler.js
class SetFriendAliasRequestHandler (line 4) | class SetFriendAliasRequestHandler extends AbstractMessageHandler{
method match (line 5) | match(proto){
FILE: src/websocket/index.js
class VueWebSocket (line 42) | class VueWebSocket {
method constructor (line 60) | constructor(){
method connect (line 83) | connect(isReconncect){
method reconnect (line 119) | reconnect(event){
method lastInteractionTime (line 126) | lastInteractionTime(actionTime){
method getLastActionTime (line 130) | getLastActionTime(){
method ping (line 134) | ping(){
method send (line 138) | send(data){
method sendAction (line 151) | sendAction(type,data){
method initHandlerList (line 155) | initHandlerList(){
method processMessage (line 184) | processMessage(data){
method sendConnectMessage (line 196) | sendConnectMessage(){
method sendDisConnectMessage (line 220) | sendDisConnectMessage(){
method getFriend (line 235) | getFriend(version = 0){
method searchUser (line 239) | searchUser(keyword){
method sendFriendAddRequest (line 248) | sendFriendAddRequest(value){
method getFriendRequest (line 252) | getFriendRequest(version){
method handleFriendRequest (line 258) | handleFriendRequest(value){
method getUserInfos (line 265) | getUserInfos(userIds){
method getUserInfo (line 269) | async getUserInfo(userId){
method modifyMyInfo (line 281) | modifyMyInfo(info){
method getGroupInfo (line 290) | getGroupInfo(groupId,refresh){
method getGroupMember (line 296) | async getGroupMember(groupId,refresh){
method addMembers (line 303) | async addMembers(groupId,memberIds){
method kickeMembers (line 317) | async kickeMembers(groupId,memberIds){
method createGroup (line 324) | async createGroup(groupName,memberIds){
method modifyGroupInfo (line 341) | async modifyGroupInfo(info){
method quitGroup (line 345) | async quitGroup(groupId){
method dismissGroup (line 351) | async dismissGroup(groupId){
method recallMessage (line 357) | async recallMessage(messageUid){
method getRemoteMessages (line 363) | async getRemoteMessages(conversation,beforeUid,count){
method pullMessage (line 371) | pullMessage(messageId,type = 0,pullType = 0,sendMessageCount = 0){
method getUploadToken (line 380) | getUploadToken(mediaType){
method getMinioUploadUrl (line 387) | async getMinioUploadUrl(mediaType,key){
method modifyFriendAlias (line 395) | async modifyFriendAlias(targetUid,alias){
method sendPublishMessage (line 408) | sendPublishMessage(subsignal,content,protoMessageId = 0){
method sendMessage (line 443) | sendMessage(protoMessage){
FILE: src/websocket/listener/onReceiverMessageListener.js
class OnReceiverMessageListener (line 1) | class OnReceiverMessageListener {
method onReceiveMessage (line 2) | onReceiveMessage(protoMessage){
FILE: src/websocket/message/fileMessageContent.js
class FileMessageContent (line 5) | class FileMessageContent extends MediaMessageContent {
method constructor (line 10) | constructor(fileOrLocalPath, remotePath) {
method digest (line 18) | digest() {
method encode (line 22) | encode() {
method decode (line 29) | decode(payload) {
method formateSize (line 41) | formateSize(value) { if (null == value || value == '') { return "0 Byt...
FILE: src/websocket/message/imageMessageContent.js
class ImageMessageContent (line 5) | class ImageMessageContent extends MediaMessageContent {
method constructor (line 9) | constructor(fileOrLocalPath, remotePath, thumbnail) {
method digest (line 14) | digest() {
method encode (line 18) | encode() {
method decode (line 25) | decode(payload) {
FILE: src/websocket/message/mediaMessageContent.js
class MediaMessageContent (line 2) | class MediaMessageContent extends MessageContent {
method constructor (line 8) | constructor(messageType, mediaType = 0, fileOrLocalPath, remotePath) {
method encode (line 26) | encode() {
method decode (line 35) | decode(payload) {
FILE: src/websocket/message/message.js
class Message (line 35) | class Message {
method toMessage (line 64) | static toMessage(state,sendMessage){
method conert2Message (line 90) | static conert2Message(sendMessage){
FILE: src/websocket/message/messageConfig.js
class MessageConfig (line 24) | class MessageConfig{
method getMessageContentClazz (line 25) | static getMessageContentClazz(type) {
method convert2MessageContent (line 39) | static convert2MessageContent(from,protoMessageContent){
method getMessageContentPersitFlag (line 58) | static getMessageContentPersitFlag(type) {
method isDisplayableMessage (line 67) | static isDisplayableMessage(protomessage){
FILE: src/websocket/message/messageContent.js
class MessageContent (line 6) | class MessageContent {
method constructor (line 14) | constructor(type, mentionedType = 0, mentionedTargets = []) {
method digest (line 20) | digest() {
method encode (line 27) | encode() {
method decode (line 39) | decode(payload) {
FILE: src/websocket/message/messageContentMediaType.js
class MessageContentMediaType (line 1) | class MessageContentMediaType {
FILE: src/websocket/message/messageContentType.js
class MessageContentType (line 1) | class MessageContentType {
FILE: src/websocket/message/messagePayload.js
class MessagePayload (line 17) | class MessagePayload {
FILE: src/websocket/message/messageStatus.js
class MessageStatus (line 1) | class MessageStatus {
FILE: src/websocket/message/modifyGroupInfoType.js
class ModifyGroupInfoType (line 1) | class ModifyGroupInfoType {
FILE: src/websocket/message/myInfoType.js
class MyInfotype (line 1) | class MyInfotype {
FILE: src/websocket/message/notification/addGroupMemberNotification.js
class AddGroupMemberNotification (line 7) | class AddGroupMemberNotification extends GroupNotificationContent {
method constructor (line 11) | constructor(invitor, invitees) {
method formatNotification (line 17) | formatNotification() {
method encode (line 41) | encode() {
method decode (line 52) | decode(payload) {
FILE: src/websocket/message/notification/changeGroupNameNotification.js
class ChangeGroupNameNotification (line 7) | class ChangeGroupNameNotification extends GroupNotificationContent {
method constructor (line 11) | constructor(operator, name) {
method formatNotification (line 17) | formatNotification() {
method encode (line 25) | encode() {
method decode (line 36) | decode(payload) {
FILE: src/websocket/message/notification/createGroupNotification.js
class CreateGroupNotification (line 7) | class CreateGroupNotification extends GroupNotificationContent {
method constructor (line 11) | constructor(creator, groupName) {
method formatNotification (line 17) | formatNotification() {
method encode (line 25) | encode() {
method decode (line 36) | decode(payload) {
FILE: src/websocket/message/notification/dismissGroupNotification.js
class DismissGroupNotification (line 7) | class DismissGroupNotification extends GroupNotificationContent {
method constructor (line 10) | constructor(operator) {
method formatNotification (line 15) | formatNotification() {
method encode (line 23) | encode() {
method decode (line 33) | decode(payload) {
FILE: src/websocket/message/notification/groupNotification.js
class GroupNotificationContent (line 3) | class GroupNotificationContent extends NotificationMessageContent {
FILE: src/websocket/message/notification/kickoffGroupMemberNotification.js
class KickoffGroupMemberNotification (line 7) | class KickoffGroupMemberNotification extends GroupNotificationContent {
method constructor (line 11) | constructor(operator, kickedMembers) {
method formatNotification (line 17) | formatNotification() {
method encode (line 33) | encode() {
method decode (line 44) | decode(payload) {
FILE: src/websocket/message/notification/notificationMessageContent.js
class NotificationMessageContent (line 2) | class NotificationMessageContent extends MessageContent {
method constructor (line 5) | constructor(type) {
method digest (line 9) | digest(message) {
method formatNotification (line 19) | formatNotification(message) {
FILE: src/websocket/message/notification/quitGroupNotification.js
class QuitGroupNotification (line 7) | class QuitGroupNotification extends GroupNotificationContent {
method constructor (line 10) | constructor(operator) {
method formatNotification (line 15) | formatNotification() {
method encode (line 23) | encode() {
method decode (line 33) | decode(payload) {
FILE: src/websocket/message/notification/recallMessageNotification.js
class RecallMessageNotification (line 6) | class RecallMessageNotification extends NotificationMessageContent {
method constructor (line 10) | constructor(operatorId, messageUid) {
method formatNotification (line 16) | formatNotification() {
method encode (line 24) | encode() {
method decode (line 31) | decode(payload) {
FILE: src/websocket/message/persistFlag.js
class PersistFlag (line 1) | class PersistFlag {
FILE: src/websocket/message/protomessage.js
class ProtoMessage (line 9) | class ProtoMessage {
method toProtoMessage (line 23) | static toProtoMessage(obj){
method convertToProtoMessage (line 57) | static convertToProtoMessage(message){
FILE: src/websocket/message/protomessageContent.js
class ProtoMessageContent (line 3) | class ProtoMessageContent{
method toProtoMessageContent (line 16) | static toProtoMessageContent(content){
method typeToContent (line 32) | static typeToContent(messageContent){
FILE: src/websocket/message/sendMessage.js
class SendMessage (line 1) | class SendMessage{
method constructor (line 6) | constructor(target,messageContent,tos=''){
FILE: src/websocket/message/textMessageContent.js
class TextMessageContent (line 5) | class TextMessageContent extends MessageContent {
method constructor (line 8) | constructor(content, mentionedType = 0, mentionedTargets = []) {
method digest (line 13) | digest() {
method encode (line 17) | encode() {
method decode (line 23) | decode(payload) {
FILE: src/websocket/message/unknownMessageContent.js
class UnknownMessageContent (line 4) | class UnknownMessageContent extends MessageContent {
method constructor (line 7) | constructor(originalPayload) {
method encode (line 12) | encode() {
method decode (line 16) | decode(paylaod) {
method digest (line 20) | digest() {
FILE: src/websocket/message/unsupportMessageContent.js
class UnsupportMessageContent (line 3) | class UnsupportMessageContent extends MessageContent {
method digest (line 5) | digest() {
FILE: src/websocket/message/videoMessageContent.js
class VideoMessageContent (line 5) | class VideoMessageContent extends MediaMessageContent {
method constructor (line 8) | constructor(fileOrLocalPath, remotePath, thumbnail) {
method digest (line 13) | digest() {
method encode (line 17) | encode() {
method decode (line 24) | decode(payload) {
FILE: src/websocket/message/websocketprotomessage.js
class WebSocketProtoMessage (line 12) | class WebSocketProtoMessage {
method constructor (line 16) | constructor(){
method setMessageId (line 20) | setMessageId(messageId){
method setSignal (line 24) | setSignal(signal){
method setSubSignal (line 28) | setSubSignal(subSignal){
method setContent (line 32) | setContent(content){
method toJson (line 36) | toJson(){
FILE: src/websocket/model/conversation.js
class Conversation (line 11) | class Conversation {
method constructor (line 17) | constructor(type, target, line) {
method equal (line 24) | equal(conversation) {
FILE: src/websocket/model/conversationInfo.js
class ConversationInfo (line 1) | class ConversationInfo{
FILE: src/websocket/model/conversationType.js
class ConversationType (line 1) | class ConversationType {
FILE: src/websocket/model/groupInfo.js
class GroupInfo (line 2) | class GroupInfo {
method convert2GroupInfo (line 12) | static convert2GroupInfo(jsonObj){
FILE: src/websocket/model/groupMember.js
class GroupMember (line 1) | class GroupMember {
method convert2GroupMember (line 10) | static convert2GroupMember(jsonObj){
FILE: src/websocket/model/groupMemberType.js
class GroupMemberType (line 1) | class GroupMemberType {
FILE: src/websocket/model/groupType.js
class GroupType (line 1) | class GroupType {
FILE: src/websocket/model/protoConversationInfo.js
class ProtoConversationInfo (line 3) | class ProtoConversationInfo{
FILE: src/websocket/model/stateConversationInfo.js
class StateConversationInfo (line 1) | class StateConversationInfo{
FILE: src/websocket/model/stateSelectChatMessage.js
class StateSelectChateMessage (line 1) | class StateSelectChateMessage{
FILE: src/websocket/model/unReadCount.js
class UnreadCount (line 1) | class UnreadCount {
FILE: src/websocket/model/userInfo.js
class UserInfo (line 1) | class UserInfo {
method convert2UserInfo (line 15) | static convert2UserInfo(jsonObj){
FILE: src/websocket/store/localstore.js
class LocalStore (line 10) | class LocalStore {
method saveConverSations (line 12) | static saveConverSations(value){
method getConversations (line 16) | static getConversations(){
method getLastMessageSeq (line 21) | static getLastMessageSeq(){
method setUploadToken (line 26) | static setUploadToken(key,token){
method getImageUploadToken (line 30) | static getImageUploadToken(){
method setLastMessageSeq (line 37) | static setLastMessageSeq(messageSeq){
method saveMessages (line 41) | static saveMessages(value){
method getMessages (line 45) | static getMessages(){
method saveUserInfoList (line 50) | static saveUserInfoList(value){
method getUserInfoList (line 54) | static getUserInfoList(){
method updateSendMessageCount (line 62) | static updateSendMessageCount(){
method getSendMessageCount (line 72) | static getSendMessageCount(){
method resetSendMessageCount (line 80) | static resetSendMessageCount(){
method getUserId (line 84) | static getUserId(){
method setSelectTarget (line 88) | static setSelectTarget(value){
method getSelectTarget (line 92) | static getSelectTarget(){
method setFriendRequestVersion (line 96) | static setFriendRequestVersion(version){
method getFriendRequestVersion (line 100) | static getFriendRequestVersion(){
method saveMessageId (line 108) | static saveMessageId(messageId){
method getMessageId (line 112) | static getMessageId(){
method clearLocalStore (line 120) | static clearLocalStore(){
FILE: src/websocket/utils/StringUtil.js
class StringUtils (line 1) | class StringUtils {
method b64_to_utf8 (line 2) | static b64_to_utf8(str) {
method utf8_to_b64 (line 7) | static utf8_to_b64(str) {
FILE: src/websocket/utils/aes.js
function decrypt (line 5) | function decrypt (text) {
function encrypt (line 19) | function encrypt(encryptCode,key) {
function convertTimeEncryptCode (line 43) | function convertTimeEncryptCode(encryptCode){
function bin2String (line 76) | function bin2String(array) {
function string2Bin (line 84) | function string2Bin(str) {
function wordToByteArray (line 92) | function wordToByteArray(wordArray) {
function byteArrayToString (line 103) | function byteArrayToString(byteArray) {
FILE: src/websocket/utils/logger.js
class Logger (line 1) | class Logger {
method log (line 2) | static log(text){
FILE: src/websocket/utils/timeUtils.js
class TimeUtils (line 1) | class TimeUtils {
method _formatDate (line 3) | static _formatDate(date, fmt){
method getTimeStringAutoShort2 (line 19) | static getTimeStringAutoShort2(timestamp,mustIncludeTime){
FILE: src/websocket/websocketcli.js
class WebSocketClient (line 5) | class WebSocketClient {
method getDisplayName (line 7) | getDisplayName(userId){
method getPortrait (line 26) | getPortrait(userId){
method createGroup (line 37) | createGroup(groupName,memberIds){
method modifyGroupInfo (line 41) | modifyGroupInfo(info){
method quitGroup (line 45) | quitGroup(groupId){
method dismissGroup (line 49) | dismissGroup(groupId){
method getGroupMember (line 53) | getGroupMember(groupId){
method addMembers (line 57) | addMembers(groupId, memberIds){
method kickeMembers (line 61) | kickeMembers(groupId,memberIds){
method recallMessage (line 65) | recallMessage(messageUid){
method getMinioUploadUrl (line 69) | getMinioUploadUrl(mediaType,key){
method modifyFriendAlias (line 73) | modifyFriendAlias(targetUid,alias){
method getRemoteMessages (line 78) | getRemoteMessages(conversation,beforeUid,count){
Condensed preview — 146 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (459K chars).
[
{
"path": ".babelrc",
"chars": 234,
"preview": "{\n \"presets\": [\n [\"env\", { \"modules\": false }],\n \"stage-2\"\n ],\n \"plugins\": [\"transform-runtime\"],\n \"comments\":"
},
{
"path": ".editorconfig",
"chars": 147,
"preview": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_"
},
{
"path": ".gitignore",
"chars": 77,
"preview": ".DS_Store\nnode_modules/\ndist/\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n"
},
{
"path": ".postcssrc.js",
"chars": 196,
"preview": "// https://github.com/michael-ciniawsky/postcss-load-config\n\nmodule.exports = {\n \"plugins\": {\n // to edit target bro"
},
{
"path": "LICENSE",
"chars": 19408,
"preview": "Creative Commons Attribution-NonCommercial 3.0 Unported\n\nCREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PRO"
},
{
"path": "README.md",
"chars": 3521,
"preview": "\n[](https://gitee.com/comsince/vue-chat)\n[![GitHu"
},
{
"path": "build/build.js",
"chars": 953,
"preview": "require('./check-versions')()\n\nprocess.env.NODE_ENV = 'production'\n\nvar ora = require('ora')\nvar rm = require('rimraf')\n"
},
{
"path": "build/check-versions.js",
"chars": 1257,
"preview": "var chalk = require('chalk')\nvar semver = require('semver')\nvar packageConfig = require('../package.json')\nvar shell = r"
},
{
"path": "build/dev-client.js",
"chars": 245,
"preview": "/* eslint-disable */\nrequire('eventsource-polyfill')\nvar hotClient = require('webpack-hot-middleware/client?noInfo=true&"
},
{
"path": "build/dev-server.js",
"chars": 2444,
"preview": "require('./check-versions')()\n\nvar config = require('../config')\nif (!process.env.NODE_ENV) {\n process.env.NODE_ENV = J"
},
{
"path": "build/utils.js",
"chars": 1979,
"preview": "var path = require('path')\nvar config = require('../config')\nvar ExtractTextPlugin = require('extract-text-webpack-plugi"
},
{
"path": "build/vue-loader.conf.js",
"chars": 307,
"preview": "var utils = require('./utils')\nvar config = require('../config')\nvar isProduction = process.env.NODE_ENV === 'production"
},
{
"path": "build/webpack.base.conf.js",
"chars": 1351,
"preview": "var path = require('path')\nvar utils = require('./utils')\nvar config = require('../config')\nvar vueLoaderConfig = requir"
},
{
"path": "build/webpack.dev.conf.js",
"chars": 1298,
"preview": "var utils = require('./utils')\nvar webpack = require('webpack')\nvar config = require('../config')\nvar merge = require('w"
},
{
"path": "build/webpack.prod.conf.js",
"chars": 3786,
"preview": "var path = require('path')\nvar utils = require('./utils')\nvar webpack = require('webpack')\nvar config = require('../conf"
},
{
"path": "config/dev.env.js",
"chars": 139,
"preview": "var merge = require('webpack-merge')\nvar prodEnv = require('./prod.env')\n\nmodule.exports = merge(prodEnv, {\n NODE_ENV: "
},
{
"path": "config/index.js",
"chars": 1437,
"preview": "// see http://vuejs-templates.github.io/webpack for documentation.\nvar path = require('path')\n\nmodule.exports = {\n buil"
},
{
"path": "config/prod.env.js",
"chars": 48,
"preview": "module.exports = {\n NODE_ENV: '\"production\"'\n}\n"
},
{
"path": "index.html",
"chars": 248,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <title>飞享IM</title>\n <link rel=\"stylesheet\" href=\"stat"
},
{
"path": "index.md",
"chars": 1,
"preview": "\n"
},
{
"path": "package.json",
"chars": 2072,
"preview": "{\n \"name\": \"wechat\",\n \"version\": \"1.1.6\",\n \"description\": \"基于fshare的开源即时通讯web客户端\",\n \"author\": \"comsince\",\n \"private"
},
{
"path": "src/App.vue",
"chars": 805,
"preview": "<template>\n <div class=\"layout-container\" id=\"app-chat\">\n <router-view></router-view>\n <searchfriend></searchfrie"
},
{
"path": "src/assets/fonts/iconfont.css",
"chars": 6142,
"preview": "@font-face {font-family: \"iconfont\";\n src: url('iconfont.eot?t=1592274269382'); /* IE9 */\n src: url('iconfont.eot?t=15"
},
{
"path": "src/assets/fonts/iconfont.js",
"chars": 18565,
"preview": "!function(c){var t,i,o,a,l,h,e,s='<svg><symbol id=\"icon-shipin\" viewBox=\"0 0 1024 1024\"><path d=\"M812.032 102.4H211.968c"
},
{
"path": "src/assets/fonts/iconfont.json",
"chars": 3633,
"preview": "{\n \"id\": \"1698562\",\n \"name\": \"vue-chat\",\n \"font_family\": \"iconfont\",\n \"css_prefix_text\": \"icon-\",\n \"description\": \""
},
{
"path": "src/components/chatlist/chatlist.vue",
"chars": 7005,
"preview": "<!-- 聊天列表 -->\n<template>\n <div class=\"conversationlist\" :style=\"{height: (appHeight-60) + 'px'}\">\n <ul v-loading=\"is"
},
{
"path": "src/components/friendlist/friendlist.vue",
"chars": 3047,
"preview": "<!-- 好友列表 -->\n<template>\n <div class=\"friendlist\" :style=\"{height: (appHeight-60) + 'px'}\">\n \t<ul>\n <li v-bind:ke"
},
{
"path": "src/components/info/info.vue",
"chars": 8327,
"preview": "<!-- 好友信息 -->\n<template>\n <div class=\"Info-wrapper\">\n <div class=\"newfriend\">\n\t\t\t<div class=\"nickname\">{{select"
},
{
"path": "src/components/menu/addtip.vue",
"chars": 1413,
"preview": "<template>\n <div class=\"add-content\">\n <div>\n <a @click=\"showSearchFriendDialog\" class=\"iconsize wx-chat-icon\"><i c"
},
{
"path": "src/components/menu/groupInfo.vue",
"chars": 12951,
"preview": "<template>\n <div id=\"group-info-id\" class=\"group-content\" :style=\"{height: (appHeight-61) + 'px'}\">\n <div clas"
},
{
"path": "src/components/menu/personalCard.vue",
"chars": 10354,
"preview": "<template>\n <div class=\"personal-card\" id=\"personal-card\">\n <div class=\"profile_mini\">\n <div class="
},
{
"path": "src/components/menu/relayMessage.vue",
"chars": 7546,
"preview": "<template>\n <div class=\"relay-message\">\n <el-dialog\n title=\"转发\"\n :visible.sync=\"showRelayMessage"
},
{
"path": "src/components/menu/rightMenu.vue",
"chars": 3847,
"preview": "<template>\n <div v-bind:class=\"menuStyle\" @contextmenu.prevent=\"\">\n <div @click=\"recallMessage\" v-if=\"isFrom\">\n <a"
},
{
"path": "src/components/message/message.vue",
"chars": 23911,
"preview": "<!-- 消息框 -->\n<template>\n\t<div class=\"message\">\n\t\t<header class=\"header\">\n\t\t\t<div class=\"friendname\">{{selectedChat.name}"
},
{
"path": "src/components/mycard/mycard.vue",
"chars": 4471,
"preview": "<!-- 最左边的选择框 -->\n<template>\n\t<div class=\"mycard\" ref=\"mycardRef\">\n\t <header>\n\t \t<img :src=\"user.img\" class=\"avatar"
},
{
"path": "src/components/search/search.vue",
"chars": 1903,
"preview": "<!-- 搜索框 -->\n<template>\n<div class=\"wrapper\">\n\t<div class=\"padding-wrapper\">\n\t\t<div class=\"search-wrapper\">\n\t\t\t<input ty"
},
{
"path": "src/components/text/text.vue",
"chars": 39993,
"preview": "<!-- 文本输入框 -->\n<template>\n<div class=\"text\">\n <div class=\"emoji\">\n <i class=\"icon iconfont icon-biaoqing\" @cli"
},
{
"path": "src/constant/index.js",
"chars": 2025,
"preview": "export const WS_PROTOCOL = 'wss';\nexport const WS_IP = 'backend-websocket.fsharechat.cn/ws';\nexport const HTTP_IP = 'bac"
},
{
"path": "src/main.js",
"chars": 554,
"preview": "// The Vue build version to load with the `import` command\n// (runtime-only or standalone) has been set in webpack.base."
},
{
"path": "src/page/chat/chat.vue",
"chars": 1040,
"preview": "<template>\n\t<div class=\"content\">\n\t\t<div class=\"msglist\">\n\t\t\t<search></search>\n\t\t\t<chatlist></chatlist>\n\t\t</div>\n\t\t<div "
},
{
"path": "src/page/friend/friend.vue",
"chars": 647,
"preview": "<template>\n\t<div class=\"content\">\n\t\t<div class=\"friend-wrapper\">\n\t\t\t<search></search>\n <friendlist></friendlist>\n\t\t"
},
{
"path": "src/page/friend/searchfriend.vue",
"chars": 4446,
"preview": "<template>\n <div class=\"addfrind\">\n <el-dialog\n title=\"添加好友\"\n :visible.sync=\"showSearchFriendDia"
},
{
"path": "src/page/group/creategroup.vue",
"chars": 18885,
"preview": "<template>\n <div class=\"create-group\" v-if=\"showCreateGroupDialog\">\n <el-dialog\n :visible.sync=\"showCreat"
},
{
"path": "src/page/group/groupVideoCall.vue",
"chars": 12607,
"preview": "<template>\n <div class=\"callContent\" v-if=\"showGroupCallVideoDialog\">\n <div class=\"callercontent callshow\" sty"
},
{
"path": "src/page/login/login.vue",
"chars": 6454,
"preview": "<template>\n <!-- 账号输入登录-->\n <div class=\"login_box\">\n <router-link to=\"#\">\n <div class=\"login_close\"></div>\n "
},
{
"path": "src/page/main.vue",
"chars": 1725,
"preview": "<template>\n <div id=\"app\" :class=\"{fullscreen: changeFullScreenMode}\" ref=\"appRef\">\n <div class=\"sidebar\">\n <my"
},
{
"path": "src/permission.js",
"chars": 520,
"preview": "import router from './router'\nimport store from './store'\nconst whiteList = ['/login'] // 不重定向白名单\nrouter.beforeEach((to,"
},
{
"path": "src/router/index.js",
"chars": 761,
"preview": "import Vue from 'vue'\nimport Router from 'vue-router'\n\nVue.use(Router)\n\nconst router = new Router({\n mode: 'history',\n/"
},
{
"path": "src/store.js",
"chars": 43882,
"preview": "import Vue from 'vue'\nimport Vuex from 'vuex'\nimport router from './router'\nimport VueWebSocket from './websocket';\nimpo"
},
{
"path": "src/webrtc/callEndReason.js",
"chars": 486,
"preview": "export default class CallEndReason {\n static REASON_Unknown = 'unknown';\n static REASON_Busy = 'busy';\n static "
},
{
"path": "src/webrtc/callSession.js",
"chars": 1719,
"preview": "import CallState from \"./callState\";\nexport default class CallSession{\n callId;\n clientId;\n audioOnly;\n star"
},
{
"path": "src/webrtc/callState.js",
"chars": 193,
"preview": "export default class CallState {\n static STATUS_IDLE = 0;\n static STATUS_OUTGOING = 1;\n static STATUS_INCOMING "
},
{
"path": "src/webrtc/engineCallback.js",
"chars": 130,
"preview": "export default class EngineCallback{\n onReceiveCall(callSession){}\n\n shouldStartRing(isIncomming){}\n\n shouldSop"
},
{
"path": "src/webrtc/groupCallClient.js",
"chars": 10821,
"preview": "import OnReceiverMessageListener from \"../websocket/listener/onReceiverMessageListener\";\nimport ChatManager from \"../web"
},
{
"path": "src/webrtc/message/callAnswerMessageContent.js",
"chars": 1033,
"preview": "import MessageContent from '../../websocket/message/messageContent';\nimport MessageContentType from '../../websocket/mes"
},
{
"path": "src/webrtc/message/callAnswerTMessageContent.js",
"chars": 1037,
"preview": "\nimport MessageContent from '../../websocket/message/messageContent';\nimport MessageContentType from '../../websocket/me"
},
{
"path": "src/webrtc/message/callByeMessageContent.js",
"chars": 659,
"preview": "\nimport MessageContent from '../../websocket/message/messageContent';\nimport MessageContentType from '../../websocket/me"
},
{
"path": "src/webrtc/message/callModifyMessageContent.js",
"chars": 1032,
"preview": "import MessageContent from '../../websocket/message/messageContent';\nimport MessageContentType from '../../websocket/mes"
},
{
"path": "src/webrtc/message/callSignalMessageContent.js",
"chars": 878,
"preview": "import MessageContent from '../../websocket/message/messageContent';\nimport MessageContentType from '../../websocket/mes"
},
{
"path": "src/webrtc/message/callStartMessageContent.js",
"chars": 1372,
"preview": "import MessageContent from '../../websocket/message/messageContent';\nimport MessageContentType from '../../websocket/mes"
},
{
"path": "src/webrtc/participant.js",
"chars": 1540,
"preview": "import CallSignalMessageContent from \"./message/callSignalMessageContent\";\n\nexport default class Participant {\n targe"
},
{
"path": "src/webrtc/sessionCallback.js",
"chars": 338,
"preview": "export default class SessionCallback{\n didCallEndWithReason(callEndReason,sender = ''){}\n\n didChangeState(callStat"
},
{
"path": "src/webrtc/voipclient.js",
"chars": 19944,
"preview": "import CallStartMessageContent from \"./message/callStartMessageContent\";\nimport ChatManager from \"../websocket/chatMana"
},
{
"path": "src/websocket/chatManager.js",
"chars": 484,
"preview": "export default class ChatManager {\n static onReceiveMessageListeners = [];\n\n static addReceiveMessageListener(list"
},
{
"path": "src/websocket/future/futureResult.js",
"chars": 151,
"preview": "export default class FutureResult {\n code;\n result;\n\n constructor(code, result){\n this.code = code\n "
},
{
"path": "src/websocket/future/promiseResolve.js",
"chars": 198,
"preview": "export default class PromiseResolve {\n resolve;\n timeoutId;\n protoMessageId;\n\n constructor(resolve,timeoutId"
},
{
"path": "src/websocket/handler/abstractmessagehandler.js",
"chars": 1220,
"preview": "import MessageHandler from \"./messageHandler\";\nimport Logger from \"../utils/logger\";\nimport FutureResult from '../future"
},
{
"path": "src/websocket/handler/addGroupMemberHandler.js",
"chars": 278,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUB_ACK, GAM } from \"../../constant\";\n\nexport de"
},
{
"path": "src/websocket/handler/connectackhandler.js",
"chars": 1295,
"preview": "import { CONNECT_ACK } from \"../../constant\";\nimport AbstractMessageHandler from \"./abstractmessagehandler\";\nimport Loca"
},
{
"path": "src/websocket/handler/createGroupHandler.js",
"chars": 299,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUB_ACK, GC, ERROR_CODE, SUCCESS_CODE } from \".."
},
{
"path": "src/websocket/handler/dismissGroupHandler.js",
"chars": 273,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUB_ACK, GD } from \"../../constant\";\n\nexport def"
},
{
"path": "src/websocket/handler/friendAddRequestHandler.js",
"chars": 456,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { FAR,PUB_ACK } from \"../../constant\";\nimport Logg"
},
{
"path": "src/websocket/handler/friendRequestHandler.js",
"chars": 939,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUB_ACK, FRP } from \"../../constant\";\nimport Loc"
},
{
"path": "src/websocket/handler/getGroupInfoHandler.js",
"chars": 684,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { GPGI, PUB_ACK } from \"../../constant\";\nimport Gr"
},
{
"path": "src/websocket/handler/getGroupMemberHandler.js",
"chars": 619,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUB_ACK, GPGM } from \"../../constant\";\nimport Gr"
},
{
"path": "src/websocket/handler/getMinioUploadUrlHandler.js",
"chars": 384,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUB_ACK, GMURL } from \"../../constant\";\n\nexport "
},
{
"path": "src/websocket/handler/getUploadtokenHandler.js",
"chars": 676,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUB_ACK, GQNUT } from \"../../constant\";\nimport L"
},
{
"path": "src/websocket/handler/getfriendresultHandler.js",
"chars": 713,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUB_ACK, FP } from \"../../constant\";\nimport Loca"
},
{
"path": "src/websocket/handler/getuserinfoHandler.js",
"chars": 2700,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUB_ACK, UPUI, ERROR_CODE, SUCCESS_CODE } from \""
},
{
"path": "src/websocket/handler/handleFriendRequestHandler.js",
"chars": 524,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUB_ACK, FHR } from \"../../constant\";\nimport Log"
},
{
"path": "src/websocket/handler/kickGroupmemberHandler.js",
"chars": 278,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUB_ACK, GKM } from \"../../constant\";\n\nexport de"
},
{
"path": "src/websocket/handler/loadRemoteMessageHander.js",
"chars": 281,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUB_ACK, LRM } from \"../../constant\";\n\nexport de"
},
{
"path": "src/websocket/handler/messageHandler.js",
"chars": 119,
"preview": "export default class MessageHandler{\n match(signal){\n return false;\n }\n\n processMessage(data){\n\n }\n}"
},
{
"path": "src/websocket/handler/modifyMyInfoHandler.js",
"chars": 441,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { MMI, PUB_ACK } from \"../../constant\";\nimport Log"
},
{
"path": "src/websocket/handler/notifyFriendHandler.js",
"chars": 565,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { FN, PUBLISH } from \"../../constant\";\nimport Logg"
},
{
"path": "src/websocket/handler/notifyFriendRequestHandler.js",
"chars": 765,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUBLISH, FRN } from \"../../constant\";\nimport Log"
},
{
"path": "src/websocket/handler/notifyMessageHandler.js",
"chars": 745,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUBLISH, MN } from \"../../constant\";\nimport Loca"
},
{
"path": "src/websocket/handler/notifyRecallMessageHandler.js",
"chars": 616,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUBLISH, RMN } from \"../../constant\";\n\nexport de"
},
{
"path": "src/websocket/handler/quitGroupHandler.js",
"chars": 271,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUB_ACK, GQ } from \"../../constant\";\n\nexport def"
},
{
"path": "src/websocket/handler/recallMessageHandler.js",
"chars": 382,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUB_ACK, MR } from \"../../constant\";\n\nexport def"
},
{
"path": "src/websocket/handler/receiveMessageHandler.js",
"chars": 1669,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { MP, PUB_ACK } from \"../../constant\";\nimport Mess"
},
{
"path": "src/websocket/handler/searchUserResultHandler.js",
"chars": 506,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { US,PUB_ACK } from \"../../constant\";\nimport Logge"
},
{
"path": "src/websocket/handler/sendMessageHandler.js",
"chars": 1876,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { MS, PUB_ACK } from \"../../constant\";\nimport Loca"
},
{
"path": "src/websocket/handler/setFriendAliasRequestHandler.js",
"chars": 287,
"preview": "import AbstractMessageHandler from \"./abstractmessagehandler\";\nimport { PUB_ACK, FALS } from \"../../constant\";\n\nexport d"
},
{
"path": "src/websocket/index.js",
"chars": 15643,
"preview": "import {PUBLISH, FP, UPUI, MP, MS, KEY_VUE_USER_ID, KEY_VUE_DEVICE_ID, DISCONNECT, GPGI, GQNUT, US, FAR, FRP, FHR, MMI, "
},
{
"path": "src/websocket/listener/onReceiverMessageListener.js",
"chars": 93,
"preview": "export default class OnReceiverMessageListener {\n onReceiveMessage(protoMessage){\n\n }\n}"
},
{
"path": "src/websocket/message/fileMessageContent.js",
"chars": 1840,
"preview": "import MediaMessageContent from './mediaMessageContent';\nimport MessageContentMediaType from './messageContentMediaType'"
},
{
"path": "src/websocket/message/imageMessageContent.js",
"chars": 864,
"preview": "import MediaMessageContent from './mediaMessageContent';\nimport MessageContentMediaType from './messageContentMediaType'"
},
{
"path": "src/websocket/message/mediaMessageContent.js",
"chars": 1331,
"preview": "import MessageContent from './messageContent'\nexport default class MediaMessageContent extends MessageContent {\n file"
},
{
"path": "src/websocket/message/message.js",
"chars": 4024,
"preview": "import Conversation from '../model/conversation'\nimport MessageStatus from './messageStatus'\nimport store from '../../st"
},
{
"path": "src/websocket/message/messageConfig.js",
"chars": 9189,
"preview": "import PersistFlag from './persistFlag'\nimport UnknownMessageContent from './unknownMessageContent'\nimport MessageConte"
},
{
"path": "src/websocket/message/messageContent.js",
"chars": 1064,
"preview": "import MessagePayload from \"./messagePayload\";\n\n/**\n * 消息类型基类,一般包括文本消息,文件类消息,媒体类消息\n */\nexport default class MessageConte"
},
{
"path": "src/websocket/message/messageContentMediaType.js",
"chars": 209,
"preview": "export default class MessageContentMediaType {\n static General = 0;\n static Image = 1;\n static Voice = 2;\n s"
},
{
"path": "src/websocket/message/messageContentType.js",
"chars": 1330,
"preview": "export default class MessageContentType {\n // 基本消息类型\n static Unknown = 0;\n static Text = 1;\n static Voice = "
},
{
"path": "src/websocket/message/messagePayload.js",
"chars": 714,
"preview": "/**\n * \n \"content\": {\n \"type\": 1, \n \"searchableContent\": \"1234\", \n \"pushContent\""
},
{
"path": "src/websocket/message/messageStatus.js",
"chars": 235,
"preview": "export default class MessageStatus {\n static Sending = 0;\n static Sent = 1;\n static SendFailure = 2;\n static"
},
{
"path": "src/websocket/message/modifyGroupInfoType.js",
"chars": 151,
"preview": "export default class ModifyGroupInfoType {\n static Modify_Group_Name = 0;\n static Modify_Group_Portrait = 1;\n s"
},
{
"path": "src/websocket/message/myInfoType.js",
"chars": 312,
"preview": "export default class MyInfotype {\n static Modify_DisplayName = 0;\n static Modify_Portrait = 1;\n static Modify_G"
},
{
"path": "src/websocket/message/notification/addGroupMemberNotification.js",
"chars": 1716,
"preview": "import MessageContentType from '../messageContentType';\n\nimport GroupNotificationContent from './groupNotification';\nimp"
},
{
"path": "src/websocket/message/notification/changeGroupNameNotification.js",
"chars": 1260,
"preview": "import MessageContentType from \"../messageContentType\";\n\nimport GroupNotificationContent from \"./groupNotification\";\nimp"
},
{
"path": "src/websocket/message/notification/createGroupNotification.js",
"chars": 1268,
"preview": "import MessageContentType from '../messageContentType';\n\nimport GroupNotificationContent from './groupNotification';\nimp"
},
{
"path": "src/websocket/message/notification/dismissGroupNotification.js",
"chars": 1111,
"preview": "import MessageContentType from '../messageContentType';\n\nimport GroupNotificationContent from './groupNotification';\nimp"
},
{
"path": "src/websocket/message/notification/groupNotification.js",
"chars": 174,
"preview": "import NotificationMessageContent from \"./notificationMessageContent\";\n\nexport default class GroupNotificationContent ex"
},
{
"path": "src/websocket/message/notification/kickoffGroupMemberNotification.js",
"chars": 1526,
"preview": "import MessageContentType from \"../messageContentType\";\n\nimport GroupNotificationContent from \"./groupNotification\";\nimp"
},
{
"path": "src/websocket/message/notification/notificationMessageContent.js",
"chars": 529,
"preview": "import MessageContent from \"../messageContent\";\nexport default class NotificationMessageContent extends MessageContent {"
},
{
"path": "src/websocket/message/notification/quitGroupNotification.js",
"chars": 1105,
"preview": "import MessageContentType from '../messageContentType';\n\nimport GroupNotificationContent from './groupNotification';\nimp"
},
{
"path": "src/websocket/message/notification/recallMessageNotification.js",
"chars": 1121,
"preview": "import NotificationMessageContent from './notificationMessageContent'\nimport MessageContentType from '../messageContentT"
},
{
"path": "src/websocket/message/persistFlag.js",
"chars": 149,
"preview": "export default class PersistFlag {\n static No_Persist = 0;\n static Persist = 1;\n static Persist_And_Count = 3;\n"
},
{
"path": "src/websocket/message/protomessage.js",
"chars": 2574,
"preview": "import MessageStatus from \"./messageStatus\";\nimport ConversationType from \"../model/conversationType\";\nimport ProtoMessa"
},
{
"path": "src/websocket/message/protomessageContent.js",
"chars": 1887,
"preview": "import MessageContentType from \"./messageContentType\";\n\nexport default class ProtoMessageContent{\n type;\n searchab"
},
{
"path": "src/websocket/message/sendMessage.js",
"chars": 230,
"preview": "export default class SendMessage{\n target;\n messageContent;\n tos;\n\n constructor(target,messageContent,tos=''"
},
{
"path": "src/websocket/message/textMessageContent.js",
"chars": 707,
"preview": "import MessageContent from './messageContent'\nimport MessagePayload from './messagePayload';\nimport MessageContentType f"
},
{
"path": "src/websocket/message/unknownMessageContent.js",
"chars": 501,
"preview": "import MessageContent from \"./messageContent\";\nimport MessageContentType from \"./messageContentType\";\n\nexport default cl"
},
{
"path": "src/websocket/message/unsupportMessageContent.js",
"chars": 191,
"preview": "import MessageContent from \"./messageContent\";\n\nexport default class UnsupportMessageContent extends MessageContent {\n\n "
},
{
"path": "src/websocket/message/videoMessageContent.js",
"chars": 832,
"preview": "import MediaMessageContent from './mediaMessageContent'\nimport MessageContentMediaType from './messageContentMediaType';"
},
{
"path": "src/websocket/message/websocketprotomessage.js",
"chars": 855,
"preview": "\n/**\n * websocket json 主协议\n * \n * {\n\t\"signal\": \"connect\",\n\t\"sub_signal\": \"conect_ack\",\n\t\"message_id\": 0,\n\t\"content\": \"\"\n"
},
{
"path": "src/websocket/model/conversation.js",
"chars": 692,
"preview": "import ConversationType from \"./conversationType\";\n\n/**\n * \n \"conversation\":{\n \"conversationType\": 0, "
},
{
"path": "src/websocket/model/conversationInfo.js",
"chars": 184,
"preview": "export default class ConversationInfo{\n conversation = {};\n lastMessage = {};\n timestamp = 0;\n draft = '';\n "
},
{
"path": "src/websocket/model/conversationType.js",
"chars": 135,
"preview": "export default class ConversationType {\n static Single = 0;\n static Group = 1;\n static ChatRoom = 2;\n static"
},
{
"path": "src/websocket/model/groupInfo.js",
"chars": 674,
"preview": "import GroupType from './groupType'\nexport default class GroupInfo {\n target = '';\n name = '';\n portrait = '';\n"
},
{
"path": "src/websocket/model/groupMember.js",
"chars": 465,
"preview": "export default class GroupMember {\n groupId = '';\n memberId = '';\n alias = '';\n type = 0;\n updateDt = 0;\n"
},
{
"path": "src/websocket/model/groupMemberType.js",
"chars": 109,
"preview": "export default class GroupMemberType {\n static Normal = 0;\n static Manager = 1;\n static Owner = 2;\n}"
},
{
"path": "src/websocket/model/groupType.js",
"chars": 105,
"preview": "export default class GroupType {\n static Normal = 0;\n static Free = 1;\n static Restricted = 2;\n}"
},
{
"path": "src/websocket/model/protoConversationInfo.js",
"chars": 266,
"preview": "import UnreadCount from \"./unReadCount\";\n\nexport default class ProtoConversationInfo{\n conversationType;\n target;\n"
},
{
"path": "src/websocket/model/stateConversationInfo.js",
"chars": 119,
"preview": "export default class StateConversationInfo{\n name;\n img;\n //ProtoConversationInfo\n conversationInfo = {};\n}"
},
{
"path": "src/websocket/model/stateSelectChatMessage.js",
"chars": 98,
"preview": "export default class StateSelectChateMessage{\n name = '';\n target;\n protoMessages = [];\n}"
},
{
"path": "src/websocket/model/unReadCount.js",
"chars": 141,
"preview": "export default class UnreadCount {\n // 单聊未读数\n unread = 0;\n // 群聊@数\n unreadMention = 0;\n // 群聊@All数\n un"
},
{
"path": "src/websocket/model/userInfo.js",
"chars": 892,
"preview": "export default class UserInfo {\n uid = '';\n name = '';\n displayName = '';\n gender = 0;\n portrait = '';\n "
},
{
"path": "src/websocket/store/localstore.js",
"chars": 3232,
"preview": "import { KEY_VUE_USER_ID } from \"../../constant\";\n\n/**\n * 本地缓存,包括如下基本信息\n * 消息缓存\n * 消息当前序列号,用于消息拉取\n * 朋友列表信息\n * 群组信息\n */\n"
},
{
"path": "src/websocket/utils/StringUtil.js",
"chars": 220,
"preview": "export default class StringUtils {\n static b64_to_utf8(str) {\n return decodeURIComponent(escape(atob(str)));\n "
},
{
"path": "src/websocket/utils/aes.js",
"chars": 3267,
"preview": "\nimport CryptoJS from 'crypto-js'\nconst iv = CryptoJS.enc.Base64.parse('ABEiM0RVZnd4eXp7fH1+fw==');\n\nexport function dec"
},
{
"path": "src/websocket/utils/logger.js",
"chars": 154,
"preview": "export default class Logger {\n static log(text){\n var time = new Date();\n console.log(\"[\" + time.toLocaleTi"
},
{
"path": "src/websocket/utils/timeUtils.js",
"chars": 4401,
"preview": "export default class TimeUtils {\n //参考链接: https://juejin.im/entry/5c7103f5f265da2dab17d1a6\n static _formatDate(dat"
},
{
"path": "src/websocket/websocketcli.js",
"chars": 2461,
"preview": "import Logger from \"./utils/logger\";\nimport vuexStore from '../store'\n\n\nexport class WebSocketClient {\n \n getDispl"
},
{
"path": "static/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "static/css/reset.css",
"chars": 2072,
"preview": "html, body, div, span, applet, object, iframe,\nh1, h2, h3, h4, h5, h6, p, blockquote, pre,\na, abbr, acronym, address, bi"
}
]
About this extraction
This page contains the full source code of the fsharechat/vue-chat GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 146 files (414.9 KB), approximately 106.7k tokens, and a symbol index with 494 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.