├── vue-chat-client
├── static
│ ├── .gitkeep
│ ├── notice.mp3
│ └── wechat.ico
├── config
│ ├── prod.env.js
│ ├── dev.env.js
│ └── index.js
├── src
│ ├── assets
│ │ ├── bg.jpg
│ │ ├── logo.png
│ │ ├── font-icon
│ │ │ ├── iconfont.eot
│ │ │ ├── iconfont.ttf
│ │ │ ├── iconfont.woff
│ │ │ ├── iconfont.css
│ │ │ ├── demo_fontclass.html
│ │ │ ├── demo_unicode.html
│ │ │ ├── iconfont.svg
│ │ │ ├── demo_symbol.html
│ │ │ ├── demo.css
│ │ │ └── iconfont.js
│ │ └── styles
│ │ │ └── css_initialize.css
│ ├── store
│ │ ├── face.js
│ │ ├── index.js
│ │ ├── state.js
│ │ ├── mutations.js
│ │ └── actions.js
│ ├── common
│ │ ├── events.js
│ │ ├── install.js
│ │ ├── socket.js
│ │ └── ws.js
│ ├── main.js
│ ├── App.vue
│ └── components
│ │ ├── avatar-reset.vue
│ │ ├── signinAndSignup.vue
│ │ ├── sider-left.vue
│ │ └── sider-right.vue
├── .editorconfig
├── .postcssrc.js
├── build
│ ├── dev-client.js
│ ├── vue-loader.conf.js
│ ├── build.js
│ ├── webpack.dev.conf.js
│ ├── check-versions.js
│ ├── webpack.base.conf.js
│ ├── utils.js
│ ├── dev-server.js
│ └── webpack.prod.conf.js
├── .babelrc
├── README.md
├── index.html
└── package.json
├── install.bat
├── start.bat
├── stop.bat
├── restart.bat
├── vue-chat-document
├── API.doc
├── 架构图.ddd
├── wechat.ddd
└── function.doc
├── vue-chat-server
├── static
│ └── paopao
│ │ ├── 01.png
│ │ ├── 02.png
│ │ ├── 03.png
│ │ ├── 04.png
│ │ ├── 05.png
│ │ ├── 06.png
│ │ ├── 07.png
│ │ ├── 08.png
│ │ ├── 09.png
│ │ ├── 10.png
│ │ ├── 11.png
│ │ ├── 12.png
│ │ ├── 13.png
│ │ ├── 14.png
│ │ ├── 15.png
│ │ ├── 16.png
│ │ ├── 17.png
│ │ ├── 18.png
│ │ ├── 19.png
│ │ ├── 20.png
│ │ ├── 21.png
│ │ ├── 22.png
│ │ └── face.png
├── models
│ ├── index.js
│ ├── message.js
│ ├── group.js
│ ├── user.js
│ └── avatar.js
├── README.md
├── events
│ ├── addFriend.js
│ ├── removeFriend.js
│ ├── getFriends.js
│ ├── getUser.js
│ ├── getGroups.js
│ ├── searchGroups.js
│ ├── updateUser.js
│ ├── searchUsers.js
│ ├── signIn.js
│ ├── createGroup.js
│ ├── signUp.js
│ ├── getGroupMember.js
│ ├── addGroup.js
│ ├── removeGroup.js
│ ├── uploadImg.js
│ ├── modifyAvatar.js
│ ├── pushMsg.js
│ └── pullMsg.js
├── eventsMap
│ └── index.js
├── package.json
└── app.js
├── .gitignore
└── README.md
/vue-chat-client/static/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/install.bat:
--------------------------------------------------------------------------------
1 | cd ./vue-chat-server && npm i && cd ../vue-chat-client && npm i
--------------------------------------------------------------------------------
/start.bat:
--------------------------------------------------------------------------------
1 | cd ./vue-chat-server && npm start && cd ../vue-chat-client && npm start
--------------------------------------------------------------------------------
/stop.bat:
--------------------------------------------------------------------------------
1 | cd ./vue-chat-server && npm run stop && cd ../vue-chat-client && npm run stop
--------------------------------------------------------------------------------
/restart.bat:
--------------------------------------------------------------------------------
1 | cd ./vue-chat-server && npm run restart && cd ../vue-chat-client && npm run restart
--------------------------------------------------------------------------------
/vue-chat-client/config/prod.env.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NODE_ENV: '"production"'
3 | }
4 |
--------------------------------------------------------------------------------
/vue-chat-document/API.doc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-document/API.doc
--------------------------------------------------------------------------------
/vue-chat-document/架构图.ddd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-document/架构图.ddd
--------------------------------------------------------------------------------
/vue-chat-document/wechat.ddd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-document/wechat.ddd
--------------------------------------------------------------------------------
/vue-chat-document/function.doc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-document/function.doc
--------------------------------------------------------------------------------
/vue-chat-client/src/assets/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-client/src/assets/bg.jpg
--------------------------------------------------------------------------------
/vue-chat-client/static/notice.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-client/static/notice.mp3
--------------------------------------------------------------------------------
/vue-chat-client/static/wechat.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-client/static/wechat.ico
--------------------------------------------------------------------------------
/vue-chat-client/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-client/src/assets/logo.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/01.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/02.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/03.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/04.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/05.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/06.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/07.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/08.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/09.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/10.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/11.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/12.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/13.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/14.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/15.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/16.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/17.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/18.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/19.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/20.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/21.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/22.png
--------------------------------------------------------------------------------
/vue-chat-server/static/paopao/face.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-server/static/paopao/face.png
--------------------------------------------------------------------------------
/vue-chat-client/src/assets/font-icon/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-client/src/assets/font-icon/iconfont.eot
--------------------------------------------------------------------------------
/vue-chat-client/src/assets/font-icon/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-client/src/assets/font-icon/iconfont.ttf
--------------------------------------------------------------------------------
/vue-chat-client/src/assets/font-icon/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SWPUliusong/vue-chat/HEAD/vue-chat-client/src/assets/font-icon/iconfont.woff
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # 图片
9 | temp/
10 |
11 | # 日志文件
12 | error*.log
13 | access*.log
--------------------------------------------------------------------------------
/vue-chat-client/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 |
--------------------------------------------------------------------------------
/vue-chat-client/.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 |
--------------------------------------------------------------------------------
/vue-chat-client/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | // to edit target browsers: use "browserlist" field in package.json
6 | "autoprefixer": {}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/vue-chat-server/models/index.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose")
2 | mongoose.Promise = require("bluebird")
3 | mongoose.connect('mongodb://localhost:27017/wechat')
4 |
5 | module.exports = {
6 | User: require("./user"),
7 | Group: require("./group"),
8 | Message: require("./message")
9 | }
--------------------------------------------------------------------------------
/vue-chat-client/build/dev-client.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | require('eventsource-polyfill')
3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
4 |
5 | hotClient.subscribe(function (event) {
6 | if (event.action === 'reload') {
7 | window.location.reload()
8 | }
9 | })
10 |
--------------------------------------------------------------------------------
/vue-chat-client/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", { "modules": false }],
4 | "stage-2"
5 | ],
6 | "plugins": ["transform-runtime"],
7 | "comments": false,
8 | "env": {
9 | "test": {
10 | "presets": ["env", "stage-2"],
11 | "plugins": [ "istanbul" ]
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/vue-chat-client/src/store/face.js:
--------------------------------------------------------------------------------
1 | const prefix = '/img/paopao/'
2 |
3 | let arr = [], temp
4 |
5 | for (let i = 1; i < 23; i++) {
6 | temp = i
7 | if (i < 10) {
8 | temp = '0' + temp
9 | }
10 |
11 | temp = `${prefix}${temp}.png`
12 |
13 | arr.push(temp)
14 | }
15 |
16 | export default arr
--------------------------------------------------------------------------------
/vue-chat-client/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 |
4 | import state from './state'
5 | import mutations from './mutations'
6 | import actions from './actions'
7 |
8 | Vue.use(Vuex)
9 |
10 | export default new Vuex.Store({
11 | state,
12 | mutations,
13 | actions
14 | })
--------------------------------------------------------------------------------
/vue-chat-server/README.md:
--------------------------------------------------------------------------------
1 | ## 目录结构
2 | ```
3 | |-events socket通讯的各个事件
4 | |-eventsMap 自动映射包,将events下的事件映射到socket上
5 | |-models 数据库文档结构
6 | ㄴapp.js 启动文件
7 | ```
8 | ## 安装依赖
9 | > npm i
10 | ## 启动服务器
11 | > npm test
12 | ## 后台启动
13 | > npm start
14 | ## 重启后台服务
15 | > npm run restart
16 | ## 关闭后台服务
17 | > npm run stop
--------------------------------------------------------------------------------
/vue-chat-client/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils')
2 | var config = require('../config')
3 | var isProduction = process.env.NODE_ENV === 'production'
4 |
5 | module.exports = {
6 | loaders: utils.cssLoaders({
7 | sourceMap: isProduction
8 | ? config.build.productionSourceMap
9 | : config.dev.cssSourceMap,
10 | extract: isProduction
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/vue-chat-client/src/store/state.js:
--------------------------------------------------------------------------------
1 | import faces from './face'
2 |
3 | export default {
4 | user: '', // 当前用户
5 | list: '', // 聊天菜单列表
6 | activeList: 'friends', // 当前聊天菜单类型
7 | currentOne: '', // 当前聊天对象
8 | result: '', // 搜索结果
9 | isChange: true, // 决定消息的更新方式是增加还是重新赋值
10 | messages: [], // 消息记录
11 | count: '', // 消息个数
12 | faces, // 表情
13 | facesShow: false // 是否显示泡泡表情
14 | }
--------------------------------------------------------------------------------
/vue-chat-server/models/message.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose")
2 | const Schema = mongoose.Schema
3 |
4 | let ObjectId = Schema.Types.ObjectId
5 |
6 | let Message = new Schema({
7 | toGroup: ObjectId,
8 | from: ObjectId,
9 | toUser: ObjectId,
10 | content: String,
11 | createAt: {
12 | type: Date,
13 | default: Date.now
14 | },
15 | })
16 |
17 | module.exports = mongoose.model('message', Message)
--------------------------------------------------------------------------------
/vue-chat-server/events/addFriend.js:
--------------------------------------------------------------------------------
1 | const User = require("../models").User
2 |
3 | module.exports = (socket, io) => ({ from, friendId }) => {
4 |
5 | Promise.all([
6 | User.update({_id: from}, {$addToSet: {friends: friendId}}),
7 | User.update({_id: friendId}, {$addToSet: {friends: from}})
8 | ])
9 | .then(() => {
10 | io.sockets.emit('addFriend', { friendId })
11 | })
12 | .catch(err => {
13 | socket.emit('addFriend', err)
14 | })
15 |
16 | }
--------------------------------------------------------------------------------
/vue-chat-server/events/removeFriend.js:
--------------------------------------------------------------------------------
1 | const User = require("../models").User
2 |
3 | module.exports = (socket, io) => ({ from, friendId }) => {
4 |
5 | Promise.all([
6 | User.update({_id: from}, {$pull: {friends: friendId}}),
7 | User.update({_id: friendId}, {$pull: {friends: from}})
8 | ])
9 | .then(() => {
10 | io.sockets.emit('removeFriend', { from, friendId })
11 | })
12 | .catch(err => {
13 | socket.emit('removeFriend', err)
14 | })
15 |
16 | }
--------------------------------------------------------------------------------
/vue-chat-server/events/getFriends.js:
--------------------------------------------------------------------------------
1 | const User = require("../models").User
2 |
3 | module.exports = socket => ({ from }) => {
4 | User
5 | .findOne({_id: from})
6 | .select('friends')
7 | .exec()
8 | .then(doc => {
9 | if (doc.friends.length < 1) return []
10 | return User.find({_id: {$in: doc.friends}}).select('_id name avatar').exec()
11 | })
12 | .then(list => {
13 | socket.emit('getFriends', list)
14 | })
15 | }
--------------------------------------------------------------------------------
/vue-chat-server/events/getUser.js:
--------------------------------------------------------------------------------
1 | const User = require("../models").User
2 |
3 | module.exports = socket => ({ from }) => {
4 | User
5 | .findById(from)
6 | .exec()
7 | .then(user => {
8 | if(!user) {
9 | return Promise.reject({status: 404, msg: '该用户不存在'})
10 | } else {
11 | socket.emit('getUser', user.toObject())
12 | }
13 | })
14 | .catch(err => {
15 | socket.emit('getUser', err)
16 | })
17 | }
--------------------------------------------------------------------------------
/vue-chat-server/events/getGroups.js:
--------------------------------------------------------------------------------
1 | const User = require("../models").User
2 | const Group = require("../models").Group
3 |
4 | module.exports = socket => ({ from }) => {
5 | User
6 | .findOne({_id: from})
7 | .select('groups')
8 | .exec()
9 | .then(doc => {
10 | if (doc.groups.length < 1) return []
11 | return Group.find({_id: {$in: doc.groups}}).select('_id name avatar').exec()
12 | })
13 | .then(list => {
14 | socket.emit('getGroups', list)
15 | })
16 | }
--------------------------------------------------------------------------------
/vue-chat-client/README.md:
--------------------------------------------------------------------------------
1 | # vue-chat
2 |
3 | > A Vue.js chat tool like wechatVue
4 |
5 | ## Build Setup
6 |
7 | ``` bash
8 | # install dependencies
9 | npm install
10 |
11 | # serve with hot reload at localhost:8080
12 | npm run dev
13 |
14 | # build for production with minification
15 | npm run build
16 |
17 | # build for production and view the bundle analyzer report
18 | npm run build --report
19 | ```
20 |
21 | 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).
22 |
--------------------------------------------------------------------------------
/vue-chat-server/models/group.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose")
2 | const Schema = mongoose.Schema
3 |
4 | let ObjectId = Schema.Types.ObjectId
5 |
6 | let avatar = require("./avatar")
7 | let Group = new Schema({
8 | name: String,
9 | avatar: {
10 | type: String,
11 | default() {
12 | let num = Math.floor(Math.random() * 10)
13 | return avatar.group[num]
14 | }
15 | },
16 | member: [ObjectId],
17 | createAt: {
18 | type: Date,
19 | default: Date.now
20 | }
21 | })
22 |
23 | module.exports = mongoose.model('group', Group)
--------------------------------------------------------------------------------
/vue-chat-server/events/searchGroups.js:
--------------------------------------------------------------------------------
1 | const Group = require("../models").Group
2 |
3 | module.exports = socket => ({ keyword }) => {
4 | Group
5 | .find({
6 | name: {$regex: new RegExp(keyword)}
7 | })
8 | .limit(5)
9 | .select('_id name avatar')
10 | .exec()
11 | .then(groups => {
12 | if (!groups) return []
13 | return groups
14 | })
15 | .then(list => {
16 | socket.emit('searchGroups', list)
17 | })
18 | .catch(err => {
19 | socket.emit('searchGroups', err)
20 | })
21 | }
--------------------------------------------------------------------------------
/vue-chat-server/events/updateUser.js:
--------------------------------------------------------------------------------
1 | const User = require("../models").User
2 |
3 | module.exports = socket => ({ from, data }) => {
4 | if (!from) {
5 | socket.emit('updateUser', { status: 400, msg: '参数from缺失' })
6 | }
7 |
8 | User
9 | .findByIdAndUpdate(from, _.pick(data, ['name', 'password']))
10 | .exec()
11 | .then(user => {
12 | let body = _.assign(user.toObject(), _.pick(data, ['name', 'password']))
13 |
14 | socket.emit('updateUser', body)
15 | })
16 | .catch(err => {
17 | socket.emit('updateUser', err)
18 | })
19 | }
--------------------------------------------------------------------------------
/vue-chat-server/eventsMap/index.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs")
2 | const path = require("path")
3 |
4 | module.exports = function(dir) {
5 | let map = {}
6 | let eventNames = fs.readdirSync(dir).map(item => item.replace(/\.js$/, ''))
7 |
8 | eventNames.forEach(event => {
9 | let filePath = path.resolve(dir, event)
10 | let fn = require(filePath)
11 | if (typeof fn !== 'function') fn = () => () =>{}
12 | map[event] = fn
13 | })
14 | return (socket, io) => {
15 | _.forEach(map, (fn, eventName) => {
16 | socket.on(eventName, fn(socket, io))
17 | })
18 | }
19 | }
--------------------------------------------------------------------------------
/vue-chat-client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | vue-chat
6 |
7 |
8 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/vue-chat-server/events/searchUsers.js:
--------------------------------------------------------------------------------
1 | const User = require("../models").User
2 |
3 | module.exports = socket => ({ from, keyword }) => {
4 | User
5 | .find({
6 | name: {$regex: new RegExp(keyword)}
7 | })
8 | .limit(5)
9 | .select('_id name avatar')
10 | .exec()
11 | .then(users => {
12 | if (!users) return []
13 | return users.filter(item => item._id.toString() !== from)
14 | })
15 | .then(list => {
16 | socket.emit('searchUsers', list)
17 | })
18 | .catch(err => {
19 | socket.emit('searchUsers', err)
20 | })
21 | }
--------------------------------------------------------------------------------
/vue-chat-server/events/signIn.js:
--------------------------------------------------------------------------------
1 | const User = require("../models").User
2 |
3 | module.exports = socket => ({ data }) => {
4 | User
5 | .findOne({email: data.email})
6 | .exec()
7 | .then(user => {
8 | if(!user) {
9 | return Promise.reject({status: 401, msg: '该用户不存在'})
10 | } else if (user.password !== data.password) {
11 | return Promise.reject({status: 403, msg: '密码错误'})
12 | } else {
13 | socket.emit('signIn', user.toObject())
14 | }
15 | })
16 | .catch(err => {
17 | socket.emit('signIn', err)
18 | })
19 | }
--------------------------------------------------------------------------------
/vue-chat-server/models/user.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose")
2 | const Schema = mongoose.Schema
3 |
4 | let ObjectId = Schema.Types.ObjectId
5 |
6 | let avatar = require("./avatar")
7 | let User = new Schema({
8 | name: String,
9 | email: String,
10 | password: String,
11 | friends: [ObjectId],
12 | groups: [ObjectId],
13 | createAt: {
14 | type: Date,
15 | default: Date.now
16 | },
17 | avatar: {
18 | type: String,
19 | default() {
20 | let num = Math.floor(Math.random() * 10)
21 | return avatar.user[num]
22 | }
23 | }
24 | })
25 |
26 | module.exports = mongoose.model('user', User)
--------------------------------------------------------------------------------
/vue-chat-server/events/createGroup.js:
--------------------------------------------------------------------------------
1 | const User = require("../models").User
2 | const Group = require("../models").Group
3 |
4 | module.exports = socket => ({ from, data }) => {
5 | Group
6 | .create({
7 | name: data.name,
8 | member: [from]
9 | })
10 | .then(group => {
11 | return User
12 | .update({ _id: from }, { $addToSet: { groups: group._id } })
13 | .then(() => group._id)
14 | })
15 | .then(groupId => {
16 | socket.emit('createGroup', { groupId })
17 | })
18 | .catch(err => {
19 | socket.emit('createGroup', err)
20 | })
21 | }
--------------------------------------------------------------------------------
/vue-chat-client/src/common/events.js:
--------------------------------------------------------------------------------
1 | import store from '@/store'
2 | import { socket } from './socket'
3 |
4 | socket.on('addFriend', ({ friendId }) => {
5 | let { user, activeList } = store.state
6 | if (user && user._id == friendId && activeList === 'friends') {
7 | store.dispatch('getList')
8 | }
9 | return
10 | })
11 |
12 | socket.on('removeFriend', ({ from, friendId }) => {
13 | let { user, activeList, currentOne } = store.state
14 | if (user && user._id == friendId && activeList === 'friends') {
15 | store.dispatch('getList')
16 | }
17 | if (from === currentOne._id) {
18 | store.commit('removeCurrentOne')
19 | }
20 | return
21 | })
--------------------------------------------------------------------------------
/vue-chat-server/events/signUp.js:
--------------------------------------------------------------------------------
1 | const User = require("../models").User
2 |
3 | module.exports = socket => ({ data }) => {
4 | User
5 | .findOne({email: data.email})
6 | .exec()
7 | .then(user => {
8 | if(user) {
9 | return Promise.reject({status: 403, msg: '邮箱已被注册'})
10 | }
11 | return false
12 | })
13 | .then(() => {
14 | return User.create(_.pick(data, ['name', 'password', 'email']))
15 | })
16 | .then(user => {
17 | socket.emit('signUp', user.toObject())
18 | })
19 | .catch(err => {
20 | socket.emit('signUp', err)
21 | })
22 | }
--------------------------------------------------------------------------------
/vue-chat-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-chat-server",
3 | "version": "1.0.0",
4 | "description": "一个vuejs+socket.io的微信仿照版 ",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "node app",
8 | "start": "pm2 start app.js --name vue-chat-server -e ../error.log -o ../access.log",
9 | "restart": "pm2 restart vue-chat-server -e ../error.log -o ../access.log",
10 | "stop": "pm2 delete vue-chat-server"
11 | },
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "bluebird": "^3.5.0",
16 | "lodash": "^4.17.4",
17 | "mongoose": "^4.9.6",
18 | "socket.io": "^1.7.3"
19 | },
20 | "devDependencies": {
21 | "pm2": "^2.4.6"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/vue-chat-client/src/common/install.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | function fileValid(file, size = 100) {
4 | let types = ['image/jpeg', 'image/gif', 'image/png']
5 | let res = {
6 | flag: true
7 | }
8 | if (types.indexOf(file.type) < 0) {
9 | res.flag = false
10 | res.msg = '文件类型只能是gif,png,jpg'
11 | } else if (file.size > size * 1024) {
12 | res.flag = false
13 | res.msg = `图片大小不得超过${size}KB`
14 | }
15 |
16 | return res
17 | }
18 |
19 | fileValid.types = {
20 | 'image/jpeg': 'jpg',
21 | 'image/gif': 'gif',
22 | 'image/png': 'png'
23 | }
24 |
25 | Object.defineProperty(Vue.prototype, 'fileValid', {
26 | value: fileValid
27 | })
--------------------------------------------------------------------------------
/vue-chat-server/events/getGroupMember.js:
--------------------------------------------------------------------------------
1 | const db = require("../models")
2 |
3 | module.exports = socket => ({ groupId }) => {
4 |
5 | if (!groupId) {
6 | socket.emit('getGroupMember', { status: 400, msg: 'groupId缺失' })
7 | return
8 | }
9 |
10 | db.Group
11 | .findById(groupId)
12 | .select('member')
13 | .exec()
14 | .then(doc => {
15 | if (!doc) return []
16 | return db.User
17 | .find({ _id: { $in: doc.member } })
18 | .select('_id name avatar')
19 | .exec()
20 | })
21 | .then(data => {
22 | socket.emit('getGroupMember', { groupId, data })
23 | })
24 | }
--------------------------------------------------------------------------------
/vue-chat-server/events/addGroup.js:
--------------------------------------------------------------------------------
1 | const User = require("../models").User
2 | const Group = require("../models").Group
3 |
4 | module.exports = socket => ({ from, groupId }) => {
5 |
6 | Promise.all([
7 | User.findByIdAndUpdate(from, {$addToSet: {groups: groupId}}),
8 | Group.update({_id: groupId}, {$addToSet: {member: from}})
9 | ])
10 | .then((cols) => {
11 | socket.broadcast.emit(groupId, {
12 | groupId,
13 | data: {
14 | type: 'system',
15 | name: '系统消息',
16 | content: `${cols[0].name} 加入了聊天群组`
17 | }
18 | })
19 | socket.emit('addGroup', {})
20 | })
21 | .catch(err => {
22 | socket.emit('addGroup', err)
23 | })
24 |
25 | }
--------------------------------------------------------------------------------
/vue-chat-server/events/removeGroup.js:
--------------------------------------------------------------------------------
1 | const User = require("../models").User
2 | const Group = require("../models").Group
3 |
4 | module.exports = socket => ({ from, groupId }) => {
5 |
6 | Promise.all([
7 | User.findByIdAndUpdate(from, {$pull: {groups: groupId}}).exec(),
8 | Group.update({_id: groupId}, {$pull: {member: from}})
9 | ])
10 | .then((cols) => {
11 | socket.broadcast.emit(groupId, {
12 | groupId,
13 | data: {
14 | type: 'system',
15 | name: '系统消息',
16 | content: `${cols[0].name} 已经退出聊天群组了`
17 | }
18 | })
19 | socket.emit('removeGroup', {})
20 | })
21 | .catch(err => {
22 | socket.emit('removeGroup', err)
23 | })
24 |
25 | }
--------------------------------------------------------------------------------
/vue-chat-client/src/main.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 |
4 | // lodash
5 | import _ from 'lodash'
6 | window._ = _
7 |
8 |
9 | import Vue from 'vue'
10 | import ElementUI from 'element-ui'
11 | import './common/events'
12 | import './common/install'
13 |
14 | import './assets/styles/css_initialize.css'
15 | import 'element-ui/lib/theme-default/index.css'
16 | import './assets/font-icon/iconfont.css'
17 |
18 | import store from './store'
19 | import App from './App'
20 |
21 | Vue.use(ElementUI)
22 |
23 |
24 | Vue.config.productionTip = false
25 |
26 | /* eslint-disable no-new */
27 | new Vue({
28 | el: '#app',
29 | store,
30 | template: '',
31 | components: { App }
32 | })
33 |
--------------------------------------------------------------------------------
/vue-chat-server/events/uploadImg.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs")
2 | const path = require("path")
3 | const User = require("../models").User
4 | const crypto = require("crypto")
5 |
6 | function createNameByBuffer(buffer) {
7 | return crypto.createHash('md5').update(buffer).digest('hex')
8 | }
9 |
10 | module.exports = socket => ({ from, data }) => {
11 | if (!from) {
12 | socket.emit('uploadImg', { status: 400, msg: '参数from缺失' })
13 | }
14 |
15 | let filename = `temp/${createNameByBuffer(data.file)}.${data.type}`
16 |
17 | let url = path.resolve(process.cwd(), `./static/${filename}`)
18 | fs.writeFile(url, data.file, err => {
19 | if (err) {
20 | return socket.emit('uploadImg', err)
21 | }
22 |
23 | return socket.emit('uploadImg', { img: `/img/${filename}` })
24 | })
25 | }
--------------------------------------------------------------------------------
/vue-chat-server/app.js:
--------------------------------------------------------------------------------
1 | global._ = require("lodash")
2 | global.Promise = require("bluebird")
3 |
4 | const http = require('http')
5 | const fs = require('fs')
6 |
7 | let types = {
8 | 'jpg': 'image/jpeg',
9 | 'gif': 'image/gif',
10 | 'png': 'image/png'
11 | }
12 |
13 | let server = http.createServer((req, res) => {
14 | let url = req.url
15 | if (/^\/img\//.test(url)) {
16 | let ext = url.substring(url.lastIndexOf('.'))
17 | res.writeHead(200, {'content-type': types[ext]})
18 | fs.createReadStream(`./static/${url.replace('/img/', '')}`).pipe(res)
19 | }
20 | })
21 |
22 | let io = require('socket.io')(server);
23 | let eventsMap = require("./eventsMap")('./events')
24 |
25 | server.listen(3002, () => {
26 | console.log(`server listening on ${server.address().port}`)
27 | });
28 |
29 | io.on('connection', function (socket) {
30 | eventsMap(socket, io)
31 | });
32 |
--------------------------------------------------------------------------------
/vue-chat-server/models/avatar.js:
--------------------------------------------------------------------------------
1 | let user = [
2 | 'FrsP_fKd26dFBbbIQTtJ1IpEXbct',
3 | 'FqzW9bqSJLGjx4yCwBdR6YallVis',
4 | 'FqGy-gAxwYr9lKwQxXjB7QFhehgs',
5 | 'FpiKWZxVi2ZvPkg9G1YkBrvyNXUd',
6 | 'FlmQyroSCee5kwHIZ-k7rIC9UYsK',
7 | 'FkqOXqYnO_6ZKGinwcEYK94vSNpP',
8 | 'Fkfc7nDn1I2r1cdBFzvzVTKcOzeC',
9 | 'FhKC2btY7UkL6jXLc5ygiSEqR3Kx',
10 | 'FhAa3WusC6N8zciLg-6zJkgwfirr',
11 | 'FgLNtLQsduVqAxSzNzOkDjnyV0Dz'
12 | ]
13 |
14 | let group = [
15 | 'Fs78VD3Owcpdp2j0XzaHXHX3Fu_3',
16 | 'FpzhUhaVyhs6nK-VvululSweD1yG',
17 | 'Fom5nX-UEo1VJdSK0aQkMwky2NtJ',
18 | 'FoHvYP7zIUIxtwJiT7fATMst3kv4',
19 | 'FnKKljiSMjzkKARlGnB1tJb8HqTF',
20 | '26123517m5bg.jpg',
21 | 'FjoRiCS7U1Sc5Rkv5JXWc51XUC4C',
22 | 'FkvzGzqARaO4TMn7FCwsgn8sRvhA',
23 | 'FlPm5nSeDgilXmxOc7lYaPMKAeTt',
24 | 'FlaMfV3woBqzISvdMzt9FIpEj0qY'
25 | ]
26 |
27 |
28 | module.exports = {
29 | user: _.map(user, item => 'http://ol5140dkq.bkt.clouddn.com/' + item),
30 | group: _.map(group, item => 'http://ol5140dkq.bkt.clouddn.com/' + item)
31 | }
--------------------------------------------------------------------------------
/vue-chat-client/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
27 |
28 |
48 |
--------------------------------------------------------------------------------
/vue-chat-client/build/build.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')()
2 |
3 | process.env.NODE_ENV = 'production'
4 |
5 | var ora = require('ora')
6 | var rm = require('rimraf')
7 | var path = require('path')
8 | var chalk = require('chalk')
9 | var webpack = require('webpack')
10 | var config = require('../config')
11 | var webpackConfig = require('./webpack.prod.conf')
12 |
13 | var spinner = ora('building for production...')
14 | spinner.start()
15 |
16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
17 | if (err) throw err
18 | webpack(webpackConfig, function (err, stats) {
19 | spinner.stop()
20 | if (err) throw err
21 | process.stdout.write(stats.toString({
22 | colors: true,
23 | modules: false,
24 | children: false,
25 | chunks: false,
26 | chunkModules: false
27 | }) + '\n\n')
28 |
29 | console.log(chalk.cyan(' Build complete.\n'))
30 | console.log(chalk.yellow(
31 | ' Tip: built files are meant to be served over an HTTP server.\n' +
32 | ' Opening index.html over file:// won\'t work.\n'
33 | ))
34 | })
35 | })
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-chat
2 | vue + socket.io + mongodb 实现的一个仿网页版的微信
3 |
4 | # 拥有功能
5 | - 用户注册
6 | - 用户登录
7 | - 用户退出
8 | - 获取用户
9 | - 修改用户
10 | - 创建群组
11 | - 获取全部好友
12 | - 获取全部群组
13 | - 添加好友
14 | - 添加聊天群组
15 | - 搜索用户
16 | - 搜索群组
17 | - 获取群组成员
18 | - 删除好友
19 | - 删除群组
20 | - 推送消息
21 | - 拉取消息
22 |
23 | # 目录内容
24 | ```
25 | |-vue-chat-client vue搭建的前端页面,监听8080端口
26 | |-vue-chat-doument 开发文档
27 | ㄴvue-chat-server socket.io+monogdb搭建的服务端,监听3002端口
28 | ```
29 |
30 | # 预览
31 | ## 1.登录注册页
32 |
33 | 
34 |
35 | ## 2.好友列表
36 |
37 | 
38 |
39 | 
40 |
41 | ## 3.群组列表
42 |
43 | 
44 |
45 | 
46 |
47 | ## 4.好友聊天
48 |
49 | 
50 |
51 | ## 5.群内会话
52 |
53 | 
--------------------------------------------------------------------------------
/vue-chat-client/src/assets/font-icon/iconfont.css:
--------------------------------------------------------------------------------
1 |
2 | @font-face {font-family: "iconfont";
3 | src: url('iconfont.eot?t=1493460678859'); /* IE9*/
4 | src: url('iconfont.eot?t=1493460678859#iefix') format('embedded-opentype'), /* IE6-IE8 */
5 | url('iconfont.woff?t=1493460678859') format('woff'), /* chrome, firefox */
6 | url('iconfont.ttf?t=1493460678859') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
7 | url('iconfont.svg?t=1493460678859#iconfont') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | .iconfont {
11 | font-family:"iconfont" !important;
12 | font-size:16px;
13 | font-style:normal;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | }
17 |
18 | .icon-weibiaoti114:before { content: "\e688"; }
19 |
20 | .icon-icon-yxj-user:before { content: "\e631"; }
21 |
22 | .icon-qunzu:before { content: "\e6d7"; }
23 |
24 | .icon-kehuqunzu:before { content: "\e673"; }
25 |
26 | .icon-bianji:before { content: "\f0304"; }
27 |
28 | .icon-jia:before { content: "\f0307"; }
29 |
30 | .icon-guanbi:before { content: "\f0308"; }
31 |
32 | .icon-tuxiang:before { content: "\f030b"; }
33 |
34 | .icon-sousuo:before { content: "\f030d"; }
35 |
36 |
--------------------------------------------------------------------------------
/vue-chat-server/events/modifyAvatar.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs")
2 | const path = require("path")
3 | const User = require("../models").User
4 | const crypto = require("crypto")
5 |
6 | function createNameByBuffer(buffer) {
7 | return crypto.createHash('md5').update(buffer).digest('hex')
8 | }
9 |
10 | // 判断temp文件夹是否存在,不存在则创建
11 | if (!fs.existsSync('./static/temp')) {
12 | fs.mkdirSync('./static/temp')
13 | }
14 |
15 | module.exports = socket => ({ from, data }) => {
16 | if (!from) {
17 | socket.emit('modifyAvatar', { status: 400, msg: '参数from缺失' })
18 | }
19 |
20 | let filename = `temp/${createNameByBuffer(data.file)}.${data.type}`
21 |
22 | let url = path.resolve(process.cwd(), `./static/${filename}`)
23 | fs.writeFile(url, data.file, err => {
24 | if (err) console.error(err)
25 | })
26 |
27 | User
28 | .findByIdAndUpdate(from, {
29 | avatar: `/img/${filename}`
30 | })
31 | .exec()
32 | .then(user => {
33 | let body = _.assign(user.toObject(), {
34 | avatar: `/img/${filename}`
35 | })
36 |
37 | socket.emit('modifyAvatar', body)
38 | })
39 | .catch(err => {
40 | socket.emit('modifyAvatar', err)
41 | })
42 | }
--------------------------------------------------------------------------------
/vue-chat-client/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils')
2 | var webpack = require('webpack')
3 | var config = require('../config')
4 | var merge = require('webpack-merge')
5 | var baseWebpackConfig = require('./webpack.base.conf')
6 | var HtmlWebpackPlugin = require('html-webpack-plugin')
7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
8 |
9 | // add hot-reload related code to entry chunks
10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) {
11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
12 | })
13 |
14 | module.exports = merge(baseWebpackConfig, {
15 | module: {
16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
17 | },
18 | // cheap-module-eval-source-map is faster for development
19 | devtool: '#cheap-module-eval-source-map',
20 | plugins: [
21 | new webpack.DefinePlugin({
22 | 'process.env': config.dev.env
23 | }),
24 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
25 | new webpack.HotModuleReplacementPlugin(),
26 | new webpack.NoEmitOnErrorsPlugin(),
27 | // https://github.com/ampedandwired/html-webpack-plugin
28 | new HtmlWebpackPlugin({
29 | filename: 'index.html',
30 | template: 'index.html',
31 | inject: true
32 | }),
33 | new FriendlyErrorsPlugin()
34 | ]
35 | })
36 |
--------------------------------------------------------------------------------
/vue-chat-client/build/check-versions.js:
--------------------------------------------------------------------------------
1 | var chalk = require('chalk')
2 | var semver = require('semver')
3 | var packageConfig = require('../package.json')
4 | var shell = require('shelljs')
5 | function exec (cmd) {
6 | return require('child_process').execSync(cmd).toString().trim()
7 | }
8 |
9 | var versionRequirements = [
10 | {
11 | name: 'node',
12 | currentVersion: semver.clean(process.version),
13 | versionRequirement: packageConfig.engines.node
14 | },
15 | ]
16 |
17 | if (shell.which('npm')) {
18 | versionRequirements.push({
19 | name: 'npm',
20 | currentVersion: exec('npm --version'),
21 | versionRequirement: packageConfig.engines.npm
22 | })
23 | }
24 |
25 | module.exports = function () {
26 | var warnings = []
27 | for (var i = 0; i < versionRequirements.length; i++) {
28 | var mod = versionRequirements[i]
29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
30 | warnings.push(mod.name + ': ' +
31 | chalk.red(mod.currentVersion) + ' should be ' +
32 | chalk.green(mod.versionRequirement)
33 | )
34 | }
35 | }
36 |
37 | if (warnings.length) {
38 | console.log('')
39 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
40 | console.log()
41 | for (var i = 0; i < warnings.length; i++) {
42 | var warning = warnings[i]
43 | console.log(' ' + warning)
44 | }
45 | console.log()
46 | process.exit(1)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/vue-chat-server/events/pushMsg.js:
--------------------------------------------------------------------------------
1 | const db = require("../models")
2 |
3 | module.exports = (socket, io) => ({ from, friendId, groupId, data }) => {
4 |
5 | if (!data.content || !from) {
6 | socket.emit('pushMsg', { status: 400, msg: '消息字段不全' })
7 | return
8 | }
9 |
10 | if (groupId) {
11 | createMsg({
12 | toGroup: groupId,
13 | from: from,
14 | content: data.content
15 | }).then(message => {
16 | io.sockets.emit(groupId, {
17 | from,
18 | data: message
19 | })
20 | })
21 | } else if (friendId) {
22 | createMsg({
23 | toUser: friendId,
24 | from: from,
25 | content: data.content
26 | }).then(message => {
27 | io.sockets.emit(friendId, {
28 | from,
29 | data: message
30 | })
31 | })
32 | } else {
33 | socket.emit('pushMsg', { status: 400, msg: '消息字段不全' })
34 | }
35 | }
36 |
37 |
38 | function createMsg(data) {
39 | return db.Message
40 | .create(data)
41 | .then(msg => {
42 | msg = msg.toObject();
43 | return db.User
44 | .findById(msg.from)
45 | .select('_id name avatar')
46 | .exec()
47 | .then(user => {
48 | msg.from = user
49 | return msg
50 | })
51 | })
52 | }
--------------------------------------------------------------------------------
/vue-chat-client/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 | var config = require('../config')
4 | var vueLoaderConfig = require('./vue-loader.conf')
5 |
6 | function resolve (dir) {
7 | return path.join(__dirname, '..', dir)
8 | }
9 |
10 | module.exports = {
11 | entry: {
12 | app: './src/main.js'
13 | },
14 | output: {
15 | path: config.build.assetsRoot,
16 | filename: '[name].js',
17 | publicPath: process.env.NODE_ENV === 'production'
18 | ? config.build.assetsPublicPath
19 | : config.dev.assetsPublicPath
20 | },
21 | resolve: {
22 | extensions: ['.js', '.vue', '.json'],
23 | alias: {
24 | 'vue$': 'vue/dist/vue.esm.js',
25 | '@': resolve('src')
26 | }
27 | },
28 | module: {
29 | rules: [
30 | {
31 | test: /\.vue$/,
32 | loader: 'vue-loader',
33 | options: vueLoaderConfig
34 | },
35 | {
36 | test: /\.js$/,
37 | loader: 'babel-loader',
38 | include: [resolve('src'), resolve('test')]
39 | },
40 | {
41 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
42 | loader: 'url-loader',
43 | options: {
44 | limit: 10000,
45 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
46 | }
47 | },
48 | {
49 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
50 | loader: 'url-loader',
51 | options: {
52 | limit: 10000,
53 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
54 | }
55 | }
56 | ]
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/vue-chat-client/src/store/mutations.js:
--------------------------------------------------------------------------------
1 | export default {
2 | setUser(state, { user }) {
3 | state.user = user
4 | },
5 | removeUser(state) {
6 | _.assign(state, {
7 | user: '',
8 | list: '',
9 | activeList: 'friends',
10 | currentOne: '',
11 | isChange: true,
12 | messages: [],
13 | count: '',
14 | })
15 | },
16 | setActiveList(state, { type }) {
17 | state.isChange = true
18 | state.activeList = type
19 | },
20 | setList(state, list) {
21 | state.list = list
22 | },
23 | setResult(state, { result }) {
24 | state.result = result
25 | },
26 | removeResult(state) {
27 | state.result = ''
28 | },
29 | changeCurrentOne(state, { item }) {
30 | state.isChange = true
31 | if (state.count[item._id]) {
32 | state.count = _.assign(state.count, {
33 | [item._id]: 0
34 | })
35 | }
36 | state.currentOne = item
37 | },
38 | removeCurrentOne(state) {
39 | state.currentOne = ''
40 | state.messages = []
41 | },
42 | notChange(state) {
43 | state.isChange = false
44 | },
45 | pullMsg(state, messages) {
46 | if (state.isChange) {
47 | state.messages = messages
48 | } else {
49 | state.messages = messages.concat(state.messages)
50 | }
51 | },
52 | pushMsg(state, msg) {
53 | state.messages.push(msg)
54 | },
55 | toggleFaces(state) {
56 | state.facesShow = !state.facesShow
57 | },
58 | hiddenFaces(state) {
59 | state.facesShow = false
60 | }
61 | }
--------------------------------------------------------------------------------
/vue-chat-client/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: 8080,
27 | autoOpenBrowser: true,
28 | assetsSubDirectory: 'static',
29 | assetsPublicPath: '/',
30 | proxyTable: {
31 | '/socket.io': {
32 | target: 'http://localhost:3002',
33 | ws: true,
34 | changeOrigin: true,
35 | },
36 | '/img': {
37 | target: 'http://localhost:3002',
38 | changeOrigin: true,
39 | }
40 | },
41 | // CSS Sourcemaps off by default because relative paths are "buggy"
42 | // with this option, according to the CSS-Loader README
43 | // (https://github.com/webpack/css-loader#sourcemaps)
44 | // In our experience, they generally work as expected,
45 | // just be aware of this issue when enabling this option.
46 | cssSourceMap: false
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/vue-chat-server/events/pullMsg.js:
--------------------------------------------------------------------------------
1 | const db = require("../models")
2 |
3 | module.exports = (socket, io) => ({ from, friendId, groupId, data }) => {
4 | let page = data.page || 1
5 |
6 | if (groupId) {
7 | findMsg({ toGroup: groupId }, page).then(coll => {
8 | socket.emit('pullMsg', {
9 | groupId,
10 | data: coll
11 | })
12 | }).catch(err => {
13 | socket.emit('pullMsg', err)
14 | })
15 |
16 |
17 | } else if (friendId && from) {
18 | let ids = [from, friendId]
19 |
20 | findMsg({ from: { $in: ids }, toUser: { $in: ids } }, page).then(coll => {
21 | socket.emit('pullMsg', {
22 | friendId,
23 | data: coll
24 | })
25 | }).catch(err => {
26 | socket.emit('pullMsg', err)
27 | })
28 |
29 | } else {
30 | socket.emit('pullMsg', { status: 400, msg: '消息字段缺失' })
31 | }
32 | }
33 |
34 |
35 | function findMsg(opt, page) {
36 | return db.Message
37 | .find(opt)
38 | .sort({ 'createAt': -1 })
39 | .skip((page - 1) * 10)
40 | .limit(10)
41 | .exec()
42 | .then(coll => {
43 | if (!coll) return []
44 |
45 | let promise_arr = []
46 | coll = coll.map(doc => doc.toObject())
47 |
48 | coll.forEach(doc => {
49 | let promise = db.User
50 | .findById(doc.from)
51 | .select('_id name avatar')
52 | .exec()
53 | .then(user => {
54 | doc.from = user.toObject()
55 | })
56 |
57 | promise_arr.push(promise)
58 | })
59 |
60 | return Promise.all(promise_arr).then(() => coll.reverse())
61 | })
62 | }
--------------------------------------------------------------------------------
/vue-chat-client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-chat",
3 | "version": "1.0.0",
4 | "description": "A Vue.js chat tool like wechat",
5 | "author": "",
6 | "private": true,
7 | "scripts": {
8 | "dev": "node build/dev-server.js",
9 | "start": "pm2 start build/dev-server.js --name vue-chat-client -e ../error.log -o ../access.log",
10 | "stop": "pm2 delete vue-chat-client",
11 | "restart": "pm2 restart vue-chat-client -e ../error.log -o ../access.log",
12 | "build": "node build/build.js"
13 | },
14 | "dependencies": {
15 | "element-ui": "^1.2.9",
16 | "vue": "^2.2.6",
17 | "vuex": "^2.3.1"
18 | },
19 | "devDependencies": {
20 | "autoprefixer": "^6.7.2",
21 | "babel-core": "^6.22.1",
22 | "babel-loader": "^6.2.10",
23 | "babel-plugin-transform-runtime": "^6.22.0",
24 | "babel-preset-env": "^1.3.2",
25 | "babel-preset-stage-2": "^6.22.0",
26 | "babel-register": "^6.22.0",
27 | "chalk": "^1.1.3",
28 | "connect-history-api-fallback": "^1.3.0",
29 | "copy-webpack-plugin": "^4.0.1",
30 | "css-loader": "^0.28.0",
31 | "eventsource-polyfill": "^0.9.6",
32 | "express": "^4.14.1",
33 | "extract-text-webpack-plugin": "^2.0.0",
34 | "file-loader": "^0.11.1",
35 | "friendly-errors-webpack-plugin": "^1.1.3",
36 | "html-webpack-plugin": "^2.28.0",
37 | "http-proxy-middleware": "^0.17.3",
38 | "less": "^2.7.2",
39 | "less-loader": "^4.0.3",
40 | "opn": "^4.0.2",
41 | "optimize-css-assets-webpack-plugin": "^1.3.0",
42 | "ora": "^1.2.0",
43 | "pm2": "^2.4.6",
44 | "rimraf": "^2.6.0",
45 | "semver": "^5.3.0",
46 | "shelljs": "^0.7.6",
47 | "url-loader": "^0.5.8",
48 | "vue-loader": "^11.3.4",
49 | "vue-style-loader": "^2.0.5",
50 | "vue-template-compiler": "^2.2.6",
51 | "webpack": "^2.3.3",
52 | "webpack-bundle-analyzer": "^2.2.1",
53 | "webpack-dev-middleware": "^1.10.0",
54 | "webpack-hot-middleware": "^2.18.0",
55 | "webpack-merge": "^4.1.0"
56 | },
57 | "engines": {
58 | "node": ">= 4.0.0",
59 | "npm": ">= 3.0.0"
60 | },
61 | "browserslist": [
62 | "> 1%",
63 | "last 2 versions",
64 | "not ie <= 8"
65 | ]
66 | }
67 |
--------------------------------------------------------------------------------
/vue-chat-client/src/components/avatar-reset.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
![]()
4 |
更换头像
5 |
8 |
9 |
10 |
11 |
54 |
55 |
--------------------------------------------------------------------------------
/vue-chat-client/build/utils.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var config = require('../config')
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
4 |
5 | exports.assetsPath = function (_path) {
6 | var assetsSubDirectory = process.env.NODE_ENV === 'production'
7 | ? config.build.assetsSubDirectory
8 | : config.dev.assetsSubDirectory
9 | return path.posix.join(assetsSubDirectory, _path)
10 | }
11 |
12 | exports.cssLoaders = function (options) {
13 | options = options || {}
14 |
15 | var cssLoader = {
16 | loader: 'css-loader',
17 | options: {
18 | minimize: process.env.NODE_ENV === 'production',
19 | sourceMap: options.sourceMap
20 | }
21 | }
22 |
23 | // generate loader string to be used with extract text plugin
24 | function generateLoaders (loader, loaderOptions) {
25 | var loaders = [cssLoader]
26 | if (loader) {
27 | loaders.push({
28 | loader: loader + '-loader',
29 | options: Object.assign({}, loaderOptions, {
30 | sourceMap: options.sourceMap
31 | })
32 | })
33 | }
34 |
35 | // Extract CSS when that option is specified
36 | // (which is the case during production build)
37 | if (options.extract) {
38 | return ExtractTextPlugin.extract({
39 | use: loaders,
40 | fallback: 'vue-style-loader'
41 | })
42 | } else {
43 | return ['vue-style-loader'].concat(loaders)
44 | }
45 | }
46 |
47 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
48 | return {
49 | css: generateLoaders(),
50 | postcss: generateLoaders(),
51 | less: generateLoaders('less'),
52 | sass: generateLoaders('sass', { indentedSyntax: true }),
53 | scss: generateLoaders('sass'),
54 | stylus: generateLoaders('stylus'),
55 | styl: generateLoaders('stylus')
56 | }
57 | }
58 |
59 | // Generate loaders for standalone style files (outside of .vue)
60 | exports.styleLoaders = function (options) {
61 | var output = []
62 | var loaders = exports.cssLoaders(options)
63 | for (var extension in loaders) {
64 | var loader = loaders[extension]
65 | output.push({
66 | test: new RegExp('\\.' + extension + '$'),
67 | use: loader
68 | })
69 | }
70 | return output
71 | }
72 |
--------------------------------------------------------------------------------
/vue-chat-client/build/dev-server.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')()
2 |
3 | var config = require('../config')
4 | if (!process.env.NODE_ENV) {
5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
6 | }
7 |
8 | var opn = require('opn')
9 | var path = require('path')
10 | var express = require('express')
11 | var webpack = require('webpack')
12 | var proxyMiddleware = require('http-proxy-middleware')
13 | var webpackConfig = require('./webpack.dev.conf')
14 |
15 | // default port where dev server listens for incoming traffic
16 | var port = process.env.PORT || config.dev.port
17 | // automatically open browser, if not set will be false
18 | var autoOpenBrowser = !!config.dev.autoOpenBrowser
19 | // Define HTTP proxies to your custom API backend
20 | // https://github.com/chimurai/http-proxy-middleware
21 | var proxyTable = config.dev.proxyTable
22 |
23 | var app = express()
24 | var compiler = webpack(webpackConfig)
25 |
26 | var devMiddleware = require('webpack-dev-middleware')(compiler, {
27 | publicPath: webpackConfig.output.publicPath,
28 | quiet: true
29 | })
30 |
31 | var hotMiddleware = require('webpack-hot-middleware')(compiler, {
32 | log: () => {}
33 | })
34 | // force page reload when html-webpack-plugin template changes
35 | compiler.plugin('compilation', function (compilation) {
36 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
37 | hotMiddleware.publish({ action: 'reload' })
38 | cb()
39 | })
40 | })
41 |
42 | // proxy api requests
43 | Object.keys(proxyTable).forEach(function (context) {
44 | var options = proxyTable[context]
45 | if (typeof options === 'string') {
46 | options = { target: options }
47 | }
48 | app.use(proxyMiddleware(options.filter || context, options))
49 | })
50 |
51 | // handle fallback for HTML5 history API
52 | app.use(require('connect-history-api-fallback')())
53 |
54 | // serve webpack bundle output
55 | app.use(devMiddleware)
56 |
57 | // enable hot-reload and state-preserving
58 | // compilation error display
59 | app.use(hotMiddleware)
60 |
61 | // serve pure static assets
62 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
63 | app.use(staticPath, express.static('./static'))
64 |
65 | var uri = 'http://localhost:' + port
66 |
67 | var _resolve
68 | var readyPromise = new Promise(resolve => {
69 | _resolve = resolve
70 | })
71 |
72 | console.log('> Starting dev server...')
73 | devMiddleware.waitUntilValid(() => {
74 | console.log('> Listening at ' + uri + '\n')
75 | // when env is testing, don't need open it
76 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
77 | opn(uri)
78 | }
79 | _resolve()
80 | })
81 |
82 | var server = app.listen(port)
83 |
84 | module.exports = {
85 | ready: readyPromise,
86 | close: () => {
87 | server.close()
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/vue-chat-client/src/common/socket.js:
--------------------------------------------------------------------------------
1 | // 连接webSocket
2 | const socket = io.connect("/")
3 |
4 | // 登陆后为所有群组添加监听器
5 | function addGroupsListener(user, state) {
6 | _.forEach(user.groups, item => {
7 | socket.on(item, ({ from, data }) => {
8 | if (data.type) return
9 | if (from !== user._id) {
10 | let notice = document.getElementById('notice')
11 | notice.play()
12 | }
13 | if (item === state.currentOne._id) {
14 | state.messages.push(data)
15 | } else {
16 | if (_.isString(state.count)) {
17 | state.count = { [item]: 1 }
18 | } else {
19 | let count = state.count[item]
20 | state.count = _.assign(state.count, {
21 | [item]: count ? ++count : 1
22 | })
23 | }
24 | }
25 | })
26 | })
27 | }
28 |
29 | // 登陆后移除前一个用户的消息监听器,并为新用户添加消息监听器
30 | function addUserListener(user, state) {
31 | let old = state.user
32 | socket.removeAllListeners(old._id)
33 | socket.on(user._id, ({ from, data }) => {
34 | let notice = document.getElementById('notice')
35 | notice.play()
36 | if (from === state.currentOne._id) {
37 | state.messages.push(data)
38 | } else {
39 | if (_.isString(state.count)) {
40 | state.count = { [from]: 1 }
41 | } else {
42 | let count = state.count[from]
43 | state.count = _.assign(state.count, {
44 | [from]: count ? ++count : 1
45 | })
46 | }
47 | }
48 | })
49 | }
50 |
51 | // 移除用户自身和所有群组的监听器
52 | function removeGroupAndUserListeners(user) {
53 | socket.removeAllListeners(user._id)
54 | _.forEach(user.groups, item => {
55 | socket.removeAllListeners(item)
56 | })
57 | }
58 |
59 | // 为单个群组添加监听
60 | function addSingleGroupListener(groupId, state) {
61 | socket.on(groupId, ({ from, data }) => {
62 | if (data.type) return
63 | if (from !== state.user._id) {
64 | let notice = document.getElementById('notice')
65 | notice.play()
66 | }
67 | if (groupId === state.currentOne._id) {
68 | state.messages.push(data)
69 | } else {
70 | if (_.isString(state.count)) {
71 | state.count = { [groupId]: 1 }
72 | } else {
73 | let count = state.count[groupId]
74 | state.count = _.assign(state.count, {
75 | [groupId]: count ? ++count : 1
76 | })
77 | }
78 | }
79 | })
80 | }
81 |
82 | // 删除单个群组的监听
83 | function removeSingleGroupListener(groupId) {
84 | socket.removeAllListeners(groupId)
85 | }
86 |
87 | export {
88 | socket,
89 | addGroupsListener,
90 | addUserListener,
91 | removeGroupAndUserListeners,
92 | addSingleGroupListener,
93 | removeSingleGroupListener
94 | }
--------------------------------------------------------------------------------
/vue-chat-client/src/assets/font-icon/demo_fontclass.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | IconFont
7 |
8 |
9 |
10 |
11 |
12 |
IconFont 图标
13 |
14 |
15 | -
16 |
17 |
用户
18 | .icon-weibiaoti114
19 |
20 |
21 | -
22 |
23 |
用户
24 | .icon-icon-yxj-user
25 |
26 |
27 | -
28 |
29 |
群组
30 | .icon-qunzu
31 |
32 |
33 | -
34 |
35 |
客户群组
36 | .icon-kehuqunzu
37 |
38 |
39 | -
40 |
41 |
编辑
42 | .icon-bianji
43 |
44 |
45 | -
46 |
47 |
加
48 | .icon-jia
49 |
50 |
51 | -
52 |
53 |
关闭
54 | .icon-guanbi
55 |
56 |
57 | -
58 |
59 |
图像
60 | .icon-tuxiang
61 |
62 |
63 | -
64 |
65 |
搜索
66 | .icon-sousuo
67 |
68 |
69 |
70 |
71 |
font-class引用
72 |
73 |
74 |
font-class是unicode使用方式的一种变种,主要是解决unicode书写不直观,语意不明确的问题。
75 |
与unicode使用方式相比,具有如下特点:
76 |
77 | - 兼容性良好,支持ie8+,及所有现代浏览器。
78 | - 相比于unicode语意明确,书写更直观。可以很容易分辨这个icon是什么。
79 | - 因为使用class来定义图标,所以当要替换图标时,只需要修改class里面的unicode引用。
80 | - 不过因为本质上还是使用的字体,所以多色图标还是不支持的。
81 |
82 |
使用步骤如下:
83 |
第一步:引入项目下面生成的fontclass代码:
84 |
85 |
86 |
87 |
第二步:挑选相应图标并获取类名,应用于页面:
88 |
<i class="iconfont icon-xxx"></i>
89 |
90 | "iconfont"是你项目下的font-family。可以通过编辑项目查看,默认是"iconfont"。
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/vue-chat-client/src/assets/styles/css_initialize.css:
--------------------------------------------------------------------------------
1 | body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, form, fieldset, legend, button, input, textarea, th, td { margin:0; padding:0; }
2 | h1, h2, h3, h4, h5, h6{ font-size:100%; }
3 | address, cite, dfn, em, var { font-style:normal; }
4 | code, kbd, pre, samp { font-family:couriernew, courier, monospace; }
5 | small{ font-size:12px; }
6 | ul{ list-style:none; }
7 | a, *[href] { text-decoration:none; cursor: pointer; color: #333;}
8 | a:hover { text-decoration:underline; }
9 | sup { vertical-align:text-top; }
10 | sub{ vertical-align:text-bottom; }
11 | legend { color:#000; }
12 | fieldset, img { border:0; }
13 | button, input, select, textarea { font-size:100%; }
14 | table { border-collapse:collapse; border-spacing:0; }
15 |
16 | html, body {
17 | height: 100%;
18 | overflow: hidden;
19 | box-sizing: border-box;
20 | }
21 |
22 | body {
23 | background: url("../bg.jpg") no-repeat;
24 | background-size: cover;
25 | }
26 |
27 | .bg-green {
28 | background-color: #3caf36;
29 | }
30 | .bg-greenlight {
31 | background-color: #4bc0a5;
32 | }
33 | .bg-white {
34 | background-color: #ffffff;
35 | }
36 | .bg-black {
37 | background-color: #3f495b;
38 | }
39 | .bg-orange {
40 | background-color: #ffb300;
41 | }
42 | .bg-orangelight {
43 | background-color: #ffbe29;
44 | }
45 | .bg-red {
46 | background-color: #ea3434;
47 | }
48 | .bg-pale {
49 | background-color: #f6f9f9;
50 | }
51 | .bg-gray {
52 | background-color: #cacaca;
53 | }
54 | .text-green {
55 | color: #3caf36;
56 | }
57 | .text-init {
58 | color: #666666;
59 | }
60 | .text-red {
61 | color: #ea3434;
62 | }
63 | .text-blue {
64 | color: #0087ca;
65 | }
66 | .text-orange {
67 | color: #ffb300;
68 | }
69 | .text-yellow {
70 | color: #fdfd90;
71 | }
72 | .text-white {
73 | color: #fdfdfd;
74 | }
75 | .text-gray {
76 | color: #cacaca;
77 | }
78 | .text-center {
79 | text-align: center;
80 | }
81 | .text-left {
82 | text-align: left;
83 | }
84 | .text-right {
85 | text-align: right;
86 | }
87 |
88 | .font-bold {
89 | font-weight: bold;
90 | }
91 | .font-normal {
92 | font-weight: normal;
93 | }
94 | .font-bigger {
95 | font-size: 1.2em;
96 | }
97 | .font-big {
98 | font-size: 24px;
99 | }
100 | .cursor-pointer {
101 | cursor: pointer;
102 | padding: 15px;
103 | }
104 | .padding-24 {
105 | padding: 24px;
106 | }
107 | .padding-15 {
108 | padding: 15px;
109 | }
110 | .padding-10 {
111 | padding: 10px;
112 | }
113 | .padding-top-10 {
114 | padding-top: 10px;
115 | }
116 | .padding-top-bottom-8 {
117 | padding-top: 8px;
118 | padding-bottom: 8px;
119 | }
120 | .padding-5 {
121 | padding: 5px;
122 | }
123 | .padding-right-15 {
124 | padding-right: 15px;
125 | }
126 | .border-bottom {
127 | border-bottom: 1px solid #4bc0a5;
128 | }
129 | .link:hover {
130 | cursor: pointer;
131 | }
132 | .hover-black:hover {
133 | background-color: #3a3f45;
134 | }
135 |
136 | .scrollbar {
137 | overflow-y: auto
138 | }
139 |
140 | .scrollbar::-webkit-scrollbar {
141 | width: 8px;
142 | }
143 | .scrollbar::-webkit-scrollbar-track {
144 | background-color: #fdfdfd;
145 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
146 | -webkit-border-radius: 4px;
147 | border-radius: 4px;
148 | } /* 滚动条的滑轨背景颜色 */
149 |
150 | .scrollbar::-webkit-scrollbar-thumb {
151 | -webkit-border-radius: 4px;
152 | border-radius: 4px;
153 | background-color: rgba(25, 25, 25, 0.7);
154 | } /* 滑块颜色 */
155 |
156 | .scrollbar::-webkit-scrollbar-button {
157 | display: none;
158 | background-color: #7c2929;
159 | } /* 滑轨两头的监听按钮颜色 */
160 |
161 |
162 | .pull-left {
163 | float: left;
164 | }
165 | .pull-right {
166 | float: right;
167 | }
168 | .clearfix:after {
169 | content: '';
170 | height: 0;
171 | display: block;
172 | clear: both;
173 | }
--------------------------------------------------------------------------------
/vue-chat-client/src/components/signinAndSignup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | 登录
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 注册
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | {{error.msg}}
38 |
39 | 确 定
40 |
41 |
42 |
43 |
44 |
45 |
116 |
117 |
--------------------------------------------------------------------------------
/vue-chat-client/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 | var webpack = require('webpack')
4 | var config = require('../config')
5 | var merge = require('webpack-merge')
6 | var baseWebpackConfig = require('./webpack.base.conf')
7 | var CopyWebpackPlugin = require('copy-webpack-plugin')
8 | var HtmlWebpackPlugin = require('html-webpack-plugin')
9 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
11 |
12 | var env = config.build.env
13 |
14 | var webpackConfig = merge(baseWebpackConfig, {
15 | module: {
16 | rules: utils.styleLoaders({
17 | sourceMap: config.build.productionSourceMap,
18 | extract: true
19 | })
20 | },
21 | devtool: config.build.productionSourceMap ? '#source-map' : false,
22 | output: {
23 | path: config.build.assetsRoot,
24 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
25 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
26 | },
27 | plugins: [
28 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
29 | new webpack.DefinePlugin({
30 | 'process.env': env
31 | }),
32 | new webpack.optimize.UglifyJsPlugin({
33 | compress: {
34 | warnings: false
35 | },
36 | sourceMap: true
37 | }),
38 | // extract css into its own file
39 | new ExtractTextPlugin({
40 | filename: utils.assetsPath('css/[name].[contenthash].css')
41 | }),
42 | // Compress extracted CSS. We are using this plugin so that possible
43 | // duplicated CSS from different components can be deduped.
44 | new OptimizeCSSPlugin({
45 | cssProcessorOptions: {
46 | safe: true
47 | }
48 | }),
49 | // generate dist index.html with correct asset hash for caching.
50 | // you can customize output by editing /index.html
51 | // see https://github.com/ampedandwired/html-webpack-plugin
52 | new HtmlWebpackPlugin({
53 | filename: config.build.index,
54 | template: 'index.html',
55 | inject: true,
56 | minify: {
57 | removeComments: true,
58 | collapseWhitespace: true,
59 | removeAttributeQuotes: true
60 | // more options:
61 | // https://github.com/kangax/html-minifier#options-quick-reference
62 | },
63 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
64 | chunksSortMode: 'dependency'
65 | }),
66 | // split vendor js into its own file
67 | new webpack.optimize.CommonsChunkPlugin({
68 | name: 'vendor',
69 | minChunks: function (module, count) {
70 | // any required modules inside node_modules are extracted to vendor
71 | return (
72 | module.resource &&
73 | /\.js$/.test(module.resource) &&
74 | module.resource.indexOf(
75 | path.join(__dirname, '../node_modules')
76 | ) === 0
77 | )
78 | }
79 | }),
80 | // extract webpack runtime and module manifest to its own file in order to
81 | // prevent vendor hash from being updated whenever app bundle is updated
82 | new webpack.optimize.CommonsChunkPlugin({
83 | name: 'manifest',
84 | chunks: ['vendor']
85 | }),
86 | // copy custom static assets
87 | new CopyWebpackPlugin([
88 | {
89 | from: path.resolve(__dirname, '../static'),
90 | to: config.build.assetsSubDirectory,
91 | ignore: ['.*']
92 | }
93 | ])
94 | ]
95 | })
96 |
97 | if (config.build.productionGzip) {
98 | var CompressionWebpackPlugin = require('compression-webpack-plugin')
99 |
100 | webpackConfig.plugins.push(
101 | new CompressionWebpackPlugin({
102 | asset: '[path].gz[query]',
103 | algorithm: 'gzip',
104 | test: new RegExp(
105 | '\\.(' +
106 | config.build.productionGzipExtensions.join('|') +
107 | ')$'
108 | ),
109 | threshold: 10240,
110 | minRatio: 0.8
111 | })
112 | )
113 | }
114 |
115 | if (config.build.bundleAnalyzerReport) {
116 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
117 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
118 | }
119 |
120 | module.exports = webpackConfig
121 |
--------------------------------------------------------------------------------
/vue-chat-client/src/assets/font-icon/demo_unicode.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | IconFont
7 |
8 |
9 |
29 |
30 |
31 |
32 |
IconFont 图标
33 |
34 |
35 | -
36 |
37 |
用户
38 | 
39 |
40 |
41 | -
42 |
43 |
用户
44 | 
45 |
46 |
47 | -
48 |
49 |
群组
50 | 
51 |
52 |
53 | -
54 |
55 |
客户群组
56 | 
57 |
58 |
59 | -
60 |
61 |
编辑
62 | 󰌄
63 |
64 |
65 | -
66 |
67 |
加
68 | 󰌇
69 |
70 |
71 | -
72 |
73 |
关闭
74 | 󰌈
75 |
76 |
77 | -
78 |
79 |
图像
80 | 󰌋
81 |
82 |
83 | -
84 |
85 |
搜索
86 | 󰌍
87 |
88 |
89 |
90 |
unicode引用
91 |
92 |
93 |
unicode是字体在网页端最原始的应用方式,特点是:
94 |
95 | - 兼容性最好,支持ie6+,及所有现代浏览器。
96 | - 支持按字体的方式去动态调整图标大小,颜色等等。
97 | - 但是因为是字体,所以不支持多色。只能使用平台里单色的图标,就算项目里有多色图标也会自动去色。
98 |
99 |
100 | 注意:新版iconfont支持多色图标,这些多色图标在unicode模式下将不能使用,如果有需求建议使用symbol的引用方式
101 |
102 |
unicode使用步骤如下:
103 |
第一步:拷贝项目下面生成的font-face
104 |
@font-face {
105 | font-family: 'iconfont';
106 | src: url('iconfont.eot');
107 | src: url('iconfont.eot?#iefix') format('embedded-opentype'),
108 | url('iconfont.woff') format('woff'),
109 | url('iconfont.ttf') format('truetype'),
110 | url('iconfont.svg#iconfont') format('svg');
111 | }
112 |
113 |
第二步:定义使用iconfont的样式
114 |
.iconfont{
115 | font-family:"iconfont" !important;
116 | font-size:16px;font-style:normal;
117 | -webkit-font-smoothing: antialiased;
118 | -webkit-text-stroke-width: 0.2px;
119 | -moz-osx-font-smoothing: grayscale;
120 | }
121 |
122 |
第三步:挑选相应图标并获取字体编码,应用于页面
123 |
<i class="iconfont">3</i>
124 |
125 |
126 | "iconfont"是你项目下的font-family。可以通过编辑项目查看,默认是"iconfont"。
127 |
128 |
129 |
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/vue-chat-client/src/assets/font-icon/iconfont.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
65 |
--------------------------------------------------------------------------------
/vue-chat-client/src/common/ws.js:
--------------------------------------------------------------------------------
1 | import { socket } from './socket'
2 |
3 | export default {
4 | getUser(from) {
5 | return new Promise((res, rej) => {
6 | socket.emit('getUser', { from })
7 | socket.once('getUser', data => {
8 | if (data.msg || data.message) return rej(data)
9 | return res(data)
10 | })
11 | })
12 | },
13 | signIn(payload) {
14 | return new Promise((res, rej) => {
15 | socket.emit('signIn', payload)
16 | socket.once('signIn', data => {
17 | if (data.msg || data.message) return rej(data)
18 | return res(data)
19 | })
20 | })
21 | },
22 | signUp(payload) {
23 | return new Promise((res, rej) => {
24 | socket.emit('signUp', payload)
25 | socket.once('signUp', data => {
26 | if (data.msg || data.message) return rej(data)
27 | return res(data)
28 | })
29 | })
30 | },
31 | getFriends(from) {
32 | return new Promise((res, rej) => {
33 | socket.emit('getFriends', { from })
34 | socket.once('getFriends', data => {
35 | if (data.msg || data.message) return rej(data)
36 | return res(data)
37 | })
38 | })
39 | },
40 | getGroups(from) {
41 | return new Promise((res, rej) => {
42 | socket.emit('getGroups', { from })
43 | socket.once('getGroups', data => {
44 | if (data.msg || data.message) return rej(data)
45 | return res(data)
46 | })
47 | })
48 | },
49 | searchUsers({ keyword, from }) {
50 | return new Promise((res, rej) => {
51 | socket.emit('searchUsers', { keyword, from })
52 | socket.once('searchUsers', data => {
53 | if (data.msg || data.message) return rej(data)
54 | return res(data)
55 | })
56 | })
57 | },
58 | searchGroups(keyword) {
59 | return new Promise((res, rej) => {
60 | socket.emit('searchGroups', { keyword })
61 | socket.once('searchGroups', data => {
62 | if (data.msg || data.message) return rej(data)
63 | return res(data)
64 | })
65 | })
66 | },
67 | createGroup(params) {
68 | return new Promise((res, rej) => {
69 | socket.emit('createGroup', params)
70 | socket.once('createGroup', data => {
71 | if (data.msg || data.message) return rej(data)
72 | return res(data)
73 | })
74 | })
75 | },
76 | addGroup(params) {
77 | return new Promise((res, rej) => {
78 | socket.emit('addGroup', params)
79 | socket.once('addGroup', data => {
80 | if (data.msg || data.message) return rej(data)
81 | return res(data)
82 | })
83 | })
84 | },
85 | addFriend(params) {
86 | return new Promise((res, rej) => {
87 | socket.emit('addFriend', params)
88 | socket.once('addFriend', data => {
89 | if (data.msg || data.message) return rej(data)
90 | return res(data)
91 | })
92 | })
93 | },
94 | pullMsg(params) {
95 | return new Promise((res, rej) => {
96 | socket.emit('pullMsg', params)
97 | socket.once('pullMsg', data => {
98 | if (data.msg || data.message) return rej(data)
99 | return res(data)
100 | })
101 | })
102 | },
103 | pushMsg(params) {
104 | socket.emit('pushMsg', params)
105 | },
106 | removeFriend(params) {
107 | return new Promise((res, rej) => {
108 | socket.emit('removeFriend', params)
109 | socket.once('removeFriend', data => {
110 | if (data.msg || data.message) return rej(data)
111 | return res(data)
112 | })
113 | })
114 | },
115 | removeGroup(params) {
116 | return new Promise((res, rej) => {
117 | socket.emit('removeGroup', params)
118 | socket.once('removeGroup', data => {
119 | if (data.msg || data.message) return rej(data)
120 | return res(data)
121 | })
122 | })
123 | },
124 | getGroupMember(params) {
125 | return new Promise((res, rej) => {
126 | socket.emit('getGroupMember', params)
127 | socket.once('getGroupMember', data => {
128 | if (data.msg || data.message) return rej(data)
129 | return res(data)
130 | })
131 | })
132 | },
133 | updateUser(params) {
134 | return new Promise((res, rej) => {
135 | socket.emit('updateUser', params)
136 | socket.once('updateUser', data => {
137 | if (data.msg || data.message) return rej(data)
138 | return res(data)
139 | })
140 | })
141 | },
142 | modifyAvatar(params) {
143 | return new Promise((res, rej) => {
144 | socket.emit('modifyAvatar', params)
145 | socket.once('modifyAvatar', data => {
146 | if (data.msg || data.message) return rej(data)
147 | return res(data)
148 | })
149 | })
150 | },
151 | uploadImg(params) {
152 | return new Promise((res, rej) => {
153 | socket.emit('uploadImg', params)
154 | socket.once('uploadImg', data => {
155 | if (data.msg || data.message) return rej(data)
156 | return res(data)
157 | })
158 | })
159 | }
160 |
161 | }
--------------------------------------------------------------------------------
/vue-chat-client/src/assets/font-icon/demo_symbol.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | IconFont
7 |
8 |
9 |
10 |
24 |
25 |
26 |
27 |
IconFont 图标
28 |
29 |
30 | -
31 |
34 |
用户
35 | #icon-weibiaoti114
36 |
37 |
38 | -
39 |
42 |
用户
43 | #icon-icon-yxj-user
44 |
45 |
46 | -
47 |
50 |
群组
51 | #icon-qunzu
52 |
53 |
54 | -
55 |
58 |
客户群组
59 | #icon-kehuqunzu
60 |
61 |
62 | -
63 |
66 |
编辑
67 | #icon-bianji
68 |
69 |
70 | -
71 |
74 |
加
75 | #icon-jia
76 |
77 |
78 | -
79 |
82 |
关闭
83 | #icon-guanbi
84 |
85 |
86 | -
87 |
90 |
图像
91 | #icon-tuxiang
92 |
93 |
94 | -
95 |
98 |
搜索
99 | #icon-sousuo
100 |
101 |
102 |
103 |
104 |
105 |
symbol引用
106 |
107 |
108 |
这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇文章
109 | 这种用法其实是做了一个svg的集合,与另外两种相比具有如下特点:
110 |
111 | - 支持多色图标了,不再受单色限制。
112 | - 通过一些技巧,支持像字体那样,通过
font-size,color来调整样式。
113 | - 兼容性较差,支持 ie9+,及现代浏览器。
114 | - 浏览器渲染svg的性能一般,还不如png。
115 |
116 |
使用步骤如下:
117 |
第一步:引入项目下面生成的symbol代码:
118 |
119 |
第二步:加入通用css代码(引入一次就行):
120 |
<style type="text/css">
121 | .icon {
122 | width: 1em; height: 1em;
123 | vertical-align: -0.15em;
124 | fill: currentColor;
125 | overflow: hidden;
126 | }
127 | </style>
128 |
第三步:挑选相应图标并获取类名,应用于页面:
129 |
<svg class="icon" aria-hidden="true">
130 | <use xlink:href="#icon-xxx"></use>
131 | </svg>
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/vue-chat-client/src/store/actions.js:
--------------------------------------------------------------------------------
1 | import {
2 | addGroupsListener,
3 | addUserListener,
4 | removeGroupAndUserListeners,
5 | addSingleGroupListener,
6 | removeSingleGroupListener
7 | } from '@/common/socket'
8 |
9 | import ws from '@/common/ws'
10 |
11 |
12 |
13 | export default {
14 | getUser({ commit, state }) {
15 | return ws.getUser(state.user._id).then(user => {
16 | commit('setUser', { user })
17 | })
18 | },
19 | signIn({ state, commit }, payload) {
20 | return ws.signIn(payload).then(user => {
21 | // 初始化用户监听器
22 | addUserListener(user, state)
23 | // 初始化群组监听器
24 | addGroupsListener(user, state)
25 | commit('setUser', { user })
26 | })
27 | },
28 | signUp({ commit, state }, payload) {
29 | return ws.signUp(payload).then(user => {
30 | addUserListener(user, state)
31 | addGroupsListener(user, state)
32 | commit('setUser', { user })
33 | })
34 | },
35 | signOut({ state, commit, dispatch }) {
36 | ws.getUser(state.user._id).then(removeGroupAndUserListeners)
37 | commit('removeUser')
38 | },
39 | setActiveList({ commit }, type) {
40 | commit('setActiveList', { type })
41 | commit('removeCurrentOne')
42 | },
43 | getList({ commit, state }) {
44 | if (state.activeList === 'friends') {
45 | return ws.getFriends(state.user._id)
46 | .then(list => commit('setList', list))
47 | } else {
48 | return ws.getGroups(state.user._id)
49 | .then(list => commit('setList', list))
50 | }
51 | },
52 | createGroup({ state, dispatch }, keyword) {
53 | return ws.createGroup({
54 | from: state.user._id,
55 | data: { name: keyword }
56 | }).then(({ groupId }) => {
57 | addSingleGroupListener(groupId, state)
58 | return dispatch('getList')
59 | })
60 | },
61 | searchUsers({ state, commit }, keyword) {
62 | return ws.searchUsers({
63 | keyword,
64 | from: state.user._id
65 | }).then(result => {
66 | commit('setResult', { result })
67 | })
68 | },
69 | searchGroups({ commit }, keyword) {
70 | return ws.searchGroups(keyword).then(result => {
71 | commit('setResult', { result })
72 | })
73 | },
74 | addGroup({ state, dispatch }, groupId) {
75 | addSingleGroupListener(groupId, state)
76 | return ws.addGroup({
77 | from: state.user._id,
78 | groupId
79 | }).then(() => {
80 | return dispatch('getList')
81 | })
82 | },
83 | addFriend({ state, dispatch }, friendId) {
84 | return ws.addFriend({
85 | from: state.user._id,
86 | friendId
87 | }).then(() => {
88 | return dispatch('getList')
89 | })
90 | },
91 | removeResult({ commit }) {
92 | commit('removeResult')
93 | },
94 | changeCurrentOne({ commit }, item) {
95 | commit('changeCurrentOne', { item })
96 | },
97 | pullMsg({ commit, state }, page) {
98 | let params = {
99 | from: state.user._id,
100 | data: { page }
101 | }
102 | if (state.activeList === 'friends') {
103 | params.friendId = state.currentOne._id
104 | } else {
105 | params.groupId = state.currentOne._id
106 | }
107 |
108 | return ws.pullMsg(params).then(({ data }) => {
109 | commit('pullMsg', data)
110 | if (data.length < 1) return false
111 | })
112 | },
113 | pushMsg({ commit, state }, content) {
114 | let params = {
115 | from: state.user._id,
116 | data: { content }
117 | }
118 |
119 | if (state.activeList === 'friends') {
120 | params.friendId = state.currentOne._id
121 | } else {
122 | params.groupId = state.currentOne._id
123 | }
124 |
125 | ws.pushMsg(params)
126 | if (state.activeList === 'friends') {
127 | commit('pushMsg', {
128 | from: _.pick(state.user, ['_id', 'name', 'avatar']),
129 | content
130 | })
131 | }
132 | },
133 | removeFriend({ commit, state, dispatch }) {
134 | let id = state.currentOne._id
135 | let params = {
136 | from: state.user._id,
137 | friendId: id
138 | }
139 |
140 | if (state.count[id]) {
141 | state.count = _.assign(state.count, {
142 | [id]: 0
143 | })
144 | }
145 |
146 | return ws.removeFriend(params).then(() => {
147 | commit('removeCurrentOne')
148 | dispatch('getList')
149 | })
150 | },
151 | removeGroup({ commit, state, dispatch }) {
152 | let id = state.currentOne._id
153 | let params = {
154 | from: state.user._id,
155 | groupId: id
156 | }
157 |
158 | // 同时移除群组的消息监听
159 | removeSingleGroupListener(id)
160 |
161 | if (state.count[id]) {
162 | state.count = _.assign(state.count, {
163 | [id]: 0
164 | })
165 | }
166 |
167 | return ws.removeGroup(params).then(() => {
168 | commit('removeCurrentOne')
169 | dispatch('getList')
170 | })
171 | },
172 | getGroupMember({ state }) {
173 | let groupId = state.currentOne._id
174 | return ws.getGroupMember({ groupId }).then(({ data }) => data)
175 | },
176 | updateUser({ state, commit }, data) {
177 | let params = {
178 | from: state.user._id,
179 | data
180 | }
181 |
182 | return ws.updateUser(params).then(user => {
183 | commit('setUser', { user })
184 | return user
185 | })
186 | },
187 | modifyAvatar({ state, commit }, data) {
188 | let params = {
189 | from: state.user._id,
190 | data
191 | }
192 |
193 | return ws.modifyAvatar(params).then(user => commit('setUser', { user }))
194 | },
195 | uploadImg({ state, commit }, data) {
196 | let params = {
197 | from: state.user._id,
198 | data
199 | }
200 |
201 | return ws.uploadImg(params)
202 | }
203 | }
--------------------------------------------------------------------------------
/vue-chat-client/src/assets/font-icon/demo.css:
--------------------------------------------------------------------------------
1 | *{margin: 0;padding: 0;list-style: none;}
2 | /*
3 | KISSY CSS Reset
4 | 理念:1. reset 的目的不是清除浏览器的默认样式,这仅是部分工作。清除和重置是紧密不可分的。
5 | 2. reset 的目的不是让默认样式在所有浏览器下一致,而是减少默认样式有可能带来的问题。
6 | 3. reset 期望提供一套普适通用的基础样式。但没有银弹,推荐根据具体需求,裁剪和修改后再使用。
7 | 特色:1. 适应中文;2. 基于最新主流浏览器。
8 | 维护:玉伯, 正淳
9 | */
10 |
11 | /** 清除内外边距 **/
12 | body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, /* structural elements 结构元素 */
13 | dl, dt, dd, ul, ol, li, /* list elements 列表元素 */
14 | pre, /* text formatting elements 文本格式元素 */
15 | form, fieldset, legend, button, input, textarea, /* form elements 表单元素 */
16 | th, td /* table elements 表格元素 */ {
17 | margin: 0;
18 | padding: 0;
19 | }
20 |
21 | /** 设置默认字体 **/
22 | body,
23 | button, input, select, textarea /* for ie */ {
24 | font: 12px/1.5 tahoma, arial, \5b8b\4f53, sans-serif;
25 | }
26 | h1, h2, h3, h4, h5, h6 { font-size: 100%; }
27 | address, cite, dfn, em, var { font-style: normal; } /* 将斜体扶正 */
28 | code, kbd, pre, samp { font-family: courier new, courier, monospace; } /* 统一等宽字体 */
29 | small { font-size: 12px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */
30 |
31 | /** 重置列表元素 **/
32 | ul, ol { list-style: none; }
33 |
34 | /** 重置文本格式元素 **/
35 | a { text-decoration: none; }
36 | a:hover { text-decoration: underline; }
37 |
38 |
39 | /** 重置表单元素 **/
40 | legend { color: #000; } /* for ie6 */
41 | fieldset, img { border: 0; } /* img 搭车:让链接里的 img 无边框 */
42 | button, input, select, textarea { font-size: 100%; } /* 使得表单元素在 ie 下能继承字体大小 */
43 | /* 注:optgroup 无法扶正 */
44 |
45 | /** 重置表格元素 **/
46 | table { border-collapse: collapse; border-spacing: 0; }
47 |
48 | /* 清除浮动 */
49 | .ks-clear:after, .clear:after {
50 | content: '\20';
51 | display: block;
52 | height: 0;
53 | clear: both;
54 | }
55 | .ks-clear, .clear {
56 | *zoom: 1;
57 | }
58 |
59 | .main {
60 | padding: 30px 100px;
61 | width: 960px;
62 | margin: 0 auto;
63 | }
64 | .main h1{font-size:36px; color:#333; text-align:left;margin-bottom:30px; border-bottom: 1px solid #eee;}
65 |
66 | .helps{margin-top:40px;}
67 | .helps pre{
68 | padding:20px;
69 | margin:10px 0;
70 | border:solid 1px #e7e1cd;
71 | background-color: #fffdef;
72 | overflow: auto;
73 | }
74 |
75 | .icon_lists{
76 | width: 100% !important;
77 |
78 | }
79 |
80 | .icon_lists li{
81 | float:left;
82 | width: 100px;
83 | height:180px;
84 | text-align: center;
85 | list-style: none !important;
86 | }
87 | .icon_lists .icon{
88 | font-size: 42px;
89 | line-height: 100px;
90 | margin: 10px 0;
91 | color:#333;
92 | -webkit-transition: font-size 0.25s ease-out 0s;
93 | -moz-transition: font-size 0.25s ease-out 0s;
94 | transition: font-size 0.25s ease-out 0s;
95 |
96 | }
97 | .icon_lists .icon:hover{
98 | font-size: 100px;
99 | }
100 |
101 |
102 |
103 | .markdown {
104 | color: #666;
105 | font-size: 14px;
106 | line-height: 1.8;
107 | }
108 |
109 | .highlight {
110 | line-height: 1.5;
111 | }
112 |
113 | .markdown img {
114 | vertical-align: middle;
115 | max-width: 100%;
116 | }
117 |
118 | .markdown h1 {
119 | color: #404040;
120 | font-weight: 500;
121 | line-height: 40px;
122 | margin-bottom: 24px;
123 | }
124 |
125 | .markdown h2,
126 | .markdown h3,
127 | .markdown h4,
128 | .markdown h5,
129 | .markdown h6 {
130 | color: #404040;
131 | margin: 1.6em 0 0.6em 0;
132 | font-weight: 500;
133 | clear: both;
134 | }
135 |
136 | .markdown h1 {
137 | font-size: 28px;
138 | }
139 |
140 | .markdown h2 {
141 | font-size: 22px;
142 | }
143 |
144 | .markdown h3 {
145 | font-size: 16px;
146 | }
147 |
148 | .markdown h4 {
149 | font-size: 14px;
150 | }
151 |
152 | .markdown h5 {
153 | font-size: 12px;
154 | }
155 |
156 | .markdown h6 {
157 | font-size: 12px;
158 | }
159 |
160 | .markdown hr {
161 | height: 1px;
162 | border: 0;
163 | background: #e9e9e9;
164 | margin: 16px 0;
165 | clear: both;
166 | }
167 |
168 | .markdown p,
169 | .markdown pre {
170 | margin: 1em 0;
171 | }
172 |
173 | .markdown > p,
174 | .markdown > blockquote,
175 | .markdown > .highlight,
176 | .markdown > ol,
177 | .markdown > ul {
178 | width: 80%;
179 | }
180 |
181 | .markdown ul > li {
182 | list-style: circle;
183 | }
184 |
185 | .markdown > ul li,
186 | .markdown blockquote ul > li {
187 | margin-left: 20px;
188 | padding-left: 4px;
189 | }
190 |
191 | .markdown > ul li p,
192 | .markdown > ol li p {
193 | margin: 0.6em 0;
194 | }
195 |
196 | .markdown ol > li {
197 | list-style: decimal;
198 | }
199 |
200 | .markdown > ol li,
201 | .markdown blockquote ol > li {
202 | margin-left: 20px;
203 | padding-left: 4px;
204 | }
205 |
206 | .markdown code {
207 | margin: 0 3px;
208 | padding: 0 5px;
209 | background: #eee;
210 | border-radius: 3px;
211 | }
212 |
213 | .markdown pre {
214 | border-radius: 6px;
215 | background: #f7f7f7;
216 | padding: 20px;
217 | }
218 |
219 | .markdown pre code {
220 | border: none;
221 | background: #f7f7f7;
222 | margin: 0;
223 | }
224 |
225 | .markdown strong,
226 | .markdown b {
227 | font-weight: 600;
228 | }
229 |
230 | .markdown > table {
231 | border-collapse: collapse;
232 | border-spacing: 0px;
233 | empty-cells: show;
234 | border: 1px solid #e9e9e9;
235 | width: 95%;
236 | margin-bottom: 24px;
237 | }
238 |
239 | .markdown > table th {
240 | white-space: nowrap;
241 | color: #333;
242 | font-weight: 600;
243 |
244 | }
245 |
246 | .markdown > table th,
247 | .markdown > table td {
248 | border: 1px solid #e9e9e9;
249 | padding: 8px 16px;
250 | text-align: left;
251 | }
252 |
253 | .markdown > table th {
254 | background: #F7F7F7;
255 | }
256 |
257 | .markdown blockquote {
258 | font-size: 90%;
259 | color: #999;
260 | border-left: 4px solid #e9e9e9;
261 | padding-left: 0.8em;
262 | margin: 1em 0;
263 | font-style: italic;
264 | }
265 |
266 | .markdown blockquote p {
267 | margin: 0;
268 | }
269 |
270 | .markdown .anchor {
271 | opacity: 0;
272 | transition: opacity 0.3s ease;
273 | margin-left: 8px;
274 | }
275 |
276 | .markdown .waiting {
277 | color: #ccc;
278 | }
279 |
280 | .markdown h1:hover .anchor,
281 | .markdown h2:hover .anchor,
282 | .markdown h3:hover .anchor,
283 | .markdown h4:hover .anchor,
284 | .markdown h5:hover .anchor,
285 | .markdown h6:hover .anchor {
286 | opacity: 1;
287 | display: inline-block;
288 | }
289 |
290 | .markdown > br,
291 | .markdown > p > br {
292 | clear: both;
293 | }
294 |
295 |
296 | .hljs {
297 | display: block;
298 | background: white;
299 | padding: 0.5em;
300 | color: #333333;
301 | overflow-x: auto;
302 | }
303 |
304 | .hljs-comment,
305 | .hljs-meta {
306 | color: #969896;
307 | }
308 |
309 | .hljs-string,
310 | .hljs-variable,
311 | .hljs-template-variable,
312 | .hljs-strong,
313 | .hljs-emphasis,
314 | .hljs-quote {
315 | color: #df5000;
316 | }
317 |
318 | .hljs-keyword,
319 | .hljs-selector-tag,
320 | .hljs-type {
321 | color: #a71d5d;
322 | }
323 |
324 | .hljs-literal,
325 | .hljs-symbol,
326 | .hljs-bullet,
327 | .hljs-attribute {
328 | color: #0086b3;
329 | }
330 |
331 | .hljs-section,
332 | .hljs-name {
333 | color: #63a35c;
334 | }
335 |
336 | .hljs-tag {
337 | color: #333333;
338 | }
339 |
340 | .hljs-title,
341 | .hljs-attr,
342 | .hljs-selector-id,
343 | .hljs-selector-class,
344 | .hljs-selector-attr,
345 | .hljs-selector-pseudo {
346 | color: #795da3;
347 | }
348 |
349 | .hljs-addition {
350 | color: #55a532;
351 | background-color: #eaffea;
352 | }
353 |
354 | .hljs-deletion {
355 | color: #bd2c00;
356 | background-color: #ffecec;
357 | }
358 |
359 | .hljs-link {
360 | text-decoration: underline;
361 | }
362 |
363 | pre{
364 | background: #fff;
365 | }
366 |
367 |
368 |
369 |
370 |
371 |
--------------------------------------------------------------------------------
/vue-chat-client/src/components/sider-left.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 | {{user.name}}
8 |
9 |
10 |
11 | -
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | -
26 |
27 | 添加{{activeList=='friends'?'好友':'群组'}}
28 |
29 |
30 | -
32 |
33 |
34 |
35 |
36 | {{item.name}}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
个人资料
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | 修改资料
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
66 |
67 |
68 |
没有找到想要的?
69 |
71 | 创建该群组
72 |
73 |
74 |
75 | -
76 |
77 | {{item.name}}
78 |
79 | 加入
81 | 存在
82 |
83 |
84 | 没有相关群组
85 |
86 |
87 |
88 |
89 |
91 |
92 |
93 | -
94 |
95 | {{item.name}}
96 | 添加
98 | 存在
99 |
100 |
101 | 没有相关用户
102 |
103 |
104 |
105 |
106 |
212 |
213 |
--------------------------------------------------------------------------------
/vue-chat-client/src/assets/font-icon/iconfont.js:
--------------------------------------------------------------------------------
1 | ;(function(window) {
2 |
3 | var svgSprite = ''
68 | var script = function() {
69 | var scripts = document.getElementsByTagName('script')
70 | return scripts[scripts.length - 1]
71 | }()
72 | var shouldInjectCss = script.getAttribute("data-injectcss")
73 |
74 | /**
75 | * document ready
76 | */
77 | var ready = function(fn) {
78 | if (document.addEventListener) {
79 | if (~["complete", "loaded", "interactive"].indexOf(document.readyState)) {
80 | setTimeout(fn, 0)
81 | } else {
82 | var loadFn = function() {
83 | document.removeEventListener("DOMContentLoaded", loadFn, false)
84 | fn()
85 | }
86 | document.addEventListener("DOMContentLoaded", loadFn, false)
87 | }
88 | } else if (document.attachEvent) {
89 | IEContentLoaded(window, fn)
90 | }
91 |
92 | function IEContentLoaded(w, fn) {
93 | var d = w.document,
94 | done = false,
95 | // only fire once
96 | init = function() {
97 | if (!done) {
98 | done = true
99 | fn()
100 | }
101 | }
102 | // polling for no errors
103 | var polling = function() {
104 | try {
105 | // throws errors until after ondocumentready
106 | d.documentElement.doScroll('left')
107 | } catch (e) {
108 | setTimeout(polling, 50)
109 | return
110 | }
111 | // no errors, fire
112 |
113 | init()
114 | };
115 |
116 | polling()
117 | // trying to always fire before onload
118 | d.onreadystatechange = function() {
119 | if (d.readyState == 'complete') {
120 | d.onreadystatechange = null
121 | init()
122 | }
123 | }
124 | }
125 | }
126 |
127 | /**
128 | * Insert el before target
129 | *
130 | * @param {Element} el
131 | * @param {Element} target
132 | */
133 |
134 | var before = function(el, target) {
135 | target.parentNode.insertBefore(el, target)
136 | }
137 |
138 | /**
139 | * Prepend el to target
140 | *
141 | * @param {Element} el
142 | * @param {Element} target
143 | */
144 |
145 | var prepend = function(el, target) {
146 | if (target.firstChild) {
147 | before(el, target.firstChild)
148 | } else {
149 | target.appendChild(el)
150 | }
151 | }
152 |
153 | function appendSvg() {
154 | var div, svg
155 |
156 | div = document.createElement('div')
157 | div.innerHTML = svgSprite
158 | svgSprite = null
159 | svg = div.getElementsByTagName('svg')[0]
160 | if (svg) {
161 | svg.setAttribute('aria-hidden', 'true')
162 | svg.style.position = 'absolute'
163 | svg.style.width = 0
164 | svg.style.height = 0
165 | svg.style.overflow = 'hidden'
166 | prepend(svg, document.body)
167 | }
168 | }
169 |
170 | if (shouldInjectCss && !window.__iconfont__svg__cssinject__) {
171 | window.__iconfont__svg__cssinject__ = true
172 | try {
173 | document.write("");
174 | } catch (e) {
175 | console && console.log(e)
176 | }
177 | }
178 |
179 | ready(appendSvg)
180 |
181 |
182 | })(window)
--------------------------------------------------------------------------------
/vue-chat-client/src/components/sider-right.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{currentOne.name}}
7 |
8 |
9 |
10 | {{activeList=='friends'?'删除好友':'退出群组'}}
11 |
12 |
18 |
19 |
41 |
42 |
65 |
66 |
67 |
68 |
185 |
186 |
--------------------------------------------------------------------------------