├── static └── .gitkeep ├── .eslintignore ├── config ├── prod.env.js ├── test.env.js ├── dev.env.js └── index.js ├── src ├── assets │ ├── 2.jpg │ ├── default.mp3 │ └── reset.css ├── components │ ├── Emoji │ │ ├── emoji │ │ │ ├── 0.png │ │ │ ├── 1.png │ │ │ ├── 10.png │ │ │ ├── 11.png │ │ │ ├── 12.png │ │ │ ├── 13.png │ │ │ ├── 14.png │ │ │ ├── 15.png │ │ │ ├── 16.png │ │ │ ├── 17.png │ │ │ ├── 18.png │ │ │ ├── 19.png │ │ │ ├── 2.png │ │ │ ├── 20.png │ │ │ ├── 21.png │ │ │ ├── 22.png │ │ │ ├── 23.png │ │ │ ├── 24.png │ │ │ ├── 25.png │ │ │ ├── 26.png │ │ │ ├── 27.png │ │ │ ├── 28.png │ │ │ ├── 29.png │ │ │ ├── 3.png │ │ │ ├── 30.png │ │ │ ├── 31.png │ │ │ ├── 32.png │ │ │ ├── 33.png │ │ │ ├── 34.png │ │ │ ├── 35.png │ │ │ ├── 36.png │ │ │ ├── 37.png │ │ │ ├── 38.png │ │ │ ├── 39.png │ │ │ ├── 4.png │ │ │ ├── 40.png │ │ │ ├── 41.png │ │ │ ├── 42.png │ │ │ ├── 43.png │ │ │ ├── 44.png │ │ │ ├── 45.png │ │ │ ├── 46.png │ │ │ ├── 47.png │ │ │ ├── 48.png │ │ │ ├── 49.png │ │ │ ├── 5.png │ │ │ ├── 50.png │ │ │ ├── 51.png │ │ │ ├── 52.png │ │ │ ├── 53.png │ │ │ ├── 54.png │ │ │ ├── 55.png │ │ │ ├── 56.png │ │ │ ├── 6.png │ │ │ ├── 7.png │ │ │ ├── 8.png │ │ │ └── 9.png │ │ └── Emoji.vue │ ├── Contacts │ │ ├── Contacts.vue │ │ └── ContactCard.vue │ ├── FriendList │ │ └── FriendList.vue │ ├── ChatList │ │ └── ChatList.vue │ ├── GroupList │ │ └── GroupList.vue │ ├── ChatLog │ │ └── ChatLog.vue │ ├── Header │ │ └── TheHeader.vue │ └── ChatBox │ │ └── ChatBox.vue ├── filters │ └── filters.js ├── main.js ├── util │ ├── storage.js │ └── utils.js ├── directives │ └── drag.js ├── skin │ ├── white.scss │ ├── red.scss │ └── blue.scss ├── store │ └── store.js └── im.vue ├── test ├── unit │ ├── .eslintrc │ ├── specs │ │ ├── Hello.spec.js │ │ └── HelloWorld.spec.js │ ├── index.js │ └── karma.conf.js └── e2e │ ├── specs │ └── test.js │ ├── custom-assertions │ └── elementCount.js │ ├── runner.js │ └── nightwatch.conf.js ├── .npmrc ├── .editorconfig ├── .npmignore ├── .postcssrc.js ├── index.html ├── demo ├── main.js ├── im.vue └── lists.js ├── .gitignore ├── .babelrc ├── LOG.md ├── .eslintrc.js ├── package.json ├── README.md └── LICENSE /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/assets/2.jpg -------------------------------------------------------------------------------- /src/assets/default.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/assets/default.mp3 -------------------------------------------------------------------------------- /src/components/Emoji/emoji/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/0.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/1.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/10.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/11.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/12.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/13.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/14.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/15.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/16.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/17.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/18.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/19.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/2.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/20.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/21.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/22.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/23.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/24.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/25.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/26.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/27.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/28.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/29.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/3.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/30.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/31.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/32.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/33.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/34.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/35.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/36.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/37.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/38.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/39.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/4.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/40.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/41.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/42.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/43.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/43.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/44.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/45.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/46.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/46.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/47.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/48.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/49.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/49.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/5.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/50.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/51.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/51.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/52.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/53.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/53.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/54.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/54.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/55.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/56.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/6.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/7.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/8.png -------------------------------------------------------------------------------- /src/components/Emoji/emoji/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mt51/vue-im/HEAD/src/components/Emoji/emoji/9.png -------------------------------------------------------------------------------- /test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "expect": true, 7 | "sinon": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var devEnv = require('./dev.env') 3 | 4 | module.exports = merge(devEnv, { 5 | NODE_ENV: '"testing"' 6 | }) 7 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | sass_binary_site=https://npm.taobao.org/mirrors/node-sass/ 2 | chromedriver_cdnurl=https://npm.taobao.org/mirrors/chromedriver 3 | phantomjs_cdnurl=https://bitbucket.org/ariya/phantomjs/downloads -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | build/ 2 | config/ 3 | demo/ 4 | test/ 5 | index.html 6 | src/ 7 | static/ 8 | .babelrc 9 | .gitignore 10 | .eslintrc.js 11 | .eslintignore 12 | .npmrc 13 | .postcssrc.js 14 | 15 | *.log -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | vue-im 6 | 7 | 8 |
9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /demo/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueIM from '../src/main.js' 3 | import IM from './im.vue' 4 | 5 | Vue.use(VueIM) 6 | 7 | // 开启debug模式 8 | Vue.config.debug = true 9 | 10 | new Vue({ // eslint-disable-line no-new 11 | el: '#app', 12 | render: h => h(IM) 13 | }) 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | test/unit/coverage 7 | test/e2e/reports 8 | selenium-debug.log 9 | 10 | # Editor directories and files 11 | .idea 12 | *.suo 13 | *.ntvs* 14 | *.njsproj 15 | *.sln 16 | 17 | *.tgz 18 | dist/ 19 | -------------------------------------------------------------------------------- /test/unit/specs/Hello.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Hello from '@/components/hello/Hello' 3 | 4 | describe('Hello.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(Hello) 7 | const vm = new Constructor().$mount() 8 | expect(vm.$el.textContent) 9 | .to.equal('This is hello from rs-ui.') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/unit/specs/HelloWorld.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import HelloWorld from '@/components/HelloWorld' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(HelloWorld) 7 | const vm = new Constructor().$mount() 8 | expect(vm.$el.querySelector('.hello h1').textContent) 9 | .to.equal('Welcome to Your Vue.js App') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/unit/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.config.productionTip = false 4 | 5 | // require all test files (files that ends with .spec.js) 6 | const testsContext = require.context('./specs', true, /\.spec$/) 7 | testsContext.keys().forEach(testsContext) 8 | 9 | // require all src files except main.js for coverage. 10 | // you can also change this to match only the subset of files that 11 | // you want coverage for. 12 | const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/) 13 | srcContext.keys().forEach(srcContext) 14 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": [ 12 | "transform-runtime", 13 | [ 14 | "component", 15 | { 16 | "libraryName": "element-ui", 17 | "styleLibraryName": "theme-chalk" 18 | } 19 | ] 20 | ], 21 | "env": { 22 | "test": { 23 | "presets": ["env", "stage-2"], 24 | "plugins": ["istanbul"] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LOG.md: -------------------------------------------------------------------------------- 1 | # 更新日志 2 | 3 | - **2.3.1** 修复问题 4 | 1. 修复客服模式和群组头像名称显示异常 5 | 6 | - **2.2.0** 代码重构 7 | 1. 优化换肤功能 8 | 2. 使用简单的状态管理 9 | 3. 修复搜索功能和brief模式 10 | 4. 修复已知的其他问题 11 | 12 | 13 | - **2.1.0** 更新界面结构,将历史会话和联系人分离 14 | 1. 添加换肤功能 15 | 2. 添加好友分组显示 16 | 3. 添加群组列表显示,添加接口显示群组成员 17 | 4. 修复聊天记录查看问题,添加事件接口 18 | 5. 搜索功能、最小化功能和brief模式暂时不可用 19 | 6. 修复已知的其他问题 20 | 21 | - **2.0.0** 更新界面风格,其他细节调整 22 | 1. 删除除拖拽功能 23 | 2. 修复发送图片功能 24 | 3. 调整代码结构 25 | 4. 其他已知问题完善 26 | 27 | 28 | - **1.0.8** fix brief 模式下没有设置当前聊天的用户 29 | - **1.0.7** 第一次使用,然后刷新之后发送出错 30 | - **1.0.4** 表情图片路径错误,默认不开启声音提示 -------------------------------------------------------------------------------- /test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function (browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .assert.elementPresent('.hello') 15 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 16 | .assert.elementCount('img', 1) 17 | .end() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/filters/filters.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | moment.locale('zh-cn') 4 | 5 | export function formatDate (value, type) { 6 | if (!value) { 7 | return value 8 | } 9 | const now = Date.now() 10 | const diff = now - value 11 | const oneDay = 24 * 60 * 60 * 1000 12 | if (diff < oneDay) { 13 | return moment(value).format('HH:mm') 14 | } else if (diff < 2 * oneDay) { 15 | return '昨天' 16 | } else if (diff < 7 * oneDay && diff > 2 * oneDay) { 17 | return moment(value).format('dddd') 18 | } else if (type) { 19 | return moment(value).format('YYYY-MM-DD HH:mm') 20 | } else { 21 | return '' 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | jquery: true 12 | }, 13 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 14 | extends: 'standard', 15 | // required to lint *.vue files 16 | plugins: [ 17 | 'html' 18 | ], 19 | // add your custom rules here 20 | 'rules': { 21 | // allow paren-less arrow functions 22 | 'arrow-parens': 0, 23 | // allow async-await 24 | 'generator-star-spacing': 0, 25 | // allow debugger during development 26 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import IM from './im' 2 | import { Button, Dialog, Checkbox, Transfer, Input, Message, Pagination, Upload } from 'element-ui' 3 | import 'font-awesome/css/font-awesome.min.css' 4 | import '@/assets/reset.css' 5 | import '@/skin/red.scss' 6 | import '@/skin/blue.scss' 7 | import '@/skin/white.scss' 8 | 9 | const VueIM = {} 10 | 11 | VueIM.install = function (Vue, opts) { 12 | Vue.component('vue-im', IM) 13 | Vue.prototype.$message = Message 14 | Vue.use(Dialog) 15 | Vue.use(Checkbox) 16 | Vue.use(Transfer) 17 | Vue.use(Input) 18 | Vue.use(Pagination) 19 | Vue.use(Upload) 20 | Vue.use(Button) 21 | } 22 | 23 | if (typeof window !== 'undefined' && window.Vue) { 24 | VueIM.install(window.Vue) 25 | } 26 | 27 | export default VueIM 28 | -------------------------------------------------------------------------------- /src/util/storage.js: -------------------------------------------------------------------------------- 1 | const saveData = function (key, data) { 2 | let localData = window.localStorage.getItem('VueIM') 3 | if (!localData) { 4 | localData = { 5 | [key]: data 6 | } 7 | } else { 8 | localData = JSON.parse(localData) 9 | localData[key] = data 10 | } 11 | window.localStorage.setItem('VueIM', JSON.stringify(localData)) 12 | } 13 | 14 | const readData = function (key) { 15 | const localData = window.localStorage.getItem('VueIM') 16 | return localData ? JSON.parse(localData)[key] : localData 17 | } 18 | 19 | const getItem = function (name) { 20 | return window.localStorage.getItem(name) 21 | } 22 | 23 | const setItem = function (name, data) { 24 | return window.localStorage.setItem(name, data) 25 | } 26 | 27 | export default { 28 | saveData, 29 | readData, 30 | getItem, 31 | setItem 32 | } 33 | -------------------------------------------------------------------------------- /test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // the name of the method is the filename. 3 | // can be used in tests like this: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // for how to write custom assertions see 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | exports.assertion = function (selector, count) { 10 | this.message = 'Testing if element <' + selector + '> has count: ' + count 11 | this.expected = count 12 | this.pass = function (val) { 13 | return val === this.expected 14 | } 15 | this.value = function (res) { 16 | return res.value 17 | } 18 | this.command = function (cb) { 19 | var self = this 20 | return this.api.execute(function (selector) { 21 | return document.querySelectorAll(selector).length 22 | }, [selector], function (res) { 23 | cb.call(self, res) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | // This is a karma config file. For more details see 2 | // http://karma-runner.github.io/0.13/config/configuration-file.html 3 | // we are also using it with karma-webpack 4 | // https://github.com/webpack/karma-webpack 5 | 6 | var webpackConfig = require('../../build/webpack.test.conf') 7 | 8 | module.exports = function (config) { 9 | config.set({ 10 | // to run in additional browsers: 11 | // 1. install corresponding karma launcher 12 | // http://karma-runner.github.io/0.13/config/browsers.html 13 | // 2. add it to the `browsers` array below. 14 | browsers: ['PhantomJS'], 15 | frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'], 16 | reporters: ['spec', 'coverage'], 17 | files: ['./index.js'], 18 | preprocessors: { 19 | './index.js': ['webpack', 'sourcemap'] 20 | }, 21 | webpack: webpackConfig, 22 | webpackMiddleware: { 23 | noInfo: true 24 | }, 25 | coverageReporter: { 26 | dir: './coverage', 27 | reporters: [ 28 | { type: 'lcov', subdir: '.' }, 29 | { type: 'text-summary' } 30 | ] 31 | } 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing' 3 | var server = require('../../build/dev-server.js') 4 | 5 | server.ready.then(() => { 6 | // 2. run the nightwatch test suite against it 7 | // to run in additional browsers: 8 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings" 9 | // 2. add it to the --env flag below 10 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 11 | // For more information on Nightwatch's config file, see 12 | // http://nightwatchjs.org/guide#settings-file 13 | var opts = process.argv.slice(2) 14 | if (opts.indexOf('--config') === -1) { 15 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) 16 | } 17 | if (opts.indexOf('--env') === -1) { 18 | opts = opts.concat(['--env', 'chrome']) 19 | } 20 | 21 | var spawn = require('cross-spawn') 22 | var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) 23 | 24 | runner.on('exit', function (code) { 25 | server.close() 26 | process.exit(code) 27 | }) 28 | 29 | runner.on('error', function (err) { 30 | server.close() 31 | throw err 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/gettingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/util/utils.js: -------------------------------------------------------------------------------- 1 | export function typeOf (data) { 2 | const toString = Object.prototype.toString 3 | const map = { 4 | '[object Boolean]': 'boolean', 5 | '[object Number]': 'number', 6 | '[object String]': 'string', 7 | '[object Function]': 'function', 8 | '[object Array]': 'array', 9 | '[object Date]': 'date', 10 | '[object RegExp]': 'regExp', 11 | '[object Undefined]': 'undefined', 12 | '[object Null]': 'null', 13 | '[object Object]': 'object' 14 | } 15 | return map[toString.call(data)] 16 | } 17 | 18 | export function deepCopy (data) { 19 | const t = typeOf(data) 20 | let o 21 | if (t === 'array') { 22 | o = [] 23 | } else if (t === 'object') { 24 | o = {} 25 | } else { 26 | return data 27 | } 28 | if (t === 'array') { 29 | for (let i = 0; i < data.length; i++) { 30 | o.push(deepCopy(data[i])) 31 | } 32 | } else if (t === 'object') { 33 | for (let i in data) { 34 | o[i] = deepCopy(data[i]) 35 | } 36 | } 37 | return o 38 | } 39 | 40 | export function device () { 41 | const agent = navigator.userAgent.toLowerCase() 42 | return (!!window.ActiveXObject || 'ActiveXObject' in window) ? ( 43 | (agent.match(/msie\s(\d+)/) || [])[1] || '11' 44 | ) : false 45 | } 46 | 47 | export function objIsEmpty (obj) { 48 | return Object.keys(obj).length === 0 49 | } 50 | -------------------------------------------------------------------------------- /src/components/Emoji/Emoji.vue: -------------------------------------------------------------------------------- 1 | 6 | 22 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../dist/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'static', 10 | assetsPublicPath: '/', 11 | productionSourceMap: true, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'], 18 | // Run the build command with an extra argument to 19 | // View the bundle analyzer report after build finishes: 20 | // `npm run build --report` 21 | // Set to `true` or `false` to always turn it on or off 22 | bundleAnalyzerReport: process.env.npm_config_report 23 | }, 24 | dev: { 25 | env: require('./dev.env'), 26 | port: 7080, 27 | autoOpenBrowser: true, 28 | assetsSubDirectory: 'static', 29 | assetsPublicPath: '/', 30 | proxyTable: {}, 31 | // CSS Sourcemaps off by default because relative paths are "buggy" 32 | // with this option, according to the CSS-Loader README 33 | // (https://github.com/webpack/css-loader#sourcemaps) 34 | // In our experience, they generally work as expected, 35 | // just be aware of this issue when enabling this option. 36 | cssSourceMap: false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/assets/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } 49 | html, body{ 50 | width: 100%; 51 | height: 100%; 52 | } 53 | .el-checkbox+.el-checkbox { 54 | margin-left: 0; 55 | display: block; 56 | } 57 | .el-checkbox { 58 | display: block; 59 | } 60 | .el-transfer { 61 | margin-left: 56px; 62 | } -------------------------------------------------------------------------------- /src/directives/drag.js: -------------------------------------------------------------------------------- 1 | export default { 2 | bind (el, binding) { 3 | let params = { 4 | flag: false, 5 | target: null 6 | } 7 | document.onselectstart = function () { 8 | return false 9 | } 10 | el.onmousedown = function (e) { 11 | const pageWidth = document.documentElement.clientWidth 12 | const pageHeight = document.documentElement.clientHeight 13 | e = e || window.event 14 | params.target = el._dragTarget 15 | params.flag = true 16 | params.mouseX = e.clientX 17 | params.mouseY = e.clientY 18 | params.initX = params.target.offsetLeft 19 | params.initY = params.target.offsetTop 20 | params.targetWidth = params.target.offsetWidth 21 | params.targetHeight = params.target.offsetHeight 22 | params.right = pageWidth - params.targetWidth 23 | params.bottom = pageHeight - params.targetHeight 24 | } 25 | el.onmousemove = function (e) { 26 | if (params.flag) { 27 | let currentX = e.clientX - params.mouseX + params.initX 28 | let currentY = e.clientY - params.mouseY + params.initY 29 | currentX = currentX <= 0 ? 0 : currentX 30 | currentY = currentY <= 0 ? 0 : currentY 31 | currentX = currentX >= params.right ? params.right : currentX 32 | currentY = currentY >= params.bottom ? params.bottom : currentY 33 | params.target.style.left = currentX + params.targetWidth / 2 + 'px' 34 | params.target.style.top = currentY + params.targetHeight / 2 + 'px' 35 | } 36 | } 37 | el.onmouseup = function () { 38 | params.flag = false 39 | } 40 | el.onmouseleave = function () { 41 | params.flag = false 42 | } 43 | }, 44 | update (el, binding) { 45 | el._dragTarget = binding.value 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/skin/white.scss: -------------------------------------------------------------------------------- 1 | $mainBgColor: #e45050; 2 | $searchInputBgColor: #fff; 3 | $searchInputBdColor: #999; 4 | $searchInputTextColor: #a6b5d7; 5 | $topToolItemColor: #999; 6 | $middleBgColor: #fff; 7 | $middleBdColor: #ccc; 8 | $middleItemTextColor: #363e47; 9 | $contactLeftBgColor: #fff; 10 | $contactLeftBdColor: #ccc; 11 | $contactLeftTextColor: #363e47; 12 | $contactTabBdColor: #2f363e; 13 | $contactFGActiveBgColor: #f5f5f5; 14 | $contactFIActiveBgColor: #d5d5d5; 15 | .white { 16 | .im-header { 17 | background: #fff; 18 | border: 1px solid #ccc; 19 | .top-search { 20 | .search-box { 21 | background: $searchInputBgColor; 22 | color: $searchInputTextColor; 23 | border: 1px solid $searchInputBdColor; 24 | } 25 | button{ 26 | background: $searchInputBgColor; 27 | color: $searchInputTextColor; 28 | } 29 | } 30 | .im-tools-item:hover .btn { 31 | color: $mainBgColor; 32 | } 33 | .im-tab-item { 34 | &:hover .btn { 35 | color: $mainBgColor; 36 | } 37 | .ti-arrow { 38 | color: $mainBgColor; 39 | } 40 | } 41 | .btn { 42 | background: transparent; 43 | color: $topToolItemColor; 44 | } 45 | .active .btn{ 46 | color: $mainBgColor; 47 | } 48 | } 49 | .middle{ 50 | background: $middleBgColor; 51 | border-right: 1px solid $middleBdColor; 52 | .list{ 53 | .list-item{ 54 | &:hover, &.current{ 55 | background: #f5f5f5; 56 | } 57 | } 58 | .user{ 59 | .username { 60 | color: $middleItemTextColor; 61 | } 62 | .message{ 63 | color: $middleItemTextColor; 64 | } 65 | } 66 | .time{ 67 | color: $middleItemTextColor; 68 | } 69 | } 70 | .chat-close:hover { 71 | background: $mainBgColor; 72 | } 73 | } 74 | .im-contacts { 75 | .contact-left { 76 | background: #fff; 77 | color: $contactLeftTextColor; 78 | border-right: 1px solid $contactLeftBdColor; 79 | } 80 | .contact-tab { 81 | border-bottom: 1px solid contactTabBdColor; 82 | } 83 | .concat-item { 84 | .line { 85 | border-bottom: 2px solid transparent; 86 | } 87 | &:hover>.line, &.active>.line { 88 | border-bottom: 2px solid $mainBgColor; 89 | } 90 | } 91 | } 92 | .contact-card .cc-send { 93 | background: $mainBgColor; 94 | } 95 | .friends { 96 | &-group:hover{ 97 | background: $contactFGActiveBgColor; 98 | } 99 | .fg-item:hover { 100 | background: $contactFIActiveBgColor; 101 | } 102 | } 103 | 104 | .gg-list-container { 105 | .gg-item:hover { 106 | background: $contactFGActiveBgColor; 107 | } 108 | .gg-add-new { 109 | background: $mainBgColor!important; 110 | color: #fff; 111 | } 112 | } 113 | 114 | .chat-box{ 115 | .tool-bar-item:hover{ 116 | color: $mainBgColor!important; 117 | } 118 | .send-btn { 119 | background: $mainBgColor; 120 | color: #fff; 121 | } 122 | .send-btn:hover { 123 | background: #e7e7e7; 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /src/components/Contacts/Contacts.vue: -------------------------------------------------------------------------------- 1 | 19 | 65 | 102 | -------------------------------------------------------------------------------- /src/skin/red.scss: -------------------------------------------------------------------------------- 1 | $mainBgColor: #e45050; 2 | $searchInputBgColor: #fff; 3 | $searchInputTextColor: #a6b5d7; 4 | $topToolItemColor: #d1dce4; 5 | $topToolItemActiveColor: #fff; 6 | $middleBgColor: #363e47; 7 | $middleBdColor: #ccc; 8 | $middleItemActiveBgColor: #2f363e; 9 | $middleItemTextColor: #fff; 10 | $middleItemOtherTextColor: #afa0a8; 11 | $contactLeftBgColor: #363e47; 12 | $contactTabBdColor: #2f363e; 13 | $contactFGActiveBgColor: #2f363e; 14 | $contactFIActiveBgColor: #363e47; 15 | .red { 16 | .im-header { 17 | background: $mainBgColor; 18 | border: 1px solid transparent; 19 | .top-search { 20 | .search-box { 21 | background: $searchInputBgColor; 22 | color: $searchInputTextColor; 23 | border: 1px solid transparent; 24 | } 25 | button{ 26 | background: $searchInputBgColor; 27 | color: $searchInputTextColor; 28 | } 29 | } 30 | .im-tools-item:hover .btn { 31 | color: $topToolItemActiveColor; 32 | } 33 | .im-tab-item { 34 | &:hover .btn { 35 | color: $topToolItemActiveColor; 36 | } 37 | .ti-arrow { 38 | color: $topToolItemActiveColor; 39 | } 40 | } 41 | .btn { 42 | background: transparent; 43 | color: $topToolItemColor; 44 | } 45 | .active .btn{ 46 | color: $topToolItemActiveColor; 47 | } 48 | } 49 | .middle{ 50 | background: $middleBgColor; 51 | border-right: 1px solid $middleBdColor; 52 | .list{ 53 | .list-item{ 54 | &:hover, &.current{ 55 | background: $middleItemActiveBgColor; 56 | } 57 | } 58 | .user{ 59 | .username { 60 | color: $middleItemTextColor; 61 | } 62 | .message{ 63 | color: $middleItemOtherTextColor; 64 | } 65 | } 66 | .time{ 67 | color: $middleItemOtherTextColor; 68 | } 69 | } 70 | .chat-close:hover { 71 | background: $mainBgColor; 72 | } 73 | } 74 | .im-contacts { 75 | .contact-left { 76 | background: $contactLeftBgColor; 77 | color: #fff; 78 | } 79 | .contact-tab { 80 | border-bottom: 1px solid $contactTabBdColor; 81 | } 82 | .concat-item { 83 | .line { 84 | border-bottom: 2px solid transparent; 85 | } 86 | &:hover>.line, &.active>.line { 87 | border-bottom: 2px solid $mainBgColor; 88 | } 89 | } 90 | } 91 | .contact-card .cc-send { 92 | background: $mainBgColor; 93 | } 94 | .friends { 95 | &-group:hover{ 96 | background: $contactFGActiveBgColor; 97 | } 98 | .fg-item:hover { 99 | background: $contactFIActiveBgColor; 100 | } 101 | } 102 | .gg-list-container { 103 | .gg-item:hover { 104 | background: $contactFGActiveBgColor; 105 | } 106 | .gg-add-new { 107 | background: $mainBgColor; 108 | } 109 | .el-button--default:hover{ 110 | background: $mainBgColor; 111 | color: #fff; 112 | } 113 | } 114 | .chat-box{ 115 | .tool-bar-item:hover{ 116 | color: $mainBgColor!important; 117 | } 118 | .send-btn { 119 | background: $mainBgColor; 120 | color: #fff; 121 | &:hover { 122 | background: #f95656; 123 | } 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /src/skin/blue.scss: -------------------------------------------------------------------------------- 1 | $mainBgColor: #1d93ec; 2 | $searchInputBgColor: #fff; 3 | $searchInputTextColor: #a6b5d7; 4 | $topToolItemColor: #d1dce4; 5 | $topToolItemActiveColor: #fff; 6 | $middleBgColor: #fafafa; 7 | $middleBdColor: #ccc; 8 | $middleItemActiveBgColor: #ebebec; 9 | $middleItemTextColor: #363e47; 10 | $contactLeftBgColor: #fff; 11 | $contactLeftBdColor: #ccc; 12 | $contactTabBdColor: #2f363e; 13 | $contactFGActiveBgColor: #f5f5f5; 14 | $contactFIActiveBgColor: #d5d5d5; 15 | .blue { 16 | .im-header { 17 | background: $mainBgColor; 18 | border: 1px solid transparent; 19 | .top-search { 20 | .search-box { 21 | background: $searchInputBgColor; 22 | color: $searchInputTextColor; 23 | border: 1px solid transparent; 24 | } 25 | button{ 26 | background: $searchInputBgColor;; 27 | color: $searchInputTextColor; 28 | } 29 | } 30 | .im-tools-item:hover .btn { 31 | color: $topToolItemActiveColor; 32 | } 33 | .im-tab-item { 34 | &:hover .btn { 35 | color: $topToolItemActiveColor; 36 | } 37 | .ti-arrow { 38 | color: $topToolItemActiveColor; 39 | } 40 | } 41 | .btn { 42 | background: transparent; 43 | color: $topToolItemColor; 44 | } 45 | .active .btn{ 46 | color: $topToolItemActiveColor; 47 | } 48 | } 49 | .middle{ 50 | background: $middleBgColor; 51 | border-right: 1px solid #ccc; 52 | .list{ 53 | .list-item{ 54 | &:hover, &.current{ 55 | background: $middleItemActiveBgColor; 56 | } 57 | } 58 | .user{ 59 | .username { 60 | color: $middleItemTextColor; 61 | } 62 | .message{ 63 | color: $middleItemTextColor; 64 | } 65 | } 66 | .time{ 67 | color: $middleItemTextColor; 68 | } 69 | } 70 | .chat-close:hover { 71 | background: $mainBgColor; 72 | } 73 | } 74 | .im-contacts { 75 | .contact-left { 76 | background: $contactLeftBgColor; 77 | color: #363e47; 78 | border-right: 1px solid $contactLeftBdColor; 79 | } 80 | .contact-tab { 81 | border-bottom: 1px solid $contactTabBdColor; 82 | } 83 | .concat-item { 84 | .line { 85 | border-bottom: 2px solid transparent; 86 | } 87 | &:hover>.line, &.active>.line { 88 | border-bottom: 2px solid $mainBgColor; 89 | } 90 | } 91 | } 92 | .contact-card .cc-send { 93 | background: $mainBgColor; 94 | } 95 | .friends { 96 | &-group:hover{ 97 | background: $contactFGActiveBgColor; 98 | } 99 | .fg-item:hover { 100 | background: $contactFIActiveBgColor; 101 | } 102 | } 103 | .gg-list-container { 104 | .gg-item:hover { 105 | background: $contactFGActiveBgColor; 106 | } 107 | .gg-add-new { 108 | background: $mainBgColor; 109 | } 110 | .el-button--default:hover{ 111 | background: $mainBgColor; 112 | color: #fff; 113 | } 114 | } 115 | .chat-box{ 116 | .tool-bar-item:hover{ 117 | color: $mainBgColor!important; 118 | } 119 | .send-btn { 120 | background: $mainBgColor; 121 | color: #fff; 122 | &:hover { 123 | background: #2c9ff7; 124 | } 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /src/components/FriendList/FriendList.vue: -------------------------------------------------------------------------------- 1 | 19 | 82 | 124 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-im", 3 | "version": "2.3.4", 4 | "description": "基于vue的即时通讯组件", 5 | "author": "liuziyang", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "start": "node build/dev-server.js", 10 | "build": "node build/build.js", 11 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run", 12 | "e2e": "node test/e2e/runner.js", 13 | "test": "npm run unit && npm run e2e", 14 | "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/lzy1043/vue-im.git" 19 | }, 20 | "main": "dist/vue-im.js", 21 | "dependencies": { 22 | "element-ui": "^2.2.1", 23 | "font-awesome": "^4.7.0", 24 | "lodash": "^4.17.4", 25 | "moment": "^2.19.3" 26 | }, 27 | "devDependencies": { 28 | "autoprefixer": "^7.1.2", 29 | "axios": "^0.18.0", 30 | "babel-core": "^6.22.1", 31 | "babel-eslint": "^7.1.1", 32 | "babel-loader": "^7.1.1", 33 | "babel-plugin-component": "^1.1.0", 34 | "babel-plugin-istanbul": "^4.1.1", 35 | "babel-plugin-transform-runtime": "^6.22.0", 36 | "babel-preset-env": "^1.3.2", 37 | "babel-preset-stage-2": "^6.22.0", 38 | "babel-register": "^6.22.0", 39 | "chai": "^3.5.0", 40 | "chalk": "^2.0.1", 41 | "chromedriver": "^2.27.2", 42 | "connect-history-api-fallback": "^1.3.0", 43 | "copy-webpack-plugin": "^4.0.1", 44 | "cross-env": "^5.0.1", 45 | "cross-spawn": "^5.0.1", 46 | "css-loader": "^0.28.0", 47 | "eslint": "^3.19.0", 48 | "eslint-config-standard": "^6.2.1", 49 | "eslint-friendly-formatter": "^3.0.0", 50 | "eslint-loader": "^1.7.1", 51 | "eslint-plugin-html": "^3.0.0", 52 | "eslint-plugin-promise": "^3.4.0", 53 | "eslint-plugin-standard": "^2.0.1", 54 | "eventsource-polyfill": "^0.9.6", 55 | "express": "^4.14.1", 56 | "extract-text-webpack-plugin": "^2.0.0", 57 | "file-loader": "^0.11.1", 58 | "friendly-errors-webpack-plugin": "^1.1.3", 59 | "html-webpack-plugin": "^2.28.0", 60 | "http-proxy-middleware": "^0.17.3", 61 | "inject-loader": "^3.0.0", 62 | "karma": "^1.4.1", 63 | "karma-coverage": "^1.1.1", 64 | "karma-mocha": "^1.3.0", 65 | "karma-phantomjs-launcher": "^1.0.2", 66 | "karma-phantomjs-shim": "^1.4.0", 67 | "karma-sinon-chai": "^1.3.1", 68 | "karma-sourcemap-loader": "^0.3.7", 69 | "karma-spec-reporter": "0.0.31", 70 | "karma-webpack": "^2.0.2", 71 | "less": "^2.7.3", 72 | "less-loader": "^4.0.5", 73 | "lolex": "^1.5.2", 74 | "mocha": "^3.2.0", 75 | "nightwatch": "^0.9.12", 76 | "node-loader": "^0.6.0", 77 | "node-sass": "^4.5.3", 78 | "opn": "^5.1.0", 79 | "optimize-css-assets-webpack-plugin": "^2.0.0", 80 | "ora": "^1.2.0", 81 | "phantomjs-prebuilt": "^2.1.14", 82 | "rimraf": "^2.6.0", 83 | "sass-loader": "^6.0.6", 84 | "selenium-server": "^3.0.1", 85 | "semver": "^5.3.0", 86 | "shelljs": "^0.7.6", 87 | "sinon": "^2.1.0", 88 | "sinon-chai": "^2.8.0", 89 | "url-loader": "^0.5.8", 90 | "vue": "^2.5.13", 91 | "vue-loader": "^12.1.0", 92 | "vue-style-loader": "^3.0.1", 93 | "vue-template-compiler": "^2.3.3", 94 | "webpack": "^2.6.1", 95 | "webpack-bundle-analyzer": "^2.2.1", 96 | "webpack-dev-middleware": "^1.10.0", 97 | "webpack-hot-middleware": "^2.18.0", 98 | "webpack-merge": "^4.1.0" 99 | }, 100 | "engines": { 101 | "node": ">= 4.0.0", 102 | "npm": ">= 3.0.0" 103 | }, 104 | "browserslist": [ 105 | "> 1%", 106 | "last 2 versions", 107 | "not ie <= 8" 108 | ] 109 | } 110 | -------------------------------------------------------------------------------- /demo/im.vue: -------------------------------------------------------------------------------- 1 | 23 | 127 | -------------------------------------------------------------------------------- /src/components/Contacts/ContactCard.vue: -------------------------------------------------------------------------------- 1 | 34 | 50 | -------------------------------------------------------------------------------- /src/components/ChatList/ChatList.vue: -------------------------------------------------------------------------------- 1 | 21 | 97 | 180 | -------------------------------------------------------------------------------- /src/components/GroupList/GroupList.vue: -------------------------------------------------------------------------------- 1 | 25 | 113 | 181 | -------------------------------------------------------------------------------- /src/components/ChatLog/ChatLog.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 97 | 207 | -------------------------------------------------------------------------------- /src/store/store.js: -------------------------------------------------------------------------------- 1 | import storage from '@/util/storage' 2 | import { typeOf, objIsEmpty, deepCopy } from '@/util/utils' 3 | 4 | const saveChatList = function (data) { 5 | let localData = storage.readData('iminfo') 6 | if (data.length > 30) { 7 | data = data.slice(0, 29) 8 | } 9 | let tempData = [] 10 | data.forEach(item => { 11 | let tempItem = { 12 | id: item.id, 13 | type: item.type 14 | } 15 | tempData.push(tempItem) 16 | }) 17 | localData.chatList = tempData 18 | storage.saveData('iminfo', localData) 19 | } 20 | 21 | const saveHistory = function (history) { 22 | let tempData = {} 23 | Object.keys(history).map(key => { 24 | if (history[key].length > 10) { 25 | tempData[key] = history[key].slice(history[key].length - 10) 26 | } else { 27 | tempData[key] = history[key] 28 | } 29 | }) 30 | for (let key in tempData) { 31 | tempData[key].forEach(item => { 32 | delete item.avatar 33 | delete item.username 34 | }) 35 | } 36 | let localData = storage.readData('iminfo') 37 | localData.history = tempData 38 | storage.saveData('iminfo', localData) 39 | } 40 | 41 | const IMStroe = function (IM, initialState = {}) { 42 | if (!IM) { 43 | throw new Error('IM is required') 44 | } 45 | this.IM = IM 46 | this.states = { 47 | currentChat: null, 48 | chatLogsList: [], 49 | newMsgLists: {}, 50 | currentTab: 'user', 51 | count: 0, 52 | skin: 'blue', 53 | mine: null, 54 | localHistory: {}, 55 | userInfoCenter: {} 56 | } 57 | 58 | for (let prop in initialState) { 59 | if (initialState.hasOwnProperty(prop) && this.state.hasOwnProperty(prop)) { 60 | this.states[prop] = initialState 61 | } 62 | } 63 | } 64 | 65 | IMStroe.prototype.mutations = { 66 | setCurrentChat (states, currentChat) { 67 | const oldCurrentChat = states.currentChat 68 | if (!currentChat) { 69 | return null 70 | } else if (oldCurrentChat === null || oldCurrentChat.id !== currentChat.id) { 71 | states.currentChat = { 72 | id: currentChat.id, 73 | hostId: states.mine.id, 74 | type: currentChat.type 75 | } 76 | this.IM.$emit('on-chat-change', currentChat) 77 | storage.saveData('currentChat', states.currentChat) 78 | } 79 | if (oldCurrentChat !== null) { 80 | states.newMsgLists[oldCurrentChat.id] = [] 81 | } 82 | }, 83 | setCurrentTab (states, currentTab) { 84 | states.currentTab = currentTab 85 | if (currentTab === 'chat' && states.count !== 0) { 86 | this.commit('clearCount') 87 | } 88 | }, 89 | setSkin (states, skin) { 90 | const oldSkin = states.skin 91 | if (oldSkin !== skin) { 92 | states.skin = skin 93 | storage.setItem('im-skin', skin) 94 | } 95 | }, 96 | updateChatLogsList (states, chatLog) { 97 | if (chatLog === null || chatLog === undefined) chatLog = [] 98 | if (typeOf(chatLog) === 'array') { 99 | chatLog.map(item => { 100 | item.count ? item.count : 0 101 | }) 102 | states.chatLogsList = chatLog 103 | } else { 104 | let tempIndex 105 | states.chatLogsList.forEach((item, index) => { 106 | if (item.id === chatLog.id) { 107 | tempIndex = index 108 | } 109 | }) 110 | if (tempIndex > -1) { 111 | states.chatLogsList.splice(tempIndex, 1) 112 | } 113 | if (chatLog.id === states.currentChat.id) { 114 | chatLog.count = 0 115 | } else { 116 | chatLog.count = chatLog.count || 0 117 | } 118 | states.chatLogsList.unshift(chatLog) 119 | } 120 | this.commit('updateChatLog', chatLog) 121 | saveChatList(states.chatLogsList) 122 | }, 123 | updateChatLog (states, message) { 124 | const history = states.localHistory 125 | if (objIsEmpty(history)) return 126 | const tempData = deepCopy(states.chatLogsList) 127 | tempData.map(item => { 128 | const hisItem = history[item.id] 129 | if (hisItem !== undefined) { 130 | const len = hisItem.length - 1 131 | item.chatlog = hisItem[len].content 132 | item.chatlogType = hisItem[len].chatlogType || 'text' 133 | item.time = hisItem[len].time 134 | } 135 | }) 136 | states.chatLogsList = tempData 137 | }, 138 | removeChatLog (states, index) { 139 | states.chatLogsList.splice(index, 1) 140 | saveChatList(states.chatLogsList) 141 | this.commit('setCurrentChat', states.chatLogsList[0]) 142 | }, 143 | getNewMsg (states, message) { 144 | if (!states.newMsgLists.hasOwnProperty(message.sender)) { 145 | states.newMsgLists[message.sender] = [] 146 | } 147 | states.newMsgLists[message.sender].push(message) 148 | if (states.currentTab !== 'chat') { 149 | states.count += 1 150 | } 151 | }, 152 | setLocalHistory (states, localHistory) { 153 | states.localHistory = localHistory 154 | }, 155 | updateLocalHistory (states, sendData) { 156 | const tempData = deepCopy(states.localHistory) 157 | if (!tempData.hasOwnProperty(sendData.id)) { 158 | tempData[sendData.id] = [] 159 | } 160 | tempData[sendData.id].push(sendData) 161 | states.localHistory = tempData 162 | saveHistory(states.localHistory) 163 | this.commit('updateChatLog', sendData) 164 | }, 165 | resetItemCount (states, chatLog) { 166 | const index = states.chatLogsList.findIndex(item => item.id === chatLog.id) 167 | states.newMsgLists[chatLog.id] = [] 168 | states.chatLogsList.splice(index, 1, chatLog) 169 | }, 170 | clearCount (states) { 171 | states.count = 0 172 | }, 173 | setUserInfoCenter (states, userInfo) { 174 | const tempData = deepCopy(states.userInfoCenter) 175 | userInfo.forEach(item => { 176 | if (!tempData.hasOwnProperty(item.id)) { 177 | tempData[item.id] = { 178 | avatar: item.avatar, 179 | type: item.type, 180 | username: item.username 181 | } 182 | } 183 | }) 184 | states.userInfoCenter = tempData 185 | }, 186 | updateUserInfoCenter (states, userInfo) { 187 | if (!states.userInfoCenter.hasOwnProperty(userInfo)) { 188 | states.userInfoCenter[userInfo.id] = { 189 | avatar: userInfo.avatar, 190 | type: userInfo.type, 191 | username: userInfo.username 192 | } 193 | } 194 | } 195 | } 196 | 197 | IMStroe.prototype.commit = function (name, ...args) { 198 | const mutations = this.mutations 199 | if (mutations[name]) { 200 | mutations[name].apply(this, [this.states].concat(args)) 201 | } else { 202 | throw new Error(`Action not found: ${name}`) 203 | } 204 | } 205 | 206 | export default IMStroe 207 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-im 2 | 3 | > 基于Vue的即时通讯组件 4 | 5 | 注意: 6 | 1. 该组件会将部分聊天记录保存在localStorage,尽量不要将头像转成base64格式,不然会localStorage或溢出 7 | 2. 部分数据使用了layim模拟数据,如有侵权请告知修改 8 | 9 | - [demo](https://lzy1043.github.io/vue-im-demo/) 10 | 11 | ### 安装 12 | 13 | ``` 14 | $ npm install vue-im --save 15 | ``` 16 | 17 | ### 引入 18 | 19 | - 单文件组件 20 | 21 | ``` js 22 | 23 | import vueIM from 'vue-im' 24 | import Vue from 'vue' 25 | Vue.use(vue-im) 26 | ``` 27 | 28 | - script引用 29 | 30 | 31 | ``` html 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | ``` 41 | 42 | 43 | 44 | ### 使用 45 | 46 | #### 数据格式 47 | 48 | ##### 1. 当前用户 49 | 50 | ``` js 51 | 52 | { 53 | 'username': '会飞的猪', 54 | 'id': '100000', 55 | 'avatar': 'http://ofl49b399.bkt.clouddn.com/1.jpg' 56 | } 57 | 58 | ``` 59 | ##### 2. 联系人数组 60 | ``` js 61 | 62 | 若未提供groupname, 会处理为默认的未命名分组中 63 | 64 | [ 65 | { 66 | "groupname": "我心中的女神", 67 | "id": 3, 68 | "list": [{ 69 | "username": "林心如", 70 | "id": "76543", 71 | "avatar": "http://tp3.sinaimg.cn/1223762662/180/5741707953/0" 72 | }, { 73 | "username": "佟丽娅", 74 | "id": "4803920", 75 | "avatar": "http://tp4.sinaimg.cn/1345566427/180/5730976522/0" 76 | }] 77 | } 78 | ... 79 | ] 80 | ``` 81 | 82 | ##### 3. 群组列表 83 | 84 | ``` js 85 | [ 86 | { 87 | 'groupname': '前端群', 88 | 'id': '101', 89 | 'avatar': 'http://tp2.sinaimg.cn/2211874245/180/40050524279/0' 90 | }, { 91 | 'groupname': 'Fly社区官方群', 92 | 'id': '102', 93 | 'avatar': 'http://tp2.sinaimg.cn/5488749285/50/5719808192/1' 94 | } 95 | ] 96 | ``` 97 | 98 | 4. 群组成员 99 | 100 | ``` js 101 | [{ 102 | 'username': '奔跑的五花肉', 103 | 'id': '100001', 104 | 'avatar': '//ofl49b399.bkt.clouddn.com/2.jpg' 105 | }, 106 | { 107 | 'username': '刘涛tamia', 108 | 'id': '100001222', 109 | 'avatar': '//tva4.sinaimg.cn/crop.0.1.1125.1125.180/475bb144jw8f9nwebnuhkj20v90vbwh9.jpg' 110 | }, 111 | { 112 | 'username': '谢楠', 113 | 'id': '10034001', 114 | 'avatar': '//tva2.sinaimg.cn/crop.1.0.747.747.180/633f068fjw8f9h040n951j20ku0kr74t.jpg' 115 | }, 116 | { 117 | 'username': '马小云', 118 | 'id': '168168', 119 | 'avatar': '//tva1.sinaimg.cn/crop.0.0.180.180.180/7fde8b93jw1e8qgp5bmzyj2050050aa8.jpg' 120 | }] 121 | 122 | 123 | ``` 124 | 125 | 126 | ##### 5. 聊天记录 127 | 128 | ``` js 129 | 130 | { 131 | total: 21, //总条数 132 | size: 20, // 每页显示条数 133 | records: [ // 聊天记录具体能容 134 | { 135 | content: '你好', 136 | 'username': '奔跑的五花肉', 137 | 'avatar': 'https://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg', 138 | sender: '100001', 139 | recver: '100000', 140 | time: 1513215782417, 141 | sendername: '奔跑的五花肉', 142 | recvername: '会飞的猪' 143 | }, 144 | { 145 | content: '你好', 146 | 'username': '奔跑的五花肉', 147 | 'avatar': 'https://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg', 148 | sender: '100001', 149 | recver: '100000', 150 | time: 1513215782417, 151 | sendername: '奔跑的五花肉', 152 | recvername: '会飞的猪' 153 | } 154 | ] 155 | } 156 | 157 | ``` 158 | 159 | ##### 6. 新消息 160 | ``` js 161 | { 162 | content: '你好呀', // 消息内容 163 | avatar: this.lists[0].avatar, // 发送者头像 164 | sender: this.lists[0].id, // 发送人id 165 | recver: this.mine.id, // 接收人id,当前登录用户 166 | time: new Date().getTime(), // 发送时间 167 | sendername: this.lists[0].username, // 发送人名称 168 | recvername: this.mine.username // 接收人名称, 当前登录用户 169 | chatlogType: 'text' // 消息类型, text为文本,file文件,image图片 170 | } 171 | 172 | ``` 173 | 174 | ##### 7.发送文件和图片 175 | 176 | 发送文件和图片后台接口的返回值格式: 177 | 178 | ``` js 179 | { 180 | //图片路径 181 | src: ‘’ 182 | } 183 | 184 | ``` 185 | 186 | 187 | 188 | ### 2. 分页控制 189 | 190 | 监听 ` on-page-change ` 事件能够获取当前点击的页数,根据页数修改 history.records 会自动触发更新 191 | 192 | ### 3. 接收到新消息 193 | 194 | 接收到信息时, 需要调用getMessage 方法,将消息内容传递给组件 195 | 196 | ``` html 197 | html 198 | .... 199 | 200 | 201 | 202 | .... 203 | 204 | ``` 205 | 206 | ``` js 207 | .... 208 | this.refs.vueim.getMessage({ 209 | content: '你好呀', 210 | avatar: this.lists[0].avatar, 211 | sender: this.lists[0].id, 212 | recver: this.mine.id, 213 | time: new Date().getTime(), 214 | sendername: this.lists[0].username, 215 | recvername: this.mine.username, 216 | chatlogType: 'text' // 消息类型, text为文本,file文件,image图片 217 | }) 218 | .... 219 | 220 | ``` 221 | 222 | ### 4、查看历史记录和群组成员 223 | 224 | 提供了相应的事件接口和属性,监听相应的事件,获取数据,再利用属性值传递给组件即可 225 | 226 | 227 | ## API 文档 228 | 229 | ### props 230 | 231 | | 名称 | 类型 | 默认值 | 描述 | 232 | | --------- | --- | ------ | ----------------------------- | 233 | | mine | Object | —— | 初始值,必需 | 234 | | lists | Array | 空数组 | 联系人数组,好友数组 | 235 | | history | Object | 空对象 | 历史记录数据 | 236 | | brief | Boolean | false | 是否使用客服模式 | 237 | | voice | String | —— | 新消息声音提醒,需要传入声音文件,使用mp3格式 | 238 | | ext | Array | [] | 配置图片发送支持的格式 | 239 | | action | String | —— | 图片上传的后台地址,启用图片发送功能时该项为必需 | 240 | | upload-name | String | image | 图片上传的表单的name属性 | 241 | | members-list | Array | 空数组 | 聊天的群组成员列表 | 242 | | groups-list | Array | 空数组 | 聊天的群组列表 | 243 | | chat | Object | —— | 客服模式下的会话对象,客服模式下必需 | 244 | | un-read-list | Array | 空数组 | 离线消息,格式同消息 | 245 | 246 | 247 | 248 | 249 | ### events 250 | 251 | | 名称 | 参数 | 描述 | 252 | | ---------------- | ----------------------------- | ---------------------------------------- | 253 | | on-chat-change | Object | 聊天框切换时触发该回调, 参数是当前的聊天用户 | 254 | | on-send | Object | 发送消息时触发该回调 | 255 | | on-page-change | Number: page | 切换聊天记录分页是触发 | 256 | | on-view-history | Object, 当前对话的用户 | 点击查看历史聊天记录时触发 | 257 | | on-view-members | Object, 当前点击的群组对象 | 点击查看群信息和成员时触发 | 258 | 259 | ### methods 260 | | 名称 | 参数 | 描述 | 261 | | -------------| --------------- | --------------------------------- | 262 | | getMessage | Object | 接收的的新消息内容 | 263 | 264 | ## TODOS 265 | 266 | * 添加好友,删除好友功能 267 | * ... 268 | 269 | 暂时只想到这些,有想法的可以提issues,可以添加 270 | 271 | ## Dependencies (依赖) 272 | 273 | - [moment](https://github.com/moment/moment.git) 274 | 275 | ## update(更新内容) 276 | 277 | - [更新日志](./LOG.md) 278 | 279 | ## Licence (证书) 280 | 281 | ## Issues 282 | 283 | - [issues](https://github.com/lzy1043/vue-im/issues) 284 | 285 | vueIM is open source and released under the MIT Licence. 286 | 287 | Copyright (c) 2017 liuziyang 288 | 289 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-present iView 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | MIT LICENSE 24 | 25 | Copyright (c) 2015-present Alipay.com, https://www.alipay.com/ 26 | 27 | Permission is hereby granted, free of charge, to any person obtaining 28 | a copy of this software and associated documentation files (the 29 | "Software"), to deal in the Software without restriction, including 30 | without limitation the rights to use, copy, modify, merge, publish, 31 | distribute, sublicense, and/or sell copies of the Software, and to 32 | permit persons to whom the Software is furnished to do so, subject to 33 | the following conditions: 34 | 35 | The above copyright notice and this permission notice shall be 36 | included in all copies or substantial portions of the Software. 37 | 38 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 39 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 40 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 41 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 42 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 43 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 44 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 45 | 46 | The MIT License (MIT) 47 | 48 | Copyright (c) 2016 ElemeFE 49 | 50 | Permission is hereby granted, free of charge, to any person obtaining a copy 51 | of this software and associated documentation files (the "Software"), to deal 52 | in the Software without restriction, including without limitation the rights 53 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 54 | copies of the Software, and to permit persons to whom the Software is 55 | furnished to do so, subject to the following conditions: 56 | 57 | The above copyright notice and this permission notice shall be included in all 58 | copies or substantial portions of the Software. 59 | 60 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 61 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 62 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 63 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 64 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 65 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 66 | SOFTWARE. 67 | 68 | The MIT License (MIT) 69 | 70 | Copyright (c) 2015 Koala 71 | 72 | Permission is hereby granted, free of charge, to any person obtaining a copy 73 | of this software and associated documentation files (the "Software"), to deal 74 | in the Software without restriction, including without limitation the rights 75 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 76 | copies of the Software, and to permit persons to whom the Software is 77 | furnished to do so, subject to the following conditions: 78 | 79 | The above copyright notice and this permission notice shall be included in all 80 | copies or substantial portions of the Software. 81 | 82 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 83 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 84 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 85 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 86 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 87 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 88 | SOFTWARE. 89 | 90 | The MIT License (MIT) 91 | 92 | Copyright (c) 2016 vue-beauty 93 | 94 | Permission is hereby granted, free of charge, to any person obtaining a copy 95 | of this software and associated documentation files (the "Software"), to deal 96 | in the Software without restriction, including without limitation the rights 97 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 98 | copies of the Software, and to permit persons to whom the Software is 99 | furnished to do so, subject to the following conditions: 100 | 101 | The above copyright notice and this permission notice shall be included in all 102 | copies or substantial portions of the Software. 103 | 104 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 105 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 106 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 107 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 108 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 109 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 110 | SOFTWARE. 111 | 112 | MIT License 113 | 114 | Copyright (c) 2016-present, Airyland 115 | 116 | Permission is hereby granted, free of charge, to any person obtaining a copy 117 | of this software and associated documentation files (the "Software"), to deal 118 | in the Software without restriction, including without limitation the rights 119 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 120 | copies of the Software, and to permit persons to whom the Software is 121 | furnished to do so, subject to the following conditions: 122 | 123 | The above copyright notice and this permission notice shall be included in all 124 | copies or substantial portions of the Software. 125 | 126 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 127 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 128 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 129 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 130 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 131 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 132 | SOFTWARE. 133 | 134 | The MIT License (MIT) 135 | 136 | Copyright (c) 2016 Drifty (http://drifty.com/) 137 | 138 | Permission is hereby granted, free of charge, to any person obtaining a copy 139 | of this software and associated documentation files (the "Software"), to deal 140 | in the Software without restriction, including without limitation the rights 141 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 142 | copies of the Software, and to permit persons to whom the Software is 143 | furnished to do so, subject to the following conditions: 144 | 145 | The above copyright notice and this permission notice shall be included in 146 | all copies or substantial portions of the Software. 147 | 148 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 149 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 150 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 151 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 152 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 153 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 154 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/components/Header/TheHeader.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 110 | 298 | -------------------------------------------------------------------------------- /demo/lists.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'mine': 3 | { 4 | 'username': '会飞的猪', 5 | 'id': '100000', 6 | 'avatar': '//ofl49b399.bkt.clouddn.com/1.jpg' 7 | }, 8 | 'list': [ 9 | { 10 | 'username': '奔跑的五花肉', 11 | 'id': '100001', 12 | 'avatar': '//ofl49b399.bkt.clouddn.com/2.jpg' 13 | }, 14 | { 15 | 'username': '刘涛tamia', 16 | 'id': '100001222', 17 | 'avatar': '//tva4.sinaimg.cn/crop.0.1.1125.1125.180/475bb144jw8f9nwebnuhkj20v90vbwh9.jpg' 18 | }, 19 | { 20 | 'username': '谢楠', 21 | 'id': '10034001', 22 | 'avatar': '//tva2.sinaimg.cn/crop.1.0.747.747.180/633f068fjw8f9h040n951j20ku0kr74t.jpg' 23 | }, 24 | { 25 | 'username': '马小云', 26 | 'id': '168168', 27 | 'avatar': '//tva1.sinaimg.cn/crop.0.0.180.180.180/7fde8b93jw1e8qgp5bmzyj2050050aa8.jpg' 28 | }, 29 | { 30 | 'username': '徐小峥', 31 | 'id': '666666', 32 | 'avatar': '//tva1.sinaimg.cn/crop.0.0.512.512.180/6a4acad5jw8eqi6yaholjj20e80e8t9f.jpg' 33 | }, 34 | { 35 | 'username': '罗玉凤', 36 | 'id': '121286', 37 | 'avatar': '//tva4.sinaimg.cn/crop.0.0.640.640.180/4a02849cjw8fc8vn18vktj20hs0hs75v.jpg' 38 | }, 39 | { 40 | 'username': 'Z_子晴', 41 | 'id': '108101', 42 | 'avatar': '//tva1.sinaimg.cn/crop.0.23.1242.1242.180/8693225ajw8fbimjimpjwj20yi0zs77l.jpg' 43 | }, 44 | { 45 | 'username': '大鱼_MsYuyu', 46 | 'id': '12123454', 47 | 'avatar': '//tva2.sinaimg.cn/crop.0.0.512.512.180/005LMAegjw8f2bp9qg4mrj30e80e8dg5.jpg' 48 | }, 49 | { 50 | 'username': '醋醋cucu', 51 | 'id': '102101', 52 | 'avatar': '//tva2.sinaimg.cn/crop.0.0.640.640.180/648fbe5ejw8ethmg0u9egj20hs0ht0tn.jpg' 53 | }, 54 | { 55 | 'username': '柏雪近在它香', 56 | 'id': '3435343', 57 | 'avatar': '//tva2.sinaimg.cn/crop.0.8.751.751.180/961a9be5jw8fczq7q98i7j20kv0lcwfn.jpg' 58 | }, 59 | { 60 | 'username': '王祖贤', 61 | 'id': '76543', 62 | 'avatar': '//wx2.sinaimg.cn/mw690/5db11ff4gy1flxmew7edlj203d03wt8n.jpg' 63 | }, 64 | { 65 | 'username': '佟丽娅', 66 | 'id': '4803920', 67 | 'avatar': '//tva3.sinaimg.cn/crop.0.0.750.750.180/5033b6dbjw8etqysyifpkj20ku0kuwfw.jpg' 68 | } 69 | ], 70 | 'log': { 71 | total: 21, 72 | size: 20, 73 | records: [ 74 | { 75 | content: '你好', 76 | 'username': '贤心', 77 | 'avatar': '//tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg', 78 | sender: '100001', 79 | recver: '100000', 80 | time: 1513215782417, 81 | sendername: '贤心', 82 | recvername: '纸飞机' 83 | }, 84 | { 85 | content: '你好', 86 | 'username': '贤心', 87 | 'avatar': '//tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg', 88 | sender: '100001', 89 | recver: '100000', 90 | time: 1513215782417, 91 | sendername: '贤心', 92 | recvername: '纸飞机' 93 | }, 94 | { 95 | content: '你好', 96 | 'username': '贤心', 97 | 'avatar': '//tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg', 98 | sender: '100001', 99 | recver: '100000', 100 | time: 1513215782417, 101 | sendername: '贤心', 102 | recvername: '纸飞机' 103 | }, 104 | { 105 | content: '你好, 我是纸飞机', 106 | avatar: '//ofl49b399.bkt.clouddn.com/1.jpg', 107 | sender: '100000', 108 | recver: '100001', 109 | time: 1513215782417, 110 | sendername: '纸飞机', 111 | recvername: '贤心' 112 | }, 113 | { 114 | content: '你好', 115 | 'username': '贤心', 116 | 'avatar': '//tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg', 117 | sender: '100001', 118 | recver: '100000', 119 | time: 1513215782417, 120 | sendername: '贤心', 121 | recvername: '纸飞机' 122 | }, 123 | { 124 | content: '你好, 我是纸飞机', 125 | avatar: '//ofl49b399.bkt.clouddn.com/1.jpg', 126 | sender: '100000', 127 | recver: '100001', 128 | time: 1513215782417, 129 | sendername: '纸飞机', 130 | recvername: '贤心' 131 | }, 132 | { 133 | content: '你好', 134 | 'username': '贤心', 135 | 'avatar': '//tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg', 136 | sender: '100001', 137 | recver: '100000', 138 | time: 1513215782417, 139 | sendername: '贤心', 140 | recvername: '纸飞机' 141 | }, 142 | { 143 | content: '你好, 我是纸飞机', 144 | avatar: '//ofl49b399.bkt.clouddn.com/1.jpg', 145 | sender: '100000', 146 | recver: '100001', 147 | time: 1513215782417, 148 | sendername: '纸飞机', 149 | recvername: '贤心' 150 | }, 151 | { 152 | content: '你好', 153 | 'username': '贤心', 154 | 'avatar': '//tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg', 155 | sender: '100001', 156 | recver: '100000', 157 | time: 1513215782417, 158 | sendername: '贤心', 159 | recvername: '纸飞机' 160 | }, 161 | { 162 | content: '你好, 我是纸飞机', 163 | avatar: '//ofl49b399.bkt.clouddn.com/1.jpg', 164 | sender: '100000', 165 | recver: '100001', 166 | time: 1513215782417, 167 | sendername: '纸飞机', 168 | recvername: '贤心' 169 | }, 170 | { 171 | content: '你好', 172 | 'username': '贤心', 173 | 'avatar': '//tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg', 174 | sender: '100001', 175 | recver: '100000', 176 | time: 1513215782417, 177 | sendername: '贤心', 178 | recvername: '纸飞机' 179 | }, 180 | { 181 | content: '你好, 我是纸飞机', 182 | avatar: '//ofl49b399.bkt.clouddn.com/1.jpg', 183 | sender: '100000', 184 | recver: '100001', 185 | time: 1513215782417, 186 | sendername: '纸飞机', 187 | recvername: '贤心' 188 | }, 189 | { 190 | content: '你好', 191 | 'username': '贤心', 192 | 'avatar': '//tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg', 193 | sender: '100001', 194 | recver: '100000', 195 | time: 1513215782417, 196 | sendername: '贤心', 197 | recvername: '纸飞机' 198 | }, 199 | { 200 | content: '你好, 我是纸飞机', 201 | avatar: '//ofl49b399.bkt.clouddn.com/1.jpg', 202 | sender: '100000', 203 | recver: '100001', 204 | time: 1513215782417, 205 | sendername: '纸飞机', 206 | recvername: '贤心' 207 | }, 208 | { 209 | content: '你好', 210 | 'username': '贤心', 211 | 'avatar': '//tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg', 212 | sender: '100001', 213 | recver: '100000', 214 | time: 1513215782417, 215 | sendername: '贤心', 216 | recvername: '纸飞机' 217 | }, 218 | { 219 | content: '你好, 我是纸飞机', 220 | avatar: '//ofl49b399.bkt.clouddn.com/1.jpg', 221 | sender: '100000', 222 | recver: '100001', 223 | time: 1513215782417, 224 | sendername: '纸飞机', 225 | recvername: '贤心' 226 | }, 227 | { 228 | content: '你好', 229 | 'username': '贤心', 230 | 'avatar': '//tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg', 231 | sender: '100001', 232 | recver: '100000', 233 | time: 1513215782417, 234 | sendername: '贤心', 235 | recvername: '纸飞机' 236 | }, 237 | { 238 | content: '你好, 我是纸飞机', 239 | avatar: '//ofl49b399.bkt.clouddn.com/1.jpg', 240 | sender: '100000', 241 | recver: '100001', 242 | time: 1513215782417, 243 | sendername: '纸飞机', 244 | recvername: '贤心' 245 | }, 246 | { 247 | content: '你好', 248 | 'username': '贤心', 249 | 'avatar': '//tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg', 250 | sender: '100001', 251 | recver: '100000', 252 | time: 1513215782417, 253 | sendername: '贤心', 254 | recvername: '纸飞机' 255 | }, 256 | { 257 | content: '你好, 我是纸飞机', 258 | avatar: '//ofl49b399.bkt.clouddn.com/1.jpg', 259 | sender: '100000', 260 | recver: '100001', 261 | time: 1513215782417, 262 | sendername: '纸飞机', 263 | recvername: '贤心' 264 | } 265 | ] 266 | }, 267 | 'groups': [ 268 | { 269 | 'groupname': '前端群', 270 | 'id': '101', 271 | 'avatar': 'http://tp2.sinaimg.cn/2211874245/180/40050524279/0' 272 | }, { 273 | 'groupname': 'Fly社区官方群', 274 | 'id': '102', 275 | 'avatar': 'http://tp2.sinaimg.cn/5488749285/50/5719808192/1' 276 | } 277 | ] 278 | } 279 | -------------------------------------------------------------------------------- /src/im.vue: -------------------------------------------------------------------------------- 1 | 34 | 269 | 327 | -------------------------------------------------------------------------------- /src/components/ChatBox/ChatBox.vue: -------------------------------------------------------------------------------- 1 | 49 | 311 | 532 | --------------------------------------------------------------------------------