├── .babelrc
├── .editorconfig
├── .gitignore
├── .postcssrc.js
├── README.md
├── build
├── build.js
├── check-versions.js
├── dev-client.js
├── dev-server.js
├── utils.js
├── vue-loader.conf.js
├── webpack.base.conf.js
├── webpack.dev.conf.js
└── webpack.prod.conf.js
├── config
├── dev.env.js
├── index.js
└── prod.env.js
├── index.html
├── package.json
├── screenshots
├── Screenshot_1.png
├── Screenshot_2.png
├── Screenshot_3.png
├── Screenshot_4.png
├── Screenshot_5.png
├── Screenshot_6.png
├── Screenshot_7.png
├── Screenshot_8.png
├── android-armv7-debug.apk
├── dsx-alipay.png
├── dsx-wx.png
├── pre1.png
├── pre2.png
└── qrcode.jpg
├── src
├── App.vue
├── assets
│ ├── common.scss
│ ├── contact
│ │ ├── w-contact-groupchat.svg
│ │ ├── w-contact-gzh.svg
│ │ ├── w-contact-newfriend.svg
│ │ └── w-contact-sign.svg
│ ├── explore
│ │ ├── w-explore-app.svg
│ │ ├── w-explore-bottle.svg
│ │ ├── w-explore-friend.svg
│ │ ├── w-explore-game.svg
│ │ ├── w-explore-nearby.svg
│ │ ├── w-explore-sao.svg
│ │ ├── w-explore-shake.svg
│ │ └── w-explore-shop.svg
│ ├── github.png
│ ├── icon.png
│ ├── icon2.gif
│ ├── icon3.png
│ ├── mixin.scss
│ ├── profile
│ │ ├── head-portrait.jpg
│ │ ├── w-profile-album.svg
│ │ ├── w-profile-collection.svg
│ │ ├── w-profile-emoj.svg
│ │ ├── w-profile-qr.svg
│ │ ├── w-profile-setting.svg
│ │ ├── w-profile-vip.svg
│ │ └── w-profile-wallet.svg
│ └── weibo.png
├── components
│ ├── common
│ │ ├── nav-back.vue
│ │ ├── nav-bar.vue
│ │ ├── page.vue
│ │ ├── search.vue
│ │ ├── tab-bar.vue
│ │ ├── tab-cell.vue
│ │ └── tab-group.vue
│ ├── contact
│ │ ├── add-friend.vue
│ │ ├── contact.vue
│ │ ├── detail.vue
│ │ ├── online.vue
│ │ └── search-cell.vue
│ ├── explore
│ │ └── explore.vue
│ ├── icon
│ │ ├── contact.vue
│ │ ├── explore.vue
│ │ ├── profile.vue
│ │ └── wchat.vue
│ ├── message
│ │ ├── chat
│ │ │ ├── chat-bar.vue
│ │ │ ├── chat-dialog.vue
│ │ │ ├── chat.vue
│ │ │ └── icon
│ │ │ │ ├── emoji.vue
│ │ │ │ ├── more.vue
│ │ │ │ └── voice.vue
│ │ ├── message-list.vue
│ │ └── message.vue
│ ├── modal
│ │ ├── confirm.vue
│ │ └── modal.vue
│ ├── profile
│ │ ├── me.vue
│ │ ├── profile.vue
│ │ └── setting.vue
│ ├── splash.vue
│ ├── wchat.vue
│ └── wellcome
│ │ ├── login.vue
│ │ ├── register.vue
│ │ ├── w-button.vue
│ │ └── w-input.vue
├── main.js
├── plugins
│ ├── WRequest.js
│ ├── WSocket.js
│ └── modal.js
├── router
│ └── index.js
├── views
│ ├── AtMeComment
│ │ ├── index.js
│ │ └── src
│ │ │ └── AtMeComment.vue
│ ├── Comment
│ │ ├── index.js
│ │ └── src
│ │ │ └── Comment.vue
│ ├── Content
│ │ ├── index.js
│ │ └── src
│ │ │ ├── Content.vue
│ │ │ └── index.js
│ ├── Spinner
│ │ ├── index.js
│ │ └── src
│ │ │ └── Spinner.vue
│ └── UserInfo
│ │ ├── index.js
│ │ └── src
│ │ └── UserInfo.vue
├── vuex
│ ├── actions.js
│ ├── getters.js
│ ├── mutations.js
│ ├── state
│ │ ├── index.js
│ │ └── message.json
│ └── store.js
└── websocket
│ ├── add.js
│ ├── chat.js
│ ├── chatroom.js
│ └── index.js
└── static
└── .gitkeep
/.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 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .vscode/
3 | node_modules/
4 | dist/
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Dsx_wechat
2 |
3 | * 这是一个 Vue 仿微信客户端
4 | * Github项目地址:[https://github.com/GGwujun/Dsx_wechat](https://github.com/GGwujun/Dsx_wechat)
5 | * 一款模仿微信的 Web App,具有真实的聊天等功能,并采用前后端分离的方式来实现。前端基于 Vue 2.0 框架,[后端](https://github.com/GGwujun/chatserve)基于 Node.js + Express + MongoDB,聊天功能和添加好友功能通过 Websocket 实现。
6 | * 欢迎关注我的公众号:
7 | * 前端技术栈: vue 2.0、vue-cli、vuex、vue-router、webpack 2.x、pug、sass、babel等;
8 | * 后端技术栈:Node.js、Express、express-session、WebSocket(ws)、MongoDB、mongoose、ES6等。
9 |
10 |
11 | ### Intro
12 | * 学习vue有段时间了,但是公司并不使用vue,为了练习vue大大小小也做过几个个人项目,模仿过微信的pc版,由于sockit.io不能刷新,导致在pc端体验并不好,一刷新就要重新登陆才可以发消息给好友。
13 | * 在公司用的多的就是ionic,一个专注于移动端webapp的偏UI框架,里面也用到了angularjs框架。打包用的是cordova,后面就想到了用vue做一个微信客户端,用cordova打包。
14 | * 目前手机的硬件已经很好了,基本cordova打包的app体验还是很流畅的,配合vue的单页面应用基本可以无差原生app了。
15 | * 前端部分使用 vue-cli 构建、打包, 配合 vue全家桶(vue、vuex、vue-router)进行编码
16 | * 使用 axios 进行资源请求
17 | * 后台使用 Node.js的express架构开发,目前接口不多,不过会持续更新。
18 |
19 | ### Server
20 | * 使用 Nodejs + Express 开发
21 | * 实现 注册,登录,退出登录,查看好友,个人主页,添加好友,一对一聊天和群聊
22 | * Github项目地址:[https://github.com/GGwujun/chatserve](https://github.com/GGwujun/chatserve)
23 |
24 | ### Preview
25 | 
26 | 
27 |
28 | ### 下载apk
29 |
30 | [点击这里下载安装apk](./screenshots/android-armv7-debug.apk),目前只支持android(5.0以上)系统(由于项目仍在开发中,部分功能可能不是最新、或暂不可用)。
31 |
32 | 新用户必须通过注册账号进入,已注册用户可直接登录进入。目前占不支持离线消息,也不支持添加离线用户为好友(即时通信相关功能必须保证对方在线)
33 |
34 | ### 本地使用
35 |
36 | 假设你已安装 `Node.js`,那么直接克隆仓库到本地,安装完所有插件并启动服务器。
37 | 建议使用谷歌浏览器并在手机调试模式下查看(http://localhost:8808/)。
38 |
39 | ``` bash
40 | # clone
41 | git clone https://github.com/GGwujun/Dsx_wechat.git
42 |
43 | # 进入到目录 安装所有依赖包 国内建议使用cnpm
44 | cd Dsx_wechat
45 | npm install
46 |
47 | # 开启本地服务器 监听8808端口
48 | npm run dev
49 | ```
50 |
51 | ### Tips
52 | * 无法注册或者获取数据,因为我配置的后台接口是我的服务器,你可以自己下载后台代码,部署到自己服务器,不过一般情况是可以访问的。
53 | * 如果你要自己搭建服务器,你除了要安装node相关的依赖外,你还需要安装MongoDB数据库
54 |
55 |
56 | ### function
57 |
58 | 该项目已实现后端服务器的支持,具有真实的聊天功能。后端部分请转移[这里](https://github.com/GGwujun/chatserve)
59 |
60 | - 高仿微信客户端的界面设计风格,具有push、pop、modal、dismiss等转场动画;
61 | - 注册、登陆和注销功能,可记住登录状态,避免多次登录;
62 | - 聊天室功能,所有在线用户可进行群聊;
63 | - 添加好友,目前必须保证对方在线才能正确添加;
64 | - 用户私聊,目前必须保证对方在线方可正常聊天;
65 | - 目前只支持纯文本聊天。
66 |
67 | 更多功能请待续...
68 |
69 |
70 | 如果您觉得该项目不错, 欢迎**star**和分享!
71 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/build/check-versions.js:
--------------------------------------------------------------------------------
1 | var chalk = require('chalk')
2 | var semver = require('semver')
3 | var packageConfig = require('../package.json')
4 |
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 | name: 'npm',
17 | currentVersion: exec('npm --version'),
18 | versionRequirement: packageConfig.engines.npm
19 | }
20 | ]
21 |
22 | module.exports = function () {
23 | var warnings = []
24 | for (var i = 0; i < versionRequirements.length; i++) {
25 | var mod = versionRequirements[i]
26 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
27 | warnings.push(mod.name + ': ' +
28 | chalk.red(mod.currentVersion) + ' should be ' +
29 | chalk.green(mod.versionRequirement)
30 | )
31 | }
32 | }
33 |
34 | if (warnings.length) {
35 | console.log('')
36 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
37 | console.log()
38 | for (var i = 0; i < warnings.length; i++) {
39 | var warning = warnings[i]
40 | console.log(' ' + warning)
41 | }
42 | console.log()
43 | process.exit(1)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | 'babel-polyfill': 'babel-polyfill',
13 | app: './src/main.js'
14 | },
15 | output: {
16 | path: config.build.assetsRoot,
17 | filename: '[name].js',
18 | publicPath: process.env.NODE_ENV === 'production'
19 | ? config.build.assetsPublicPath
20 | : config.dev.assetsPublicPath
21 | },
22 | resolve: {
23 | extensions: ['.js', '.vue', '.json'],
24 | alias: {
25 | 'vue$': 'vue/dist/vue.esm.js',
26 | '@': resolve('src')
27 | }
28 | },
29 | module: {
30 | rules: [
31 | {
32 | test: /\.vue$/,
33 | loader: 'vue-loader',
34 | options: vueLoaderConfig
35 | },
36 | {
37 | test: /\.js$/,
38 | loader: 'babel-loader',
39 | include: [resolve('src'), resolve('test')]
40 | },
41 | {
42 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
43 | loader: 'url-loader',
44 | options: {
45 | limit: 10000,
46 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
47 | }
48 | },
49 | {
50 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
51 | loader: 'url-loader',
52 | options: {
53 | limit: 10000,
54 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
55 | }
56 | }
57 | ]
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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].js'),
25 | chunkFilename: utils.assetsPath('js/[id].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].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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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: false,
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: 8808,
27 | autoOpenBrowser: true,
28 | assetsSubDirectory: 'static',
29 | assetsPublicPath: '/',
30 | proxyTable: {},
31 | // CSS Sourcemaps off by default because relative paths are "buggy"
32 | // with this option, according to the CSS-Loader README
33 | // (https://github.com/webpack/css-loader#sourcemaps)
34 | // In our experience, they generally work as expected,
35 | // just be aware of this issue when enabling this option.
36 | cssSourceMap: false
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NODE_ENV: '"production"'
3 | }
4 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | wchat-vue
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wchat-vue",
3 | "version": "1.0.0",
4 | "description": "A Vue.js project",
5 | "author": "moohng ",
6 | "private": true,
7 | "scripts": {
8 | "dev": "node build/dev-server.js",
9 | "build": "node build/build.js"
10 | },
11 | "dependencies": {
12 | "babel-polyfill": "^6.23.0",
13 | "particles.js": "^2.0.0",
14 | "vue-material": "^0.7.1",
15 | "jquery": "^3.2.1",
16 | "js-base64": "^2.1.9",
17 | "socket.io-client": "^2.0.1",
18 | "vue": "^2.2.2",
19 | "vue-router": "^2.2.0",
20 | "vuex": "^2.2.1",
21 | "ws": "^3.0.0"
22 | },
23 | "devDependencies": {
24 | "autoprefixer": "^6.7.2",
25 | "babel-core": "^6.22.1",
26 | "babel-loader": "^6.2.10",
27 | "babel-plugin-transform-runtime": "^6.22.0",
28 | "babel-preset-env": "^1.2.1",
29 | "babel-preset-stage-2": "^6.22.0",
30 | "babel-register": "^6.22.0",
31 | "chalk": "^1.1.3",
32 | "connect-history-api-fallback": "^1.3.0",
33 | "copy-webpack-plugin": "^4.0.1",
34 | "css-loader": "^0.26.1",
35 | "eventsource-polyfill": "^0.9.6",
36 | "express": "^4.14.1",
37 | "extract-text-webpack-plugin": "^2.0.0",
38 | "file-loader": "^0.10.0",
39 | "friendly-errors-webpack-plugin": "^1.1.3",
40 | "function-bind": "^1.1.0",
41 | "html-webpack-plugin": "^2.28.0",
42 | "http-proxy-middleware": "^0.17.3",
43 | "node-sass": "^4.5.1",
44 | "opn": "^4.0.2",
45 | "optimize-css-assets-webpack-plugin": "^1.3.0",
46 | "ora": "^1.1.0",
47 | "pug": "^2.0.0-beta11",
48 | "pug-loader": "^2.3.0",
49 | "rimraf": "^2.6.0",
50 | "sass-loader": "^6.0.3",
51 | "semver": "^5.3.0",
52 | "url-loader": "^0.5.7",
53 | "vue-loader": "^11.1.4",
54 | "vue-style-loader": "^2.0.0",
55 | "vue-template-compiler": "^2.2.4",
56 | "webpack": "^2.2.1",
57 | "webpack-bundle-analyzer": "^2.2.1",
58 | "webpack-dev-middleware": "^1.10.0",
59 | "webpack-hot-middleware": "^2.16.1",
60 | "webpack-merge": "^2.6.1"
61 | },
62 | "engines": {
63 | "node": ">= 4.0.0",
64 | "npm": ">= 3.0.0"
65 | },
66 | "browserslist": [
67 | "> 1%",
68 | "last 2 versions",
69 | "not ie <= 8"
70 | ]
71 | }
--------------------------------------------------------------------------------
/screenshots/Screenshot_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/screenshots/Screenshot_1.png
--------------------------------------------------------------------------------
/screenshots/Screenshot_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/screenshots/Screenshot_2.png
--------------------------------------------------------------------------------
/screenshots/Screenshot_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/screenshots/Screenshot_3.png
--------------------------------------------------------------------------------
/screenshots/Screenshot_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/screenshots/Screenshot_4.png
--------------------------------------------------------------------------------
/screenshots/Screenshot_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/screenshots/Screenshot_5.png
--------------------------------------------------------------------------------
/screenshots/Screenshot_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/screenshots/Screenshot_6.png
--------------------------------------------------------------------------------
/screenshots/Screenshot_7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/screenshots/Screenshot_7.png
--------------------------------------------------------------------------------
/screenshots/Screenshot_8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/screenshots/Screenshot_8.png
--------------------------------------------------------------------------------
/screenshots/android-armv7-debug.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/screenshots/android-armv7-debug.apk
--------------------------------------------------------------------------------
/screenshots/dsx-alipay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/screenshots/dsx-alipay.png
--------------------------------------------------------------------------------
/screenshots/dsx-wx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/screenshots/dsx-wx.png
--------------------------------------------------------------------------------
/screenshots/pre1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/screenshots/pre1.png
--------------------------------------------------------------------------------
/screenshots/pre2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/screenshots/pre2.png
--------------------------------------------------------------------------------
/screenshots/qrcode.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/screenshots/qrcode.jpg
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 | #app
3 | //- 全屏切换
4 | transition(:name="transName", :mode="transMode")
5 | keep-alive
6 | router-view
7 |
8 |
9 |
99 |
100 |
165 |
--------------------------------------------------------------------------------
/src/assets/common.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | width: 100%;
4 | height: 100%;
5 | font-size: 16px;
6 | }
7 |
8 | // 统一样式
9 | html,
10 | body,
11 | ul,
12 | p,
13 | h1,
14 | h2,
15 | h3,
16 | h4,
17 | h5,
18 | h6 {
19 | margin: 0;
20 | padding: 0;
21 | }
22 |
23 | img {
24 | display: block;
25 | }
26 |
27 | li {
28 | list-style: none;
29 | }
30 |
31 | a {
32 | text-decoration: none;
33 | }
34 |
35 | .erji {
36 | position: relative;
37 | span.span {
38 | padding-left: 35px!important;
39 | }
40 | span.span:before {
41 | content: '';
42 | top: 50%;
43 | width: 1px;
44 | background-color: #343539;
45 | position: absolute;
46 | bottom: 0;
47 | left: 40px;
48 | margin-top: -13px;
49 | height: 26px;
50 | }
51 | }
52 |
53 |
54 | ul:not(.md-list) > li + li {
55 | margin-top: 0px;
56 | }
--------------------------------------------------------------------------------
/src/assets/contact/w-contact-groupchat.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/contact/w-contact-gzh.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/contact/w-contact-newfriend.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/contact/w-contact-sign.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/explore/w-explore-app.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/explore/w-explore-bottle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/explore/w-explore-friend.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/explore/w-explore-game.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/explore/w-explore-nearby.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/explore/w-explore-sao.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/explore/w-explore-shake.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/explore/w-explore-shop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/src/assets/github.png
--------------------------------------------------------------------------------
/src/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/src/assets/icon.png
--------------------------------------------------------------------------------
/src/assets/icon2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/src/assets/icon2.gif
--------------------------------------------------------------------------------
/src/assets/icon3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/src/assets/icon3.png
--------------------------------------------------------------------------------
/src/assets/mixin.scss:
--------------------------------------------------------------------------------
1 |
2 | @mixin abs($top: 0, $right: 0, $bottom: 0, $left: 0) {
3 | position: absolute;
4 | top: $top; right: $right; bottom: $bottom; left: $left;
5 | }
6 |
7 | @mixin fixed($top: 0, $right: 0, $bottom: 0, $left: 0) {
8 | position: fixed;
9 | top: $top; right: $right; bottom: $bottom; left: $left;
10 | }
11 |
12 | @mixin flex($z: center, $v: center, $dir: row) {
13 | display: flex;
14 | justify-content: $z;
15 | align-items: $v;
16 | flex-direction: $dir;
17 | }
18 |
19 | // 界面背景颜色
20 | $bgColor: #efefef;
21 | // 线条颜色
22 | $lineColor: #E7E7E7;
23 | // 渲染颜色 选中颜色
24 | $tintColor: #1aad19;
25 | // 灰色 未选中颜色
26 | $lightColor: #999;
27 |
--------------------------------------------------------------------------------
/src/assets/profile/head-portrait.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/src/assets/profile/head-portrait.jpg
--------------------------------------------------------------------------------
/src/assets/profile/w-profile-album.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/profile/w-profile-collection.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/profile/w-profile-emoj.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/profile/w-profile-qr.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/profile/w-profile-setting.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/profile/w-profile-vip.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/profile/w-profile-wallet.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/weibo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/src/assets/weibo.png
--------------------------------------------------------------------------------
/src/components/common/nav-back.vue:
--------------------------------------------------------------------------------
1 |
2 | a.nav-back
3 | i.icon-back
4 | span {{ title}}
5 |
6 |
7 |
12 |
13 |
28 |
--------------------------------------------------------------------------------
/src/components/common/nav-bar.vue:
--------------------------------------------------------------------------------
1 |
2 | header.nav-bar
3 | .left
4 | slot(name="left")
5 | .title
6 | slot(name="title")
7 | .right
8 | slot(name="right")
9 |
10 |
11 |
15 |
16 |
43 |
--------------------------------------------------------------------------------
/src/components/common/page.vue:
--------------------------------------------------------------------------------
1 |
2 | .page
3 | slot(name="nav-bar")
4 | section.main(:class="inset")
5 | slot(name="main")
6 | slot(name="tab-bar")
7 |
8 |
9 |
21 |
22 |
43 |
--------------------------------------------------------------------------------
/src/components/common/search.vue:
--------------------------------------------------------------------------------
1 |
2 | .search
3 | input(type="text", placeholder="搜索")
4 |
5 |
6 |
10 |
11 |
32 |
--------------------------------------------------------------------------------
/src/components/common/tab-bar.vue:
--------------------------------------------------------------------------------
1 |
2 | footer.tab-bar
3 | router-link.wrap(v-for="tab in tabbar", :to="tab.to")
4 | .icon
5 | component(:is="tab.icon")
6 | span {{ tab.title }}
7 |
8 |
9 |
47 |
48 |
88 |
--------------------------------------------------------------------------------
/src/components/common/tab-cell.vue:
--------------------------------------------------------------------------------
1 |
2 | li.tab-cell(:class="{large, contact}")
3 | header.header(v-if="img")
4 | img(:src="img")
5 | section.content(:class="{subtitle, detail, center}")
6 | span.title {{ title }}
7 | span.subtitle(v-if="subtitle || detail") {{ subtitle || detail }}
8 | footer.more(v-if="more || disclosure")
9 | .button(v-if="more")
10 | slot(name="button")
11 | i.disclosure(v-if="disclosure")
12 |
13 |
14 |
22 |
23 |
112 |
--------------------------------------------------------------------------------
/src/components/common/tab-group.vue:
--------------------------------------------------------------------------------
1 |
2 | ul.group
3 | slot
4 |
5 |
6 |
10 |
11 |
22 |
--------------------------------------------------------------------------------
/src/components/contact/add-friend.vue:
--------------------------------------------------------------------------------
1 |
2 | page.add(margin-top)
3 | nav-bar.erji(slot="nav-bar")
4 | span.span(slot="title") 添加朋友
5 | nav-back(slot="left", title="",
6 | @click.native="$router.replace({name: 'contact', query: {mode: 'pop'}})")
7 | .search(slot="right", v-if="username.length > 0",
8 | @click="search") 搜索
9 | template(slot="main")
10 | tab-group
11 | search-cell(v-model="username")
12 | .wrap
13 | span 我的微信号:gaowujun9
14 | img
15 | tab-group
16 | tab-cell(v-for="cell in cells",
17 | :img="cell.img", :title="cell.title", :subtitle="cell.subtitle",
18 | contact, disclosure)
19 |
20 |
21 |
85 |
86 |
105 |
--------------------------------------------------------------------------------
/src/components/contact/contact.vue:
--------------------------------------------------------------------------------
1 |
2 | .contact
3 | //- search
4 | tab-group
5 | tab-cell(:img="icons[0]", :title="title[0]")
6 | tab-cell(:img="icons[1]", :title="title[1]")
7 | tab-cell(:img="icons[2]", :title="title[2]")
8 | tab-cell.gzh(:img="icons[3]", :title="title[3]")
9 |
10 | 企业号
11 |
12 |
13 | tab-group
14 | tab-cell(:img="icons[0]", :title="'大师兄'")
15 | tab-cell(:img="icons[1]", :title="'企业号助手'")
16 | tab-cell(:img="icons[2]", :title="title[2]")
17 |
18 |
19 | 星标朋友
20 |
21 | tab-group.group
22 | tab-cell(v-for="title, index in titles",
23 | :img="icons[1]", :title="title", contact,
24 | @click.native.stop="tabSelect(index)")
25 |
26 |
27 | tab-group.group(v-for="group in friendList")
28 | tab-cell(v-for="friend in group",
29 | :img="icons[0]", :title="friend", contact,
30 | @click.native.stop="detail(friend)")
31 |
32 |
33 |
139 |
140 |
162 |
--------------------------------------------------------------------------------
/src/components/contact/detail.vue:
--------------------------------------------------------------------------------
1 |
2 | page.detail(margin-top)
3 | nav-bar.erji(slot="nav-bar")
4 | span.span(slot="title") 详细资料
5 | nav-back(slot="left", title="",
6 | @click.native="$router.replace({name: 'contact', query: {mode: 'pop'}})")
7 | template(slot="main", v-if="user.username")
8 | tab-group
9 | tab-cell(img="", :title="user.name", :subtitle="'微信号:' + user.username", large)
10 | tab-group
11 | tab-cell(title="设置备注和标签", disclosure)
12 | tab-group
13 | tab-cell(title="地区", detail="广东 深圳")
14 | tab-cell(title="个人相册", detail="对不起,我还没准备好", large, disclosure)
15 | tab-cell(title="更多", disclosure)
16 | //- 按钮
17 | .wrap
18 | w-button(:name="user.friendly ? '发消息' : '加好友'", type="button"
19 | @click="click")
20 | template(slot="main", v-else)
21 | .view
22 | span 用户不存在
23 |
24 |
25 |
88 |
89 |
108 |
--------------------------------------------------------------------------------
/src/components/contact/online.vue:
--------------------------------------------------------------------------------
1 |
2 | page.online(margin-top)
3 | nav-bar.erji(slot="nav-bar")
4 | span.span(slot="title") 在线用户
5 | nav-back(slot="left", title="",
6 | @click.native="$router.replace({name: 'contact', query: {mode: 'pop'}})")
7 | template(slot="main", v-if="onlineList.length > 0")
8 | tab-group
9 | tab-cell(v-for="username in onlineList",
10 | img="", :title="username", contact,
11 | @click.native.stop="select(username)")
12 |
13 |
14 |
59 |
60 |
63 |
--------------------------------------------------------------------------------
/src/components/contact/search-cell.vue:
--------------------------------------------------------------------------------
1 |
2 | li.search-cell(@click="focus")
3 | header.header
4 | img
5 | section.content
6 | input(ref="input", type="text", placeholder="微信号/手机号",
7 | @input="input($event.target)")
8 |
9 |
10 |
40 |
41 |
75 |
--------------------------------------------------------------------------------
/src/components/explore/explore.vue:
--------------------------------------------------------------------------------
1 |
2 | .explore
3 | tab-group
4 | tab-cell(:img="icons[0]", :title="titles[0]")
5 | tab-group
6 | tab-cell(:img="icons[1]", :title="titles[1]")
7 | tab-cell(:img="icons[2]", :title="titles[2]")
8 | tab-group
9 | tab-cell(:img="icons[3]", :title="titles[3]")
10 | tab-cell(:img="icons[4]", :title="titles[4]")
11 | tab-group
12 | tab-cell(:img="icons[5]", :title="titles[5]")
13 | tab-cell(:img="icons[6]", :title="titles[6]")
14 | tab-group
15 | tab-cell(:img="icons[7]", :title="titles[7]")
16 |
17 |
18 |
47 |
48 |
61 |
--------------------------------------------------------------------------------
/src/components/icon/contact.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/src/components/icon/explore.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/components/icon/profile.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/components/icon/wchat.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/components/message/chat/chat-bar.vue:
--------------------------------------------------------------------------------
1 |
2 | footer.chat-bar
3 | .content
4 | .wrap
5 | .icon
6 | icon-voice
7 | .wrap.text-area
8 | p.edit-box(contenteditable, ref="input",
9 | @keydown.enter.prevent="send($event.target)",
10 | @input.prevent="input($event.target)")
11 | .wrap
12 | .icon
13 | icon-emoji
14 | .wrap
15 | transition(name="scale", mode="out-in")
16 | a.icon.send-btn(v-if="showSend",
17 | @click.stop.prevent="send($refs['input'])") 发送
18 | .icon(v-else)
19 | icon-more
20 |
21 |
22 |
23 |
24 |
74 |
75 |
166 |
--------------------------------------------------------------------------------
/src/components/message/chat/chat-dialog.vue:
--------------------------------------------------------------------------------
1 |
2 | li.chat-dialog
3 | .wrap(v-if="date")
4 | span.date {{ message.time }}
5 | .wrap.content(:class="{reverse}")
6 | .icon
7 | img
8 | .context
9 | p {{ message.content.text }}
10 |
11 |
12 |
27 |
28 |
120 |
--------------------------------------------------------------------------------
/src/components/message/chat/chat.vue:
--------------------------------------------------------------------------------
1 |
2 | page.chat(margin-bottom)
3 | nav-bar.erji(slot="nav-bar")
4 | span.title.span(slot="title") {{ title }}
5 | nav-back(slot="left", title="",
6 | @click.native="$router.replace({name: 'message', query: {mode: 'pop'}})")
7 | span(slot="right") 占位
8 | ul(slot="main", v-scroll="messages")
9 | chat-dialog(v-for="(message, index) in messages",
10 | :date="date(message, index)", :message="message")
11 | chat-bar(slot="tab-bar", :to="title === '聊天室' ? 'all' : title")
12 |
13 |
14 |
85 |
86 |
--------------------------------------------------------------------------------
/src/components/message/chat/icon/emoji.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/components/message/chat/icon/more.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/components/message/chat/icon/voice.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/components/message/message-list.vue:
--------------------------------------------------------------------------------
1 |
2 | li.message-list
3 | header.header
4 | img(:src="icons[0]")
5 | section.content
6 | .top
7 | span.title {{ title }}
8 | span.time {{ time }}
9 | .bottom
10 | p.context {{ text }}
11 | i.icon(v-if="disturb")
12 |
13 |
14 |
50 |
51 |
140 |
--------------------------------------------------------------------------------
/src/components/message/message.vue:
--------------------------------------------------------------------------------
1 |
2 | .message
3 | //- search
4 | tab-group.group
5 | //- 会话列表
6 | message-list(v-for="session in sessionList", :session="session",
7 | @click.native.stop="push(session.title)")
8 |
9 |
10 |
41 |
42 |
52 |
--------------------------------------------------------------------------------
/src/components/modal/confirm.vue:
--------------------------------------------------------------------------------
1 |
2 | .confirm
3 | .wrap
4 | .content
5 | h1 {{ title || '提示' }}
6 | p.text {{ text }}
7 | ul.btn
8 | li(@click="$emit('confirm', false)") {{ cancel || '取消' }}
9 | li(@click="$emit('confirm', true)") {{ ok || '确定' }}
10 |
11 |
12 |
13 |
19 |
20 |
64 |
--------------------------------------------------------------------------------
/src/components/modal/modal.vue:
--------------------------------------------------------------------------------
1 |
2 | .modal
3 | .wrap
4 | i.icon(:class="[type]")
5 | p.tip {{ text }}
6 |
7 |
8 |
13 |
14 |
86 |
--------------------------------------------------------------------------------
/src/components/profile/me.vue:
--------------------------------------------------------------------------------
1 |
2 | page.me(margin-top)
3 | nav-bar.erji(slot="nav-bar")
4 | nav-back(slot="left", title="",
5 | @click.native="$router.replace({name: 'profile', query: {mode: 'pop'}})")
6 | span.span(slot="title") 个人信息
7 | template(slot="main")
8 | tab-group
9 | tab-cell(title="头像", more, disclosure, large)
10 | tab-cell(title="名字", detail="大师兄", disclosure)
11 | tab-cell(title="微信号", detail="gaowujun9", disclosure)
12 | tab-cell(title="我的二维码", more, disclosure)
13 | tab-cell(title="我的地址", disclosure)
14 | tab-group
15 | tab-cell(title="性别", detail="男", disclosure)
16 | tab-cell(title="地区", detail="湖南 长沙", disclosure)
17 | tab-cell(title="个性签名", detai="我没有个性签名", disclosure)
18 |
19 |
20 |
39 |
40 |
43 |
--------------------------------------------------------------------------------
/src/components/profile/profile.vue:
--------------------------------------------------------------------------------
1 |
2 | .profile
3 | tab-group
4 | tab-cell.head(:title="user.name", :subtitle="'微信号: ' + user.username",
5 | :img="icons[0]", more, large,
6 | @click.native="push('me')")
7 | img(slot="button", :src="icons[1]")
8 | tab-group
9 | tab-cell(:img="icons[4]", :title="titles[2]")
10 | tab-group
11 | tab-cell(:img="icons[3]", :title="titles[1]")
12 | tab-cell(:img="icons[2]", :title="titles[0]")
13 | tab-cell(:img="icons[5]", :title="titles[3]")
14 | tab-cell(:img="icons[6]", :title="titles[4]")
15 | tab-group
16 | tab-cell(:img="icons[7]", :title="titles[5]",
17 | @click.native="push('setting')")
18 |
19 |
20 |
75 |
76 |
95 |
--------------------------------------------------------------------------------
/src/components/profile/setting.vue:
--------------------------------------------------------------------------------
1 |
2 | page.setting(margin-top)
3 | nav-bar.erji(slot="nav-bar")
4 | span.span(slot="title") 设置
5 | nav-back(slot="left", title="",
6 | @click.native="$router.replace({name: 'profile', query: {mode: 'pop'}})")
7 | template(slot="main")
8 | tab-group
9 | tab-cell(title="账号与安全", disclosure)
10 | tab-group
11 | tab-cell(title="新消息通知", disclosure)
12 | tab-cell(title="隐私", disclosure)
13 | tab-cell(title="通用", disclosure)
14 | tab-group
15 | tab-cell(title="帮助与反馈", disclosure)
16 | tab-cell(title="关于微信", disclosure)
17 | tab-group
18 | tab-cell(title="退出登录", center,
19 | @click.native="logout")
20 |
21 |
22 |
65 |
66 |
69 |
--------------------------------------------------------------------------------
/src/components/splash.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Dsx-Wechat
6 |
7 |
你总是在追赶前面的人
8 |
总是抱怨自己为什么不能再努力一点
9 |
累了你可以停下来
10 |
看看原来的自己
11 |
其实你已经很了不起了
12 |
学习是没有终点的赛跑
13 |
天道终会酬勤
14 |
感谢帮助过我的人
15 |
16 |
19 |
20 |
21 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
218 |
219 |
--------------------------------------------------------------------------------
/src/components/wchat.vue:
--------------------------------------------------------------------------------
1 |
2 | page.wchat(margin-bottom)
3 | nav-bar(slot="nav-bar")
4 | span(slot="title") {{ titleList[pageIndex] }}
5 | .add(slot="right", @click="add", v-if="pageIndex < 2") +
6 | //- tab bar 切换在这里 (嵌套路由)
7 | template(slot="main")
8 | transition(name="fade", mode="out-in")
9 | router-view
10 | tab-bar(slot="tab-bar")
11 |
12 |
13 |
52 |
53 |
75 |
--------------------------------------------------------------------------------
/src/components/wellcome/login.vue:
--------------------------------------------------------------------------------
1 |
2 | .login
3 | router-link.register(:to="{name: 'register', query: {mode: 'turn'}}", replace) 注册
4 | h3 使用账号和密码登录
5 | form
6 | .wrap
7 | w-input(name="账号", placeholder="请填写用户名", type="username", v-model="username")
8 | w-input(name="密码", placeholder="请填写密码", type="password", v-model="password")
9 | .wrap
10 | //- 阻止表单默认行为
11 | w-button(name="登录", :disabled="disabled", type="submit",
12 | @click.native.prevent="login")
13 | .wrap
14 | a.tip 登录遇到问题?
15 |
16 |
17 |
79 |
80 |
112 |
--------------------------------------------------------------------------------
/src/components/wellcome/register.vue:
--------------------------------------------------------------------------------
1 |
2 | .register
3 | router-link.login(:to="{name: 'login', query: {mode: 'turn'}}", replace) 登录
4 | h3 注册新用户
5 | form
6 | .wrap
7 | w-input(name="账号", placeholder="请输入6到16位用户名", type="username", v-model="username")
8 | w-input(name="密码", placeholder="请输入6到16位密码", type="password", v-model="password")
9 | w-input(name="确认密码", placeholder="请再次输入密码",
10 | type="password", v-model="rePassword",
11 | :class="{error}")
12 | .wrap
13 | w-button(name="注册", :disabled="disabled", type="submit",
14 | @click.native.prevent="register")
15 |
16 |
17 |
95 |
96 |
121 |
--------------------------------------------------------------------------------
/src/components/wellcome/w-button.vue:
--------------------------------------------------------------------------------
1 |
2 | input.w-button(:type="type", :value="name", :disabled="disabled"
3 | @click="$emit('click')")
4 |
5 |
6 |
11 |
12 |
34 |
--------------------------------------------------------------------------------
/src/components/wellcome/w-input.vue:
--------------------------------------------------------------------------------
1 |
2 | .input(@click.stop="focus", :class="{error}")
3 | label {{ name }}
4 | input(v-type="type", :placeholder="placeholder",
5 | ref="input", @input="input($event.target)")
6 |
7 |
8 |
45 |
46 |
87 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App'
3 | import router from './router'
4 | import store from './vuex/store'
5 | // 全局样式
6 | import './assets/common.scss'
7 | import Modal from './plugins/modal'
8 | Vue.use(Modal)
9 |
10 | import WRequest from './plugins/WRequest'
11 | Vue.use(WRequest)
12 |
13 |
14 | //组件
15 | import PixelSpinner from './views/Spinner/index'
16 | Vue.use(PixelSpinner)
17 |
18 | Vue.config.productionTip = false
19 |
20 | /* eslint-disable no-new */
21 | new Vue({
22 | el: '#app',
23 | router,
24 | store,
25 | template: '',
26 | components: { App }
27 | })
28 |
--------------------------------------------------------------------------------
/src/plugins/WRequest.js:
--------------------------------------------------------------------------------
1 | import { ajax } from 'jquery'
2 | import ws from '../websocket'
3 |
4 | const request = function(Vue) {
5 |
6 | const host = '119.23.245.101:4000'
7 |
8 | // 登录
9 | Vue.prototype.$login = request.login = function (data, cb) {
10 |
11 | let options = {}
12 | if (data) {
13 | options = {
14 | method: 'POST',
15 | data
16 | }
17 | }
18 | else {
19 | options = {
20 | method: 'GET'
21 | }
22 | }
23 |
24 | const url = 'http://' + host + '/user/login'
25 | ajax(url, {
26 | ...options,
27 | xhrFields: {
28 | withCredentials: true
29 | },
30 | success (res) {
31 | // 判断是否登录成功
32 | if (res.code === 10000) {
33 | cb(null)
34 | }
35 | else {
36 | cb(res.status)
37 | }
38 |
39 | },
40 | error (err) {
41 | cb('请求错误')
42 | }
43 | })
44 | }
45 | // 连接socket
46 | Vue.prototype.$connect = request.connect = function (cb) {
47 |
48 | const url = 'ws://' + host + '/ws'
49 | ws.init(url, cb)
50 | }
51 | Vue.prototype.$disconnect = request.disconnect = function () {
52 |
53 | ws.close()
54 | }
55 | // 注册
56 | Vue.prototype.$register = request.register = function (data, cb) {
57 | const url = 'http://' + host + '/user/register'
58 | ajax(url, {
59 | method: 'POST',
60 | data,
61 | xhrFields: {
62 | withCredentials: true
63 | },
64 | success (res) {
65 | // 判断是否登录成功
66 | if (res.code === 10000) {
67 | cb(null)
68 | }
69 | else {
70 | cb(res.status)
71 | }
72 | },
73 | error (err) {
74 | cb('请求错误')
75 | }
76 | })
77 | }
78 | // 注销
79 | Vue.prototype.$logout = request.logout = function (cb) {
80 | const url = 'http://' + host + '/user/logout'
81 | ajax(url, {
82 | method: 'GET',
83 | xhrFields: {
84 | withCredentials: true
85 | },
86 | success (res) {
87 | // 判断是否成功
88 | if (res.code === 10000) {
89 | cb(null)
90 | }
91 | else {
92 | cb(res.status)
93 | }
94 | },
95 | error (err) {
96 | cb('请求错误')
97 | }
98 | })
99 | }
100 |
101 | // 获取在线用户
102 | Vue.prototype.$getOnline = request.getOnline = function (cb) {
103 |
104 | const url = 'http://' + host + '/user'
105 | ajax(url, {
106 | method: 'GET',
107 | xhrFields: {
108 | withCredentials: true
109 | },
110 | success (res) {
111 | // 判断是否成功
112 | if (res.code === 10000) {
113 | cb(null, res.users)
114 | }
115 | else {
116 | cb(res.status)
117 | }
118 | },
119 | error (err) {
120 | cb('请求错误')
121 | }
122 | })
123 | }
124 |
125 | // 获取好友列表
126 | Vue.prototype.$getFriends = request.getFriends = function (cb) {
127 | const url = 'http://' + host + '/friend'
128 | ajax(url, {
129 | method: 'GET',
130 | xhrFields: {
131 | withCredentials: true
132 | },
133 | success (res) {
134 | // 判断是否成功
135 | if (res.code === 10000) {
136 | cb(null, res.friends)
137 | }
138 | else {
139 | cb(res.status)
140 | }
141 | },
142 | error (err) {
143 | cb('请求错误')
144 | }
145 | })
146 | }
147 |
148 | /**
149 | * 获取用户信息
150 | * @param {String} username 要搜索的用户,为空时获取自己的用户信息
151 | * @param {Function} cb 获取结果回调
152 | */
153 | Vue.prototype.$search = request.search = function (username, cb) {
154 |
155 | let url
156 | if (typeof username === 'string') {
157 | url = 'http://' + host + '/user?username=' + username
158 | }
159 | else {
160 | if (typeof username === 'function') {
161 | cb = username
162 | }
163 | url = 'http://' + host + '/user/self'
164 | }
165 |
166 |
167 | ajax(url, {
168 | method: 'GET',
169 | xhrFields: {
170 | withCredentials: true
171 | },
172 | success (res) {
173 | // 判断是否成功
174 | if (res.code === 10000) {
175 | cb(null, res.user)
176 | }
177 | else {
178 | cb(res.status)
179 | }
180 | },
181 | error (err) {
182 | cb('请求错误')
183 | }
184 | })
185 | }
186 |
187 | // 添加好友
188 | Vue.prototype.$addFriend = request.addFriend = function (username, cb) {
189 | const url = 'http://' + host + '/friend/add?username=' + username
190 | ajax(url, {
191 | method: 'GET',
192 | xhrFields: {
193 | withCredentials: true
194 | },
195 | success (res) {
196 | // 判断是否成功
197 | if (res.code === 10000) {
198 | cb(null, res.user)
199 | }
200 | else {
201 | cb(res.status)
202 | }
203 | },
204 | error (err) {
205 | cb('请求错误')
206 | }
207 | })
208 | }
209 |
210 | // 同意添加好友请求
211 | Vue.prototype.$acceptFriend = request.acceptFriend = function (username, cb) {
212 |
213 | const url = 'http://' + host + '/friend/accept?username=' + username
214 | ajax(url, {
215 | method: 'GET',
216 | xhrFields: {
217 | withCredentials: true
218 | },
219 | success (res) {
220 | // 判断是否成功
221 | if (res.code === 10000) {
222 | cb(null)
223 | }
224 | else {
225 | cb(res.status)
226 | }
227 | },
228 | error (err) {
229 | cb('请求错误')
230 | }
231 | })
232 | }
233 | }
234 |
235 | export default request
236 |
--------------------------------------------------------------------------------
/src/plugins/WSocket.js:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'events'
2 |
3 | class WSocket extends EventEmitter {
4 |
5 | // 初始化 连接
6 | init (url, cb) {
7 | this.cb = cb
8 | this.url = url
9 |
10 | this.socket = new WebSocket(this.url)
11 | this.socket.onopen = this.onopen.bind(this)
12 | this.socket.onmessage = this.onmessage.bind(this)
13 | this.socket.onerror = this.onerror.bind(this)
14 | this.socket.onclose = this.onclose.bind(this)
15 | }
16 |
17 | send (data) {
18 | if (this.socket.readyState != WebSocket.OPEN) {
19 | this.init(this.url);
20 | return false
21 | }
22 |
23 | this.socket.send(data)
24 | return true
25 | }
26 |
27 | close (code, reason) {
28 | this.socket.close(code, reason)
29 | }
30 |
31 | // 默认方法
32 | onopen (e) {
33 | this.emit('open', '已连接')
34 | this.cb && this.cb()
35 | }
36 | onmessage (e) {
37 |
38 | this.emit('message', e.data)
39 | }
40 | onerror (e) {
41 |
42 | this.emit('error', '连接错误')
43 | this.cb && this.cb('连接错误')
44 | }
45 | onclose (e) {
46 |
47 | this.emit('close', '连接已断开')
48 | }
49 | }
50 |
51 | export default WSocket
52 |
--------------------------------------------------------------------------------
/src/plugins/modal.js:
--------------------------------------------------------------------------------
1 | import UIModal from '@/components/modal/modal'
2 | import UIConfirm from '@/components/modal/confirm'
3 |
4 | // Vue 插件
5 | const modal = function (Vue) {
6 | const create = function (type, text) {
7 | const Loading = Vue.extend({
8 | template: '',
9 | data() {
10 | return {
11 | text,
12 | type
13 | }
14 | },
15 | components: { UIModal }
16 | })
17 | modal.el = new Loading().$mount().$el
18 | document.body.appendChild(modal.el)
19 | }
20 |
21 | // 等待弹框
22 | Vue.prototype.$loading = modal.loading = function (text = 'you are right!') {
23 | create('loading', text)
24 | }
25 | // 完成提示
26 | Vue.prototype.$completed = modal.completed = function (text, delay) {
27 | create('completed', text)
28 | // 默认 3s 后自动关闭
29 | setTimeout(() => modal.close(), delay || 3000)
30 | }
31 | // 错误提示
32 | Vue.prototype.$error = modal.error = function (text, delay) {
33 | create('error', text)
34 | // 默认 3s 后自动关闭
35 | setTimeout(() => modal.close(), delay || 3000)
36 | }
37 |
38 | // 关闭当前弹框
39 | Vue.prototype.$close = modal.close = function () {
40 | modal.el.parentNode.removeChild(modal.el)
41 | }
42 |
43 | // 确认框
44 | Vue.prototype.$confirm = modal.confirm = function (text, cb) {
45 | let title, ok, cancel
46 | if (typeof text === 'object') {
47 | title = text.title
48 | ok = text.ok
49 | cancel = text.cancel
50 | text = text.text
51 | }
52 |
53 | const Confirm = Vue.extend({
54 | template: '',
57 | data() {
58 | return {
59 | text,
60 | title,
61 | ok,
62 | cancel
63 | }
64 | },
65 | methods: {
66 | confirm(value) {
67 | // 关闭框口
68 | modal.close()
69 | // 执行回调
70 | cb(value)
71 | }
72 | },
73 | components: { UIConfirm }
74 | })
75 | modal.el = new Confirm().$mount().$el
76 | document.body.appendChild(modal.el)
77 | }
78 | }
79 |
80 | export default modal
81 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | // 主界面
4 | import Wchat from '@/components/wchat'
5 | import Explore from '@/components/explore/explore'
6 | import Profile from '@/components/profile/profile'
7 | import Contact from '@/components/contact/contact'
8 | import Message from '@/components/message/message'
9 | // Profile
10 | import Me from '@/components/profile/me'
11 | import Setting from '@/components/profile/setting'
12 | // Chat
13 | import Chat from '@/components/message/chat/chat'
14 | // Contact
15 | import AddFriend from '@/components/contact/add-friend'
16 | import Online from '@/components/contact/online'
17 | import Detail from '@/components/contact/detail'
18 |
19 | // Login
20 | import Login from '@/components/wellcome/login'
21 | // Register
22 | import Register from '@/components/wellcome/register'
23 |
24 | // splash
25 | import Splash from '@/components/splash'
26 |
27 |
28 | Vue.use(Router)
29 |
30 | export default new Router({
31 | routes: [
32 | {
33 | path: '/',
34 | redirect: { name: 'spalsh' }
35 | },
36 | {
37 | path: '/spalsh',
38 | name: 'spalsh',
39 | component: Splash
40 | },
41 | // 登录
42 | {
43 | path: '/wellcome/login',
44 | name: 'login',
45 | component: Login
46 | },
47 | // 注册
48 | {
49 | path: '/wellcome/register',
50 | name: 'register',
51 | component: Register
52 | },
53 | // 主界面
54 | {
55 | path: '/wchat',
56 | name: 'wchat',
57 | component: Wchat,
58 | children: [
59 | {
60 | path: 'message',
61 | name: 'message',
62 | component: Message
63 | },
64 | {
65 | path: 'contact',
66 | name: 'contact',
67 | component: Contact
68 | },
69 | {
70 | path: 'explore',
71 | name: 'explore',
72 | component: Explore
73 | },
74 | {
75 | path: 'profile',
76 | name: 'profile',
77 | component: Profile
78 | }
79 | ]
80 | },
81 | // Profile
82 | {
83 | path: '/wchat/profile/me',
84 | name: 'me',
85 | component: Me
86 | },
87 | {
88 | path: '/wchat/profile/setting',
89 | name: 'setting',
90 | component: Setting
91 | },
92 | // Message
93 | {
94 | path: '/wchat/message/chat',
95 | name: 'chat',
96 | component: Chat
97 | },
98 | // Contact
99 | {
100 | path: '/wchat/contact/add',
101 | name: 'add',
102 | component: AddFriend
103 | },
104 | // Online
105 | {
106 | path: '/wchat/contact/online',
107 | name: 'online',
108 | component: Online
109 | },
110 | // Detail
111 | {
112 | path: '/wchat/contact/detail',
113 | name: 'detail',
114 | component: Detail
115 | }
116 | ]
117 | })
118 |
--------------------------------------------------------------------------------
/src/views/AtMeComment/index.js:
--------------------------------------------------------------------------------
1 | import PixelAtMeComment from './src/AtMeComment'
2 |
3 | PixelAtMeComment.install = function (Vue) {
4 | Vue.component(PixelAtMeComment.name, PixelAtMeComment)
5 | }
6 |
7 | export default PixelAtMeComment
--------------------------------------------------------------------------------
/src/views/AtMeComment/src/AtMeComment.vue:
--------------------------------------------------------------------------------
1 |
2 |
28 |
29 |
30 |
64 |
65 |
169 |
--------------------------------------------------------------------------------
/src/views/Comment/index.js:
--------------------------------------------------------------------------------
1 | import PixelContentComments from './src/Comment'
2 |
3 | PixelContentComments.install = function (Vue) {
4 | Vue.component(PixelContentComments.name, PixelContentComments)
5 | }
6 |
7 | export default PixelContentComments
--------------------------------------------------------------------------------
/src/views/Comment/src/Comment.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
41 |
42 |
104 |
--------------------------------------------------------------------------------
/src/views/Content/index.js:
--------------------------------------------------------------------------------
1 | import PixelContent from './src/Content'
2 |
3 | PixelContent.install = function (Vue) {
4 | Vue.component(PixelContent.name, PixelContent)
5 | }
6 |
7 | export default PixelContent
--------------------------------------------------------------------------------
/src/views/Content/src/Content.vue:
--------------------------------------------------------------------------------
1 |
2 |
48 |
49 |
50 |
118 |
119 |
--------------------------------------------------------------------------------
/src/views/Content/src/index.js:
--------------------------------------------------------------------------------
1 | import PixelContent from './src/Content'
2 |
3 | PixelContent.install = function (Vue) {
4 | Vue.component(PixelContent.name, PixelContent)
5 | }
6 |
7 | export default PixelContent
--------------------------------------------------------------------------------
/src/views/Spinner/index.js:
--------------------------------------------------------------------------------
1 | import PixelSpinner from './src/Spinner'
2 |
3 | PixelSpinner.install = function (Vue) {
4 | Vue.component('pixel-spinner', PixelSpinner)
5 | }
6 |
7 | export default PixelSpinner
--------------------------------------------------------------------------------
/src/views/Spinner/src/Spinner.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
61 |
62 |
--------------------------------------------------------------------------------
/src/views/UserInfo/index.js:
--------------------------------------------------------------------------------
1 | import PixelUserInfo from './src/UserInfo'
2 |
3 | PixelUserInfo.install = function (Vue) {
4 | Vue.component(PixelUserInfo.name, PixelUserInfo)
5 | }
6 |
7 | export default PixelUserInfo
--------------------------------------------------------------------------------
/src/views/UserInfo/src/UserInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
{{user.name}}
8 | {{user.description}}
9 |
10 |
14 |
15 |
16 |
17 |
33 |
34 |
89 |
--------------------------------------------------------------------------------
/src/vuex/actions.js:
--------------------------------------------------------------------------------
1 | import ws from '@/websocket'
2 |
3 | const actions = {
4 |
5 | vx_send ({ commit }, message) {
6 |
7 | // 配置格式
8 | // const data = {
9 | // from: {
10 | // account: message.from,
11 | // token: '123456'
12 | // },
13 | // ms: message.text,
14 | // ms_type: 'CN00010'
15 | // }
16 | //
17 | let type
18 | if (message.to === 'all') {
19 | type = 'all'
20 | }
21 | else {
22 | type = 'send'
23 | }
24 |
25 | if (ws.send(JSON.stringify(message))) {
26 | commit('addMessage', { message, type })
27 | }
28 |
29 | },
30 |
31 | get_online ({ commit }) {
32 |
33 | }
34 |
35 | }
36 |
37 | export default actions
38 |
--------------------------------------------------------------------------------
/src/vuex/getters.js:
--------------------------------------------------------------------------------
1 | const getters = {
2 | user: state => state.user,
3 |
4 | // 好友列表
5 | friendList: state => {
6 | // 排序 分组
7 | const friendList = state.friendList
8 | if (friendList.length === 0) return []
9 |
10 | const list = friendList.sort()
11 | let toList = []
12 | let group = []
13 | let last = list[0].substr(0, 1)
14 | for (let i = 0; i < list.length; i++) {
15 |
16 | const current = list[i].substr(0, 1)
17 | if (current === last) {
18 | group.push(list[i])
19 | }
20 | else {
21 | toList.push(group)
22 | group = []
23 | group.push(list[i])
24 | }
25 |
26 | if (i === list.length - 1) {
27 | toList.push(group)
28 | }
29 | else {
30 | last = current
31 | }
32 |
33 | }
34 |
35 | return toList
36 | },
37 |
38 | // 在线用户列表
39 | onlineList: state => {
40 | // 排序 分组
41 | const onlineList = state.onlineList
42 | if (onlineList.length === 0) return []
43 |
44 | const list = onlineList.sort()
45 | let toList = []
46 | let group = []
47 | let last = list[0].substr(0, 1)
48 | for (let i = 0; i < list.length; i++) {
49 |
50 | const current = list[i].substr(0, 1)
51 | if (current === last) {
52 | group.push(list[i])
53 | }
54 | else {
55 | toList.push(group)
56 | group = []
57 | group.push(list[i])
58 | }
59 | if (i === list.length - 1) {
60 | toList.push(group)
61 | }
62 | else {
63 | last = current
64 | }
65 | }
66 |
67 | return toList
68 | }
69 | }
70 |
71 | export default getters
72 |
--------------------------------------------------------------------------------
/src/vuex/mutations.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import store from './store'
3 |
4 | let mutations = {
5 |
6 | // add message
7 | addMessage ({ sessionList }, { message, type}) {
8 |
9 | let title
10 | switch (type) {
11 | case 'send':
12 | title = message.to
13 | break
14 | case 'receive':
15 | title = message.from
16 | break
17 | case 'all':
18 | title = '聊天室'
19 | break
20 | default:
21 | }
22 |
23 | // 判断会话是否存在
24 | let _index = false
25 | sessionList.forEach((session, index) => {
26 | if (session.title === title) {
27 | // 存在 记录索引
28 | _index = index
29 | return
30 | }
31 | })
32 |
33 | if (_index !== false) {
34 | // 存在
35 | sessionList[_index].messages.push(message)
36 | // 移动到最前端
37 | if (_index > 0) {
38 | // const currentSession = sessionList.splice(_index, 1)
39 | // sessionList.unshift(currentSession)
40 | }
41 | }
42 | else {
43 | // 不存在 添加
44 | const session = {
45 | title,
46 | messages: [message]
47 | }
48 | sessionList.unshift(session)
49 | }
50 | }
51 | }
52 |
53 | export default mutations
54 |
--------------------------------------------------------------------------------
/src/vuex/state/index.js:
--------------------------------------------------------------------------------
1 |
2 | // 会话列表
3 | let sessionList = [
4 | {
5 | title: '聊天室',
6 | messages: [
7 | {
8 | from: '小三',
9 | to: '聊天室',
10 | content: {
11 | text: '现在方便吗?'
12 | },
13 | time: '12:00'
14 | },
15 | {
16 | from: '老王',
17 | to: '聊天室',
18 | content: {
19 | text: '你不要来烦我'
20 | },
21 | time: '12:04'
22 | }
23 | ]
24 | }
25 | ]
26 | // 好友列表
27 | let friendList = [
28 | 'moohng',
29 | 'b',
30 | 'bbbbbb'
31 | ]
32 |
33 |
34 | let state = {
35 | user: {},
36 | // 聊天记录
37 | sessionList,
38 |
39 | friendList,
40 |
41 | newFriendList: [],
42 |
43 | }
44 |
45 | export default state
46 |
--------------------------------------------------------------------------------
/src/vuex/state/message.json:
--------------------------------------------------------------------------------
1 | {
2 | "from": {
3 | "username": "moohng",
4 | },
5 | "to": {
6 | "username": "1"
7 | },
8 | "info": {
9 | "text": "Hey! Nice a day!"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/vuex/store.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import mutations from './mutations'
4 | import actions from './actions'
5 | import state from './state'
6 | import getters from './getters'
7 |
8 |
9 | Vue.use(Vuex)
10 |
11 | export default new Vuex.Store({
12 | state,
13 | getters,
14 | actions,
15 | mutations
16 | })
17 |
--------------------------------------------------------------------------------
/src/websocket/add.js:
--------------------------------------------------------------------------------
1 | import store from '@/vuex/store'
2 | import modal from '@/plugins/modal'
3 | import request from '@/plugins/WRequest'
4 |
5 | export default message => {
6 | // 添加好友请求
7 | modal.confirm({
8 | text: message.from + ' 请求添加您为好友?',
9 | title: '好友请求',
10 | ok: '接受',
11 | cancel: '拒绝'
12 | }, value => {
13 | // yes or no
14 | if (value) {
15 | request.acceptFriend(message.from, err => {
16 | if (err) {
17 | console.log(err)
18 | return
19 | }
20 |
21 | // 已接受好友请求
22 | console.log('已接受好友请求')
23 | })
24 | }
25 | else {
26 |
27 | }
28 | })
29 | }
30 |
--------------------------------------------------------------------------------
/src/websocket/chat.js:
--------------------------------------------------------------------------------
1 | import store from '@/vuex/store'
2 |
3 | export default message => {
4 | // 个人消息
5 | store.commit('addMessage', { message, type: 'receive' })
6 | }
7 |
--------------------------------------------------------------------------------
/src/websocket/chatroom.js:
--------------------------------------------------------------------------------
1 | import store from '@/vuex/store'
2 |
3 | export default message => {
4 | // 聊天室
5 | store.commit('addMessage', { message, type: 'all' })
6 | }
7 |
--------------------------------------------------------------------------------
/src/websocket/index.js:
--------------------------------------------------------------------------------
1 | // import WebSocket from 'ws'
2 | import WSocket from '@/plugins/WSocket'
3 |
4 | import add from './add'
5 | import chat from './chat'
6 | import chatroom from './chatroom'
7 |
8 | const ws = new WSocket()
9 |
10 | // 监听
11 | ws.on('open', () => {
12 |
13 | })
14 | ws.on('message', message => {
15 | // 接收到消息
16 | console.log(message)
17 | // 解析消息
18 | try {
19 | message = JSON.parse(message)
20 | }
21 | catch(err) {
22 | console.log('错误消息')
23 | return
24 | }
25 |
26 | switch (message.code) {
27 | case 100: // 添加好友
28 | add(message)
29 | break
30 | case 200: // 私聊
31 | chat(message)
32 | break
33 | case 300: // 聊天室
34 | chatroom(message)
35 | break
36 | default:
37 | }
38 |
39 | })
40 |
41 | export default ws
42 |
--------------------------------------------------------------------------------
/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GGwujun/Dsx_wechat/4ce6fd9f1e737ce97fab13185b3c46340b1e8c70/static/.gitkeep
--------------------------------------------------------------------------------
{{comment.user.name}}
9 | {{formatTime(comment.created_at)}} 10 |