Full Code of fsharechat/vue-chat for AI

master 948ffd3a4082 cached
146 files
414.9 KB
106.7k tokens
494 symbols
1 requests
Download .txt
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
================================================

[![star](https://gitee.com/comsince/vue-chat/badge/star.svg?theme=white)](https://gitee.com/comsince/vue-chat)
[![GitHub stars](https://img.shields.io/github/stars/comsince/vue-chat?style=social)](https://github.com/comsince/vue-chat)

# 飞享

![image](http://image.comsince.cn/fx-chat.png)

**NOTE:** [飞享]IM系统开始进行商业化探索,欢迎有需要的`个人`,`企业`, `工作室`使用,关于授权合作事项,请咨询QQ `1282212195` 

该项目是`飞享`聊天系统客户端源码vue即时通讯web端实现,使用websocket进行消息通讯,支持文本,图片类型发送,支持实时音视频,支持音视频与[android-chat](https://github.com/fsharechat/android-chat)客户端互通

# 项目截图

* 消息提示

![image](./attachment/vue-chat-unread.png)

* 文字消息

![image](./attachment/vue-chat.png)

* 图片消息

![image](./attachment/vue-chat-pic.png)

* 视频消息

![image](./attachment/vue-chat-video.png)

# 项目演示

* [项目公测地址](https://chat.comsince.cn)
* 请选择其中任何一个帐号密码进行登录即可  

```properties
帐号:13800000000, 13800000001, 13800000002
密码:556677
```
* 暂时停止手机验证码注册登录,后续开通QQ群里面通知

## 版本规划
### V1.0.0
* 登录认证流程
* 实现朋友列表展示,用户信息获取
* 会话信息拉取,会话消息缓存
* 纯文本消息通讯
* 支持图片,视频消息展示
* 群会话功能

### V1.0.1
* 增加全屏幕模式支持,点击用户头像即可切换

![image](https://user-gold-cdn.xitu.io/2020/4/13/171719952947e62a?w=1518&h=655&f=png&s=170160)

### V1.0.2
* 计划增加一对一音视频聊天功能
* 实现与[android](https://github.com/fsharechat/android-chat)客户端音视频互通

### V1.0.3
* 增加好友搜索,好友添加功能,形成功能闭环

### V1.0.4
* 群组用户列表功能

### V1.0.5
* 增加websocket异步回调接口
* 增加创建群组功能
* 退出群聊
* 撤回消息
* 群组踢人与拉人
* 修改群名称

![image](https://user-gold-cdn.xitu.io/2020/5/8/171f4c271ba2b4dd?w=2064&h=1144&f=png&s=428322)

### 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>地&nbsp&nbsp&nbsp区</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 = '
Download .txt
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
Download .txt
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[![star](https://gitee.com/comsince/vue-chat/badge/star.svg?theme=white)](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.

Copied to clipboard!