├── .env
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── README.md
├── config
├── htmlAfterWebpackPlugin.js
├── webpack.development.js
└── webpack.production.js
├── developement.md
├── dist
├── .env
├── app.js
├── assets
│ ├── favicon.ico
│ ├── images
│ │ ├── 02d9a1cfacacfd02-c33ab.min.png
│ │ ├── 2fb5c91b6d9c4f02-3548d.min.png
│ │ ├── 303b0c915e984e02-41211.min.png
│ │ ├── 33bfeb065105c002-efcf5.min.png
│ │ ├── 4cf2f7c2e63f4a02-991ec.min.png
│ │ ├── 4d709e27fa18aa02-f0e20.min.png
│ │ ├── 536fa0d98d69d102-55138.min.png
│ │ ├── 6ea30b7cd9472a02-a2eb4.min.png
│ │ ├── 79273a9754b54002-fc63e.min.png
│ │ ├── 8bde275d8233602-4cee0.min.gif
│ │ ├── 9fd2391a3cf49702-cd804.min.png
│ │ ├── a3faf4373242d1-991ec.min.png
│ │ ├── a4ae5891171faf02-0f81e.min.png
│ │ ├── c463d610f0e32702-ca387.min.png
│ │ └── f883b0fcd93f602-870c0.min.png
│ ├── scripts
│ │ ├── _startalk_sdk.55783cb1.js
│ │ ├── common.55783cb1.js
│ │ └── index.55783cb1.js
│ └── styles
│ │ └── index.55783cb1.css
├── dotenv.js
├── middlewares
│ ├── devMiddleware.js
│ ├── errorHandler.js
│ ├── hotMiddleware.js
│ └── proxyMiddleware.js
├── package.json
├── profiles
│ └── production
│ │ └── startalk.env
├── routes
│ └── index.js
├── utils
│ └── formatter.js
└── views
│ └── index.html
├── dotenv.js
├── gulpfile.js
├── package-lock.json
├── package.json
├── profiles
├── development
│ └── startalk.env
└── production
│ └── startalk.env
├── src
├── assets
│ ├── README.md
│ ├── chat
│ │ ├── 17e02.png
│ │ ├── 38c02.png
│ │ ├── 4f302.png
│ │ ├── 75702.png
│ │ ├── 80702.png
│ │ ├── 87702.png
│ │ ├── cd902.png
│ │ ├── e8002.png
│ │ ├── efb02.png
│ │ ├── fd802.png
│ │ └── packing-logo.png
│ ├── favicon.ico
│ ├── footer
│ │ ├── 4cf2f7c2e63f4a02.png
│ │ ├── a3faf4373242d1.png
│ │ └── ff1a003aa731b0d4e2dd3d39687c8a54.png
│ ├── index
│ │ ├── 02d9a1cfacacfd02.png
│ │ ├── 2fb5c91b6d9c4f02.png
│ │ ├── 33bfeb065105c002.png
│ │ ├── 4d709e27fa18aa02.png
│ │ ├── 536fa0d98d69d102.png
│ │ ├── 6ea30b7cd9472a02.png
│ │ ├── 9fd2391a3cf49702.png
│ │ ├── a4ae5891171faf02.png
│ │ ├── c463d610f0e32702.png
│ │ └── f883b0fcd93f602.png
│ └── jstree
│ │ ├── 303b0c915e984e02.png
│ │ ├── 79273a9754b54002.png
│ │ └── 8bde275d8233602.gif
├── nodeuii
│ ├── app.js
│ ├── middlewares
│ │ ├── devMiddleware.js
│ │ ├── errorHandler.js
│ │ ├── hotMiddleware.js
│ │ └── proxyMiddleware.js
│ ├── routes
│ │ └── index.js
│ └── utils
│ │ └── formatter.js
└── web
│ ├── app
│ ├── common
│ │ ├── components
│ │ │ ├── message-box
│ │ │ │ └── index.js
│ │ │ ├── modal
│ │ │ │ ├── index.js
│ │ │ │ └── index.less
│ │ │ └── select2-one
│ │ │ │ ├── index.js
│ │ │ │ └── index.less
│ │ ├── lib
│ │ │ ├── caret.js
│ │ │ ├── jstree.js
│ │ │ └── kindeditor
│ │ │ │ └── kindeditor-all.js
│ │ ├── styles
│ │ │ ├── animate.less
│ │ │ ├── chat.less
│ │ │ ├── icon.less
│ │ │ ├── index.less
│ │ │ ├── jstree.css
│ │ │ ├── login.less
│ │ │ ├── modals.less
│ │ │ ├── panel.less
│ │ │ ├── phone
│ │ │ │ ├── chat.less
│ │ │ │ ├── modals.less
│ │ │ │ └── panel.less
│ │ │ └── reset.less
│ │ └── utils
│ │ │ ├── namespace-actions.js
│ │ │ ├── namespace-reducers.js
│ │ │ └── router.js
│ ├── index.html
│ └── pages
│ │ └── index
│ │ ├── actions.js
│ │ ├── consts.js
│ │ ├── entry.js
│ │ ├── entry.settings.js
│ │ ├── import-less.js
│ │ ├── index.js
│ │ ├── phone-ui
│ │ ├── chat
│ │ │ ├── emotions.js
│ │ │ ├── empty.js
│ │ │ ├── footer.js
│ │ │ ├── groupCard.js
│ │ │ ├── header.js
│ │ │ ├── index.js
│ │ │ ├── members.js
│ │ │ ├── message.js
│ │ │ └── userCard.js
│ │ ├── login
│ │ │ └── index.js
│ │ ├── modal
│ │ │ ├── addFriends.js
│ │ │ ├── addUser.js
│ │ │ ├── contentmenu.js
│ │ │ ├── groupCard.js
│ │ │ ├── members.js
│ │ │ └── userCard.js
│ │ ├── panel
│ │ │ ├── friends.js
│ │ │ ├── index.js
│ │ │ ├── info.js
│ │ │ ├── search.js
│ │ │ ├── session.js
│ │ │ └── tab.js
│ │ └── tree
│ │ │ ├── index.js
│ │ │ └── index.less
│ │ ├── reducer.js
│ │ ├── sdk.js
│ │ └── ui
│ │ ├── chat
│ │ ├── emotions.js
│ │ ├── empty.js
│ │ ├── footer.js
│ │ ├── groupCard.js
│ │ ├── header.js
│ │ ├── index.js
│ │ ├── members.js
│ │ ├── message.js
│ │ └── userCard.js
│ │ ├── login
│ │ └── index.js
│ │ ├── modal
│ │ ├── addFriends.js
│ │ ├── addUser.js
│ │ ├── contentmenu.js
│ │ ├── groupCard.js
│ │ ├── members.js
│ │ └── userCard.js
│ │ ├── panel
│ │ ├── friends.js
│ │ ├── index.js
│ │ ├── info.js
│ │ ├── search.js
│ │ ├── session.js
│ │ └── tab.js
│ │ └── tree
│ │ ├── index.js
│ │ └── index.less
│ ├── index.js
│ └── sdk
│ ├── common
│ ├── assets
│ │ └── 20180423_qtalk_msg.mp3
│ ├── lib
│ │ ├── jquery.fileupload.js
│ │ ├── jquery.iframe.transport.js
│ │ ├── jquery.md5.js
│ │ ├── jquery.ui.widget.js
│ │ └── strophejs-plugin-iexdomain.js
│ └── utils
│ │ ├── messageHelper.js
│ │ ├── publicEncrypt.js
│ │ ├── randomBytes.js
│ │ └── utils.js
│ ├── core
│ ├── auth.js
│ ├── buildMessage.js
│ ├── connection.js
│ ├── emotions
│ │ ├── index.js
│ │ └── oneEmotions.js
│ ├── index.js
│ ├── message.js
│ ├── ping.js
│ ├── strophe.js
│ └── upload.js
│ ├── entry.js
│ └── options.js
├── webpack.config.js
└── 开发人员使用手册.md
/.env:
--------------------------------------------------------------------------------
1 | /*
2 | * 开发环境 development
3 | * 生产环境 production
4 | */
5 |
6 | NODE_ENV=production
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /.tmp/**
2 | /docs/**
3 | /mock/**
4 | /prd/**
5 | /node_modules/**
6 | /src/pages/**
7 | /src/sdk/common/**
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | 'eslint-config-qunar/base'
4 | ].map(require.resolve),
5 | rules: {
6 | 'prefer-arrow-callback': 0,
7 | 'object-curly-newline': 0,
8 | 'complexity': 0,
9 | 'prefer-promise-reject-errors': 0
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # kdiff3 ignore
2 | *.orig
3 |
4 | # maven ignore
5 | target/
6 |
7 | # eclipse ignore
8 | .settings/
9 | .project
10 | .classpath
11 |
12 | # idea ignore
13 | .idea/
14 | *.ipr
15 | *.iml
16 | *.iws
17 |
18 | # temp ignore
19 | *.log
20 | *.cache
21 | *.diff
22 | *.patch
23 | *.tmp
24 |
25 | # system ignore
26 | .DS_Store
27 | Thumbs.db
28 |
29 | # package ignore (optional)
30 | # *.jar
31 | # *.war
32 | # *.zip
33 | # *.tar
34 | # *.tar.gz
35 |
36 | # pods ignore
37 | Pods/
38 | node_modules/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
8 | ## Startalk Web
9 | ### 简介
10 | - Startalk Web是IM网页版聊天工具。
11 | - Startalk Web依赖[后端服务](https://github.com/qunarcorp/ejabberd-open),否则无法正常使用该工具。
12 | - 涉及技术:node、koa、react、websocket、webpack等。
13 | - 主要功能:单聊、群聊、好友、组织架构、个人名片等,消息支持:文本、表情、图片、文件等。
14 | - 如果Startalk Web对您有所帮助或启发的话,还望给个star鼓励,我们团队会尽全力提供优化和支持,力求做出最优秀的企业级IM。
15 | - 此外,为有效、流畅体验Startalk Web,还请仔细阅读安装说明,如若遇到问题,欢迎进群咨询[QQ群:852987381]。
16 | ### 安装
17 | #### 环境要求
18 | - node@ >= 8.6.0
19 | - pm2 @>= 2.0.0
20 | #### 服务器环境安装(root用户)
21 | - 登录服务器后,进入下载目录,安装node:
22 | ```
23 | cd /startalk/download
24 | wget https://npm.taobao.org/mirrors/node/v8.6.0/node-v8.6.0-linux-x64.tar.xz
25 | tar -xvf node-v8.6.0-linux-x64.tar.xz
26 | cd node-v8.6.0-linux-x64/bin
27 | ```
28 | - 执行以下命令,若显示 v8.6.0 ,则表明安装成功
29 | ```
30 | ./node -v
31 | ```
32 | - 配置软连接,便于全局使用 node npm命令
33 | ```
34 | ln -s /startalk/download/node-v8.6.0-linux-x64/bin/node /usr/local/bin/node
35 | ln -s /startalk/download/node-v8.6.0-linux-x64/bin/npm /usr/local/bin/npm
36 | ```
37 | - 分别执行以下命令,若返回版本号,则表示配置成功
38 | ```
39 | node -v
40 | npm -v
41 | ```
42 | - 安装pm2,并配置软连接,便于全局使用 pm2命令
43 | ```
44 | npm install -g pm2
45 | ln -s /startalk/download/node-v8.6.0-linux-x64/bin/pm2 /usr/local/bin/pm2
46 | ```
47 | - 执行以下命令,若返回版本号,则表示配置成功
48 | ```
49 | pm2 -v
50 | ```
51 | #### 下载代码到服务器
52 | - 在 /startalk/download 目录下下载源码,然后将项目 /dist 目录下文件copy到 /startalk/startalk_web 目录下。
53 | ```
54 | cd /startalk/download
55 | git clone https://github.com/qunarcorp/startalk_web.git
56 | cp -rf startalk_web/dist /startalk/startalk_web
57 | ```
58 | #### 修改配置
59 | - 进入项目目录,配置 startalk.env 文件,将 BASEURL=http://IP:8080 的IP改成服务器IP,NAVIGATION=/ 改为后台导航
60 | - 其他配置:端口、公共路径可根据实际情况选择性配置
61 | - 建议采用淘宝镜像: npm config set registry https://registry.npm.taobao.org
62 | ```
63 | cd /startalk/startalk_web
64 | npm install -production
65 | vim profiles/production/startalk.env
66 | ```
67 | - 编辑完成后,保存退出vim编辑
68 | ```
69 | 按下 esc键
70 | 输入 :wq
71 | 回车
72 | ```
73 | #### 项目启动与预览
74 | - 使用pm2启动项目
75 | ```
76 | pm2 start /startalk/startalk_web/app.js --watch
77 | ```
78 | - 执行以下命令,查看是否启动成功,若该项目对应的status为online,则表明启动成功
79 | ```
80 | pm2 list
81 | ```
82 | - 注意:本次部署是以后台服务和前端服务部署在同一台机器上为背景,如需部署多台机器,则需通过nginx配置转发,以解决接口的跨域问题。
83 | - 项目预览:
84 | - 项目启动成功后,在电脑浏览器中输入 [本机IP:8080/web],回车键访问,输入测试账号登录,(测试账号admin,密码testpassword)
85 | - 至此,您便可享用web版及时通信聊天工具。
86 |
87 | ### 注意
88 | #### 注意事项
89 | - 前端服务默认端口为5000,默认公共路径为根路径(startalk.env 文件配置)
90 | - 如果需要修改端口或者公共路径,需要同步修改ng转发配置,确保后端接口正常调用
91 | - .env 文件默认为生产环境 NODE_ENV=production
92 | - 如若需要修改源代码,则需要重新打包生成dist目录
93 | - 先fork项目到自己的GitHub
94 | - 参考本项目下的developement.md文件,进行本地开发调试
95 | - 本地启动成功后便可将最新的dist文件上传至服务器启动
96 | - 接口位于entry.js
97 | - 获取直属领导,员工编号和查询用户电话功能已写好,接口需使用者实现
98 | #### 其他辅助命令(部署成功后不需要执行)
99 | - sudo netstat -anlp| grep 5000 查看5000 端口的进程
100 | - sudo kill -9 [进程code] 结束进程
101 | - df -h 查看不同分区的大小
102 | - rm -rf *** 删除文件夹
103 | - pm2 start [启动文件]
104 | - pm2 log 查看pm2日志
105 | - pm2 list 查看pm2启动项目清单及状态
106 | - pm2 delete [id] 删除pm2进程
107 | - pm2 show [id] 查看项目启动详情
--------------------------------------------------------------------------------
/config/htmlAfterWebpackPlugin.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: your name
4 | * @Date: 2019-08-05 14:54:15
5 | * @LastEditTime: 2019-08-13 15:49:43
6 | * @LastEditors: Please set LastEditors
7 | */
8 | const HtmlWebpackPlugin = require('html-webpack-plugin')
9 | const pluginName = "htmlAfterWebpackPlugin"
10 |
11 | const assetHelper = (assetJson = []) => {
12 | const css = []
13 | const js = []
14 | const assets = {
15 | js: (item) => ``,
16 | css: (item) => ``
17 | }
18 | const cssReg = /.css$/
19 | const jsReg = /.js$/
20 |
21 | assetJson.map(item => {
22 | if (cssReg.test(item)) {
23 | css.push(assets.css(item.slice(item.lastIndexOf('/')+1)))
24 | }
25 | if (jsReg.test(item)) {
26 | js.push(assets.js(item.slice(item.lastIndexOf('/')+1)))
27 | }
28 | })
29 |
30 | return {
31 | js,
32 | css
33 | }
34 | }
35 |
36 | class htmlAfterWebpackPlugin {
37 | apply(compiler) {
38 | compiler.hooks.compilation.tap(pluginName, (compilation) => {
39 | HtmlWebpackPlugin.getHooks(compilation).afterTemplateExecution.tapAsync(
40 | pluginName,
41 | (data, cb) => {
42 | const { html, plugin } = data
43 | let _html = html
44 | const assets = assetHelper(JSON.parse(plugin.assetJson))
45 |
46 | _html = _html.replace('', assets.css.join(''))
47 | _html = _html.replace('', assets.js.join(''))
48 |
49 | data.html = _html
50 | cb(null, data)
51 | }
52 | )
53 | })
54 | }
55 | }
56 | module.exports = htmlAfterWebpackPlugin
--------------------------------------------------------------------------------
/config/webpack.development.js:
--------------------------------------------------------------------------------
1 | const MiniCssExtractPlugin = require("mini-css-extract-plugin")
2 |
3 | module.exports = {
4 | watch:true,
5 | watchOptions:{
6 | ignored: /node_modules/
7 | },
8 | mode: 'development',
9 | stats: {
10 | children: false,
11 | },
12 | plugins: [
13 | new MiniCssExtractPlugin({
14 | filename: './styles/[name].css'
15 | })
16 | ]
17 | }
--------------------------------------------------------------------------------
/config/webpack.production.js:
--------------------------------------------------------------------------------
1 | const CleanWebpackPlugin = require('clean-webpack-plugin')
2 | const MiniCssExtractPlugin = require("mini-css-extract-plugin")
3 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
4 |
5 | // 生产环境打包文件加 hash
6 | module.exports = {
7 | mode: "production",
8 | output: {
9 | filename: 'scripts/[name].[hash:8].js'
10 | },
11 | plugins: [
12 | new CleanWebpackPlugin({
13 | root: __dirname + 'dist/assets'
14 | }),
15 | new MiniCssExtractPlugin({
16 | filename: './styles/[name].[hash:8].css'
17 | }),
18 | // 分析文件打包
19 | new BundleAnalyzerPlugin()
20 | ]
21 | }
--------------------------------------------------------------------------------
/developement.md:
--------------------------------------------------------------------------------
1 | ### 本地开发环境要求
2 | - node@ >= 8.6.0 npm git等工具
3 |
4 | ### 项目启动开发
5 | - git clone https://github.com/qunarcorp/startalk_web.git 克隆代码到本地
6 | - npm install 安装项目依赖
7 | - 修改 .env 文件为 NODE_ENV=development
8 | - 进入目录文件 profiles/development/startalk.env, 配置后台地址
9 | 如: BASEURL=http://127.0.0.1:8080
10 | NAVIGATION=startalk_nav
11 | - 执行 npm run build
12 | - 新开 tab 页执行 npm run dev 启动项目
13 |
14 | ### 项目打包上线
15 | - 修改 .env 文件为 NODE_ENV=production
16 | - 进入目录文件 profiles/production/startalk.env 配置线上环境后台地址
17 | 如: BASEURL=http://127.0.0.1:8080
18 | NAVIGATION=startalk_nav
19 | - npm run build
20 | - npm run client
21 | - 将生产的dist目录上传至服务器
22 | - 服务器启动: pm2 start app.js --watch
23 |
24 | ### 书写规范
25 | - 驼峰命名
26 | - 中英文之间空格
27 | - 不写分号
28 |
29 | ### 注意事项
30 | #### 1.模块化引用,减少包的体积
31 | - 比如lodash
32 | - import omit from 'lodash/omit' //best
33 | - import { omit } from 'lodash' //bad
--------------------------------------------------------------------------------
/dist/.env:
--------------------------------------------------------------------------------
1 | /*
2 | * 开发环境 development
3 | * 生产环境 production
4 | */
5 |
6 | NODE_ENV=production
--------------------------------------------------------------------------------
/dist/app.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _koa = require("koa");
4 |
5 | var _koa2 = _interopRequireDefault(_koa);
6 |
7 | var _koaCompress = require("koa-compress");
8 |
9 | var _koaCompress2 = _interopRequireDefault(_koaCompress);
10 |
11 | var _koaViews = require("koa-views");
12 |
13 | var _koaViews2 = _interopRequireDefault(_koaViews);
14 |
15 | var _koaStatic = require("koa-static");
16 |
17 | var _koaStatic2 = _interopRequireDefault(_koaStatic);
18 |
19 | var _index = require("./routes/index");
20 |
21 | var _index2 = _interopRequireDefault(_index);
22 |
23 | var _path = require("path");
24 |
25 | var _path2 = _interopRequireDefault(_path);
26 |
27 | require("./dotenv");
28 |
29 | var _log4js = require("log4js");
30 |
31 | var _log4js2 = _interopRequireDefault(_log4js);
32 |
33 | var _errorHandler = require("./middlewares/errorHandler.js");
34 |
35 | var _errorHandler2 = _interopRequireDefault(_errorHandler);
36 |
37 | var _proxyMiddleware = require("./middlewares/proxyMiddleware");
38 |
39 | var _proxyMiddleware2 = _interopRequireDefault(_proxyMiddleware);
40 |
41 | var _formatter = require("./utils/formatter");
42 |
43 | var _request = require("request");
44 |
45 | var _request2 = _interopRequireDefault(_request);
46 |
47 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
48 |
49 | /*
50 | * @Description: In User Settings Edit
51 | * @Author: xi.guo
52 | * @Date: 2019-08-05 14:54:15
53 | * @LastEditTime: 2019-08-19 10:24:27
54 | * @LastEditors: Please set LastEditors
55 | */
56 | // development webpack-dev-middleware
57 | let webpack, webpackConfig, devMiddleware, hotMiddleware, compiler;
58 |
59 | if (process.env.NODE_ENV === 'development') {
60 | webpack = require('webpack');
61 |
62 | const RawModule = require('webpack/lib/RawModule');
63 |
64 | webpackConfig = require('../webpack.config.js');
65 | devMiddleware = require('./middlewares/devMiddleware');
66 | hotMiddleware = require('./middlewares/hotMiddleware');
67 | compiler = webpack(webpackConfig);
68 | compiler.plugin('emit', (compilation, callback) => {
69 | const assets = compilation.assets;
70 | let data;
71 | Object.keys(assets).forEach(key => {
72 | if (key.match(/\.html$/)) {
73 | data = assets[key].source();
74 | data = data.replace('<%=navConfig%>', JSON.stringify(global.startalkNavConfig));
75 | data = data.replace('<%=keys%>', JSON.stringify(global.startalkKeys));
76 | assets[key] = new RawModule(data).source();
77 | }
78 | });
79 | callback();
80 | });
81 | }
82 |
83 | const app = new _koa2.default();
84 | const {
85 | PORT,
86 | IP,
87 | BASEURL,
88 | NAVIGATION
89 | } = process.env;
90 | global.startalkNavConfig = {};
91 | global.startalkKeys = {};
92 | (0, _request2.default)(`${BASEURL}/${NAVIGATION}`, (error, response, body) => {
93 | if (!error && response.statusCode == 200) {
94 | global.startalkNavConfig = JSON.parse(body);
95 | (0, _request2.default)(`${global.startalkNavConfig.baseaddess.javaurl}/qtapi/nck/rsa/get_public_key.do`, (error, response, body) => {
96 | if (!error && response.statusCode == 200) {
97 | global.startalkKeys = JSON.parse(body).data;
98 | }
99 | });
100 | }
101 | });
102 | app.use((0, _koaCompress2.default)({
103 | threshold: 2048
104 | })); // development webpack-dev-middleware
105 |
106 | if (process.env.NODE_ENV === 'development') {
107 | app.use(devMiddleware(compiler, {
108 | publicPath: webpackConfig.output.publicPath,
109 | quiet: true
110 | }));
111 | app.use(hotMiddleware(compiler));
112 | }
113 |
114 | app.use((0, _koaViews2.default)(_path2.default.join(__dirname, './views'), {
115 | map: {
116 | html: 'ejs'
117 | }
118 | }));
119 | app.use((0, _koaStatic2.default)(_path2.default.join(__dirname, './assets'))); // 错误日志处理
120 |
121 | _log4js2.default.configure({
122 | appenders: {
123 | cheese: {
124 | type: 'file',
125 | filename: _path2.default.join(__dirname, `logs/${(0, _formatter.getNowDate)()}.log`)
126 | }
127 | },
128 | categories: {
129 | default: {
130 | appenders: ['cheese'],
131 | level: 'error'
132 | }
133 | }
134 | });
135 |
136 | const logger = _log4js2.default.getLogger('cheese');
137 |
138 | _errorHandler2.default.error(app, logger); // 代理
139 |
140 |
141 | app.use((0, _proxyMiddleware2.default)()); //路由
142 |
143 | app.use(_index2.default.routes());
144 | app.listen(PORT, IP, () => {
145 | console.log(`请访问端口:${PORT}`);
146 | });
--------------------------------------------------------------------------------
/dist/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/dist/assets/favicon.ico
--------------------------------------------------------------------------------
/dist/assets/images/02d9a1cfacacfd02-c33ab.min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/dist/assets/images/02d9a1cfacacfd02-c33ab.min.png
--------------------------------------------------------------------------------
/dist/assets/images/2fb5c91b6d9c4f02-3548d.min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/dist/assets/images/2fb5c91b6d9c4f02-3548d.min.png
--------------------------------------------------------------------------------
/dist/assets/images/303b0c915e984e02-41211.min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/dist/assets/images/303b0c915e984e02-41211.min.png
--------------------------------------------------------------------------------
/dist/assets/images/33bfeb065105c002-efcf5.min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/dist/assets/images/33bfeb065105c002-efcf5.min.png
--------------------------------------------------------------------------------
/dist/assets/images/4cf2f7c2e63f4a02-991ec.min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/dist/assets/images/4cf2f7c2e63f4a02-991ec.min.png
--------------------------------------------------------------------------------
/dist/assets/images/4d709e27fa18aa02-f0e20.min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/dist/assets/images/4d709e27fa18aa02-f0e20.min.png
--------------------------------------------------------------------------------
/dist/assets/images/536fa0d98d69d102-55138.min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/dist/assets/images/536fa0d98d69d102-55138.min.png
--------------------------------------------------------------------------------
/dist/assets/images/6ea30b7cd9472a02-a2eb4.min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/dist/assets/images/6ea30b7cd9472a02-a2eb4.min.png
--------------------------------------------------------------------------------
/dist/assets/images/79273a9754b54002-fc63e.min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/dist/assets/images/79273a9754b54002-fc63e.min.png
--------------------------------------------------------------------------------
/dist/assets/images/8bde275d8233602-4cee0.min.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/dist/assets/images/8bde275d8233602-4cee0.min.gif
--------------------------------------------------------------------------------
/dist/assets/images/9fd2391a3cf49702-cd804.min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/dist/assets/images/9fd2391a3cf49702-cd804.min.png
--------------------------------------------------------------------------------
/dist/assets/images/a3faf4373242d1-991ec.min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/dist/assets/images/a3faf4373242d1-991ec.min.png
--------------------------------------------------------------------------------
/dist/assets/images/a4ae5891171faf02-0f81e.min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/dist/assets/images/a4ae5891171faf02-0f81e.min.png
--------------------------------------------------------------------------------
/dist/assets/images/c463d610f0e32702-ca387.min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/dist/assets/images/c463d610f0e32702-ca387.min.png
--------------------------------------------------------------------------------
/dist/assets/images/f883b0fcd93f602-870c0.min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/dist/assets/images/f883b0fcd93f602-870c0.min.png
--------------------------------------------------------------------------------
/dist/dotenv.js:
--------------------------------------------------------------------------------
1 | const dotenv = require('dotenv')
2 | const glob = require('glob')
3 |
4 | dotenv.config()
5 |
6 | const { NODE_ENV } = process.env
7 |
8 | if (!NODE_ENV) {
9 | console.log([
10 | '[error]: The .env file is not found. ',
11 | 'Please run the following command to create the file in the root of the project:',
12 | 'echo NODE_ENV=development > .env'
13 | ].join('\n'));
14 | process.exit(1);
15 | }
16 |
17 | const envs = glob.sync(`${__dirname}/profiles/${NODE_ENV}/**/*.env`)
18 |
19 | envs.forEach(env => {
20 | dotenv.config({ path: env })
21 | console.log(`[dotenv]\`${env}\` loaded.`)
22 | })
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/dist/middlewares/devMiddleware.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _webpackDevMiddleware = require("webpack-dev-middleware");
4 |
5 | var _webpackDevMiddleware2 = _interopRequireDefault(_webpackDevMiddleware);
6 |
7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
8 |
9 | // 改造成koa中间件
10 | const devMiddleware = (compiler, opts) => {
11 | const middleware = (0, _webpackDevMiddleware2.default)(compiler, opts);
12 | return async (ctx, next) => {
13 | await middleware(ctx.req, {
14 | end: content => {
15 | ctx.body = content;
16 | },
17 | setHeader: (name, value) => {
18 | ctx.set(name, value);
19 | }
20 | }, next);
21 | };
22 | };
23 |
24 | module.exports = devMiddleware;
--------------------------------------------------------------------------------
/dist/middlewares/errorHandler.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | const errorHandler = {
7 | error(app, logger) {
8 | app.use(async (ctx, next) => {
9 | try {
10 | await next();
11 | } catch (error) {
12 | logger.error(error);
13 | ctx.status = error.status || 500;
14 | ctx.body = "error page";
15 | }
16 | });
17 | app.use(async (ctx, next) => {
18 | await next();
19 | if (404 != ctx.status) return;
20 | ctx.status = 404;
21 | ctx.response.redirect('/');
22 | });
23 | }
24 |
25 | }; //
26 |
27 | exports.default = errorHandler;
--------------------------------------------------------------------------------
/dist/middlewares/hotMiddleware.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _webpackHotMiddleware = require("webpack-hot-middleware");
4 |
5 | var _webpackHotMiddleware2 = _interopRequireDefault(_webpackHotMiddleware);
6 |
7 | var _stream = require("stream");
8 |
9 | var _stream2 = _interopRequireDefault(_stream);
10 |
11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12 |
13 | // 改造成koa中间件
14 | const PassThrough = _stream2.default.PassThrough;
15 |
16 | const hotMiddleware = (compiler, opts) => {
17 | const middleware = (0, _webpackHotMiddleware2.default)(compiler, opts);
18 | return async (ctx, next) => {
19 | let stream = new PassThrough();
20 | ctx.body = stream;
21 | await middleware(ctx.req, {
22 | write: stream.write.bind(stream),
23 | writeHead: (status, headers) => {
24 | ctx.status = status;
25 | ctx.set(headers);
26 | }
27 | }, next);
28 | };
29 | };
30 |
31 | module.exports = hotMiddleware;
--------------------------------------------------------------------------------
/dist/middlewares/proxyMiddleware.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _koa2Connect = require("koa2-connect");
4 |
5 | var _koa2Connect2 = _interopRequireDefault(_koa2Connect);
6 |
7 | var _httpProxyMiddleware = require("http-proxy-middleware");
8 |
9 | var _httpProxyMiddleware2 = _interopRequireDefault(_httpProxyMiddleware);
10 |
11 | var _url = require("url");
12 |
13 | var _url2 = _interopRequireDefault(_url);
14 |
15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16 |
17 | /*
18 | * @Description: In User Settings Edit
19 | * @Author: xi.guo
20 | * @Date: 2019-08-05 14:54:15
21 | * @LastEditTime: 2019-08-13 14:33:20
22 | * @LastEditors: Please set LastEditors
23 | */
24 | const proxyMap = {
25 | '/package': {
26 | url: ['baseaddess', 'javaurl']
27 | },
28 | '/py/search': {
29 | url: ['ability', 'searchurl']
30 | },
31 | '/newapi': {
32 | url: ['baseaddess', 'httpurl']
33 | },
34 | '/api': {
35 | url: ['baseaddess', 'httpurl']
36 | },
37 | '/file': {
38 | url: ['baseaddess', 'fileurl']
39 | }
40 | };
41 |
42 | const getIn = (obj, arr = []) => {
43 | return arr.reduce((accumulator, currentValue) => {
44 | if (typeof accumulator === 'object') {
45 | return accumulator[currentValue];
46 | }
47 | }, obj);
48 | };
49 |
50 | const proxyMiddleware = () => {
51 | return async (ctx, next) => {
52 | for (var proxyKey in proxyMap) {
53 | if (ctx.url.startsWith(proxyKey)) {
54 | ctx.respond = false;
55 | const {
56 | body
57 | } = ctx.request;
58 | const contentType = ctx.request.header['content-type'];
59 |
60 | const urlObj = _url2.default.parse(global.startalkNavConfig && getIn(global.startalkNavConfig, proxyMap[proxyKey].url));
61 |
62 | const defaultOpt = {};
63 |
64 | if (proxyMap[proxyKey].pathRewrite) {
65 | defaultOpt.pathRewrite = {
66 | pathRewrite: {
67 | [proxyMap.proxyKey.pathRewrite]: urlObj.pathname
68 | }
69 | };
70 | }
71 |
72 | await (0, _koa2Connect2.default)((0, _httpProxyMiddleware2.default)(proxyKey, Object.assign({
73 | target: `${urlObj.protocol}//${urlObj.host}`,
74 | changeOrigin: true,
75 | onProxyReq: proxyReq => {
76 | if (body && contentType.indexOf('application/json') > -1) {
77 | const bodyData = JSON.stringify(body);
78 | proxyReq.setHeader('Content-Type', 'application/json');
79 | proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
80 | proxyReq.write(bodyData);
81 | } else if (body && contentType.indexOf('application/x-www-form-urlencoded') > -1) {
82 | const bodyData = Object.keys(body).map(key => `${key}=${body[key]}`).join('&');
83 | proxyReq.setHeader('Content-Type', 'application/x-www-form-urlencoded');
84 | proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
85 | proxyReq.write(bodyData);
86 | }
87 | }
88 | }, defaultOpt)))(ctx, next);
89 | }
90 | }
91 |
92 | await next();
93 | };
94 | };
95 |
96 | module.exports = proxyMiddleware;
--------------------------------------------------------------------------------
/dist/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "startalk_admin_web",
3 | "version": "1.0.0",
4 | "description": "startalk_admin_web",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "gulp",
9 | "dev": "node ./dist/app.js",
10 | "client": "webpack",
11 | "start": "pm2 start ./app.js",
12 | "eslint": "eslint --fix src"
13 | },
14 | "author": "",
15 | "private": true,
16 | "license": "ISC",
17 | "dependencies": {
18 | "dotenv": "^8.0.0",
19 | "ejs": "^2.6.2",
20 | "glob": "^7.1.4",
21 | "http-proxy-middleware": "^0.19.1",
22 | "koa": "^2.7.0",
23 | "koa-compress": "^3.0.0",
24 | "koa-router": "^7.4.0",
25 | "koa-static": "^5.0.0",
26 | "koa-views": "^6.2.0",
27 | "koa2-connect": "^1.0.2",
28 | "log4js": "^4.1.0",
29 | "request": "^2.88.0"
30 | },
31 | "devDependencies": {
32 | "@babel/core": "^7.4.3",
33 | "@babel/plugin-proposal-class-properties": "^7.4.0",
34 | "@babel/plugin-proposal-decorators": "^7.4.0",
35 | "@babel/plugin-syntax-dynamic-import": "^7.2.0",
36 | "@babel/plugin-transform-runtime": "^7.4.4",
37 | "@babel/preset-env": "^7.4.3",
38 | "@babel/preset-react": "^7.0.0",
39 | "@babel/runtime": "^7.0.0-beta.55",
40 | "autoprefixer": "^9.5.1",
41 | "axios": "^0.19.0",
42 | "babel-loader": "^8.0.5",
43 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
44 | "classnames": "^2.2.6",
45 | "clean-webpack-plugin": "^2.0.1",
46 | "clipboard": "^2.0.4",
47 | "copy-webpack-plugin": "^5.0.2",
48 | "css-loader": "^2.1.1",
49 | "dayjs": "^1.8.15",
50 | "del": "^4.1.0",
51 | "events": "^3.0.0",
52 | "file-loader": "^3.0.1",
53 | "gulp": "^4.0.0",
54 | "gulp-babel": "^8.0.0",
55 | "gulp-better-rollup": "^4.0.1",
56 | "gulp-watch": "^5.0.1",
57 | "html-loader": "^0.5.5",
58 | "html-webpack-plugin": "^4.0.0-beta.3",
59 | "imagemin": "^6.1.0",
60 | "imagemin-pngquant": "^7.0.0",
61 | "img-loader": "^3.0.1",
62 | "immutable": "^3.8.2",
63 | "jquery": "^3.4.1",
64 | "js-cookie": "^2.2.0",
65 | "less": "^3.9.0",
66 | "less-loader": "^4.1.0",
67 | "mini-css-extract-plugin": "^0.5.0",
68 | "optimize-css-assets-webpack-plugin": "^4.0.0",
69 | "polyfill-crypto.getrandomvalues": "^1.0.0",
70 | "postcss-loader": "^3.0.0",
71 | "react": "^16.8.6",
72 | "react-a11y": "^1.1.0",
73 | "react-dom": "^16.8.6",
74 | "react-lazyload": "^2.6.2",
75 | "react-redux": "^7.1.0",
76 | "react-router-dom": "^5.0.1",
77 | "react-transform-catch-errors": "^1.0.2",
78 | "redbox-react": "^1.6.0",
79 | "redux": "^4.0.4",
80 | "redux-actions": "^2.6.5",
81 | "redux-immutable": "^4.0.0",
82 | "strophe.js": "^1.3.3",
83 | "strophejs-plugin-disco": "0.0.2",
84 | "strophejs-plugin-ping": "0.0.3",
85 | "strophejs-plugin-vcard": "0.0.1",
86 | "style-loader": "^0.23.1",
87 | "url-loader": "^1.1.2",
88 | "webpack": "^4.29.6",
89 | "webpack-bundle-analyzer": "^3.3.2",
90 | "webpack-cli": "^3.3.0",
91 | "webpack-dev-middleware": "^3.7.0",
92 | "webpack-hot-middleware": "^2.25.0",
93 | "webpack-merge": "^4.2.1"
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/dist/profiles/production/startalk.env:
--------------------------------------------------------------------------------
1 | // 项目启动端口
2 | PORT=5000
3 |
4 | // 项目启动 IP
5 | IP=0.0.0.0
6 |
7 | // 后台接口地址(例:http://127.0.0.1:8080)
8 | BASEURL=
9 |
10 | //后台导航地址(例:startalk_nav或newapi/nck/qtalk_nav.qunar)
11 | NAVIGATION=
12 |
13 | //公共路径
14 | PUBLICPATH=/
--------------------------------------------------------------------------------
/dist/routes/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _koaRouter = require("koa-router");
8 |
9 | var _koaRouter2 = _interopRequireDefault(_koaRouter);
10 |
11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12 |
13 | /*
14 | * @Description: In User Settings Edit
15 | * @Author: your name
16 | * @Date: 2019-08-05 14:54:15
17 | * @LastEditTime: 2019-08-13 17:38:00
18 | * @LastEditors: Please set LastEditors
19 | */
20 | const router = new _koaRouter2.default();
21 | router.get('/reterievepassword', async (ctx, next) => {
22 | await ctx.render('reterievepassword');
23 | });
24 | router.get('/', async (ctx, next) => {
25 | const navConfig = JSON.stringify(global.startalkNavConfig);
26 | const keys = JSON.stringify(global.startalkKeys);
27 | await ctx.render('index', {
28 | navConfig,
29 | keys
30 | });
31 | });
32 | exports.default = router;
--------------------------------------------------------------------------------
/dist/utils/formatter.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | /*
8 | * @Description: In User Settings Edit
9 | * @Author: your name
10 | * @Date: 2019-08-05 14:54:15
11 | * @LastEditTime: 2019-08-12 19:55:02
12 | * @LastEditors: Please set LastEditors
13 | */
14 | const getNowDate = exports.getNowDate = () => {
15 | const time = new Date();
16 | const year = time.getFullYear();
17 | const month = time.getMonth() + 1;
18 | const day = time.getDate();
19 | return `${year}-${month}-${day}`;
20 | };
21 |
22 | const insertStr = exports.insertStr = (soure, start, newStr) => {
23 | return soure.slice(0, start) + newStr + soure.slice(start);
24 | };
--------------------------------------------------------------------------------
/dist/views/index.html:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | startalk
16 |
17 |
18 |
19 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/dotenv.js:
--------------------------------------------------------------------------------
1 | const dotenv = require('dotenv')
2 | const glob = require('glob')
3 |
4 | dotenv.config()
5 |
6 | const { NODE_ENV } = process.env
7 |
8 | if (!NODE_ENV) {
9 | console.log([
10 | '[error]: The .env file is not found. ',
11 | 'Please run the following command to create the file in the root of the project:',
12 | 'echo NODE_ENV=development > .env'
13 | ].join('\n'));
14 | process.exit(1);
15 | }
16 |
17 | const envs = glob.sync(`${__dirname}/profiles/${NODE_ENV}/**/*.env`)
18 |
19 | envs.forEach(env => {
20 | dotenv.config({ path: env })
21 | console.log(`[dotenv]\`${env}\` loaded.`)
22 | })
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp')
2 | const babel = require('gulp-babel')
3 | const watch = require('gulp-watch')
4 | const del = require('del')
5 | const dotenv = require('dotenv')
6 | dotenv.config()
7 |
8 | const ASSETS = './src/nodeuii/**/*.js'
9 | const DIST_PATH = './dist'
10 |
11 | gulp.task('clean', () => {
12 | return del(DIST_PATH, {
13 | force: true
14 | })
15 | })
16 |
17 | gulp.task('copyEnv', () => {
18 | return gulp.src('.env')
19 | .pipe(gulp.dest('./dist'))
20 | })
21 |
22 | gulp.task('copyDotenv', () => {
23 | return gulp.src('./dotenv.js')
24 | .pipe(gulp.dest('./dist'))
25 | })
26 |
27 | gulp.task('copyEnvs', () => {
28 | return gulp.src(`./profiles/${process.env.NODE_ENV}/**/*.env`)
29 | .pipe(gulp.dest(`./dist/profiles/${process.env.NODE_ENV}`))
30 | })
31 |
32 | gulp.task('build:dev', () => {
33 | return watch(ASSETS, { ignoreInitial: false }, () => {
34 | gulp.src(ASSETS)
35 | .pipe(babel({
36 | babelrc: false,
37 | 'plugins': [
38 | 'transform-es2015-modules-commonjs'
39 | ]
40 | }))
41 | .pipe(gulp.dest('./dist'))
42 | })
43 | })
44 |
45 | gulp.task('build:prod', () => {
46 | return gulp.src(ASSETS)
47 | .pipe(babel({
48 | babelrc: false,
49 | 'plugins': [
50 | 'transform-es2015-modules-commonjs'
51 | ]
52 | }))
53 | .pipe(gulp.dest('./dist'))
54 | })
55 |
56 | let _task = gulp.series('clean', 'copyEnv', 'copyDotenv', 'copyEnvs', 'build:dev')
57 |
58 | if (process.env.NODE_ENV === 'production') {
59 | _task = gulp.series('clean', 'copyEnv', 'copyDotenv', 'copyEnvs', 'build:prod')
60 | }
61 |
62 | gulp.task('default', _task)
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "startalk_admin_web",
3 | "version": "1.0.0",
4 | "description": "startalk_admin_web",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "gulp",
9 | "dev": "node ./dist/app.js",
10 | "client": "webpack",
11 | "start": "pm2 start ./app.js",
12 | "eslint": "eslint --fix src"
13 | },
14 | "author": "",
15 | "private": true,
16 | "license": "ISC",
17 | "dependencies": {
18 | "dotenv": "^8.0.0",
19 | "ejs": "^2.6.2",
20 | "glob": "^7.1.4",
21 | "http-proxy-middleware": "^0.19.1",
22 | "koa": "^2.7.0",
23 | "koa-compress": "^3.0.0",
24 | "koa-router": "^7.4.0",
25 | "koa-static": "^5.0.0",
26 | "koa-views": "^6.2.0",
27 | "koa2-connect": "^1.0.2",
28 | "log4js": "^4.1.0",
29 | "request": "^2.88.0"
30 | },
31 | "devDependencies": {
32 | "@babel/core": "^7.4.3",
33 | "@babel/plugin-proposal-class-properties": "^7.4.0",
34 | "@babel/plugin-proposal-decorators": "^7.4.0",
35 | "@babel/plugin-syntax-dynamic-import": "^7.2.0",
36 | "@babel/plugin-transform-runtime": "^7.4.4",
37 | "@babel/preset-env": "^7.4.3",
38 | "@babel/preset-react": "^7.0.0",
39 | "@babel/runtime": "^7.0.0-beta.55",
40 | "autoprefixer": "^9.5.1",
41 | "axios": "^0.21.1",
42 | "babel-loader": "^8.0.5",
43 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
44 | "classnames": "^2.2.6",
45 | "clean-webpack-plugin": "^2.0.1",
46 | "clipboard": "^2.0.4",
47 | "copy-webpack-plugin": "^5.0.2",
48 | "css-loader": "^2.1.1",
49 | "dayjs": "^1.8.15",
50 | "del": "^4.1.0",
51 | "events": "^3.0.0",
52 | "file-loader": "^3.0.1",
53 | "gulp": "^4.0.0",
54 | "gulp-babel": "^8.0.0",
55 | "gulp-better-rollup": "^4.0.1",
56 | "gulp-watch": "^5.0.1",
57 | "html-loader": "^0.5.5",
58 | "html-webpack-plugin": "^4.0.0-beta.3",
59 | "imagemin": "^6.1.0",
60 | "imagemin-pngquant": "^7.0.0",
61 | "img-loader": "^3.0.1",
62 | "immutable": "^3.8.2",
63 | "jquery": "^3.5.0",
64 | "js-cookie": "^2.2.0",
65 | "less": "^3.9.0",
66 | "less-loader": "^4.1.0",
67 | "mini-css-extract-plugin": "^0.5.0",
68 | "optimize-css-assets-webpack-plugin": "^4.0.0",
69 | "polyfill-crypto.getrandomvalues": "^1.0.0",
70 | "postcss-loader": "^3.0.0",
71 | "react": "^16.8.6",
72 | "react-a11y": "^1.1.0",
73 | "react-dom": "^16.8.6",
74 | "react-lazyload": "^2.6.2",
75 | "react-redux": "^7.1.0",
76 | "react-router-dom": "^5.0.1",
77 | "react-transform-catch-errors": "^1.0.2",
78 | "redbox-react": "^1.6.0",
79 | "redux": "^4.0.4",
80 | "redux-actions": "^2.6.5",
81 | "redux-immutable": "^4.0.0",
82 | "strophe.js": "^1.3.3",
83 | "strophejs-plugin-disco": "0.0.2",
84 | "strophejs-plugin-ping": "0.0.3",
85 | "strophejs-plugin-vcard": "0.0.1",
86 | "style-loader": "^0.23.1",
87 | "url-loader": "^1.1.2",
88 | "webpack": "^4.29.6",
89 | "webpack-bundle-analyzer": "^3.3.2",
90 | "webpack-cli": "^3.3.0",
91 | "webpack-dev-middleware": "^3.7.0",
92 | "webpack-hot-middleware": "^2.25.0",
93 | "webpack-merge": "^4.2.1"
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/profiles/development/startalk.env:
--------------------------------------------------------------------------------
1 | // 项目启动端口
2 | PORT=5000
3 |
4 | // 项目启动 IP
5 | IP=0.0.0.0
6 |
7 | // 后台接口地址(例:http://127.0.0.1:8080)
8 | BASEURL=
9 |
10 | //后台导航地址(例:startalk_nav或newapi/nck/qtalk_nav.qunar)
11 | NAVIGATION=
12 |
13 | //公共路径
14 | PUBLICPATH=/
15 |
--------------------------------------------------------------------------------
/profiles/production/startalk.env:
--------------------------------------------------------------------------------
1 | // 项目启动端口
2 | PORT=5000
3 |
4 | // 项目启动 IP
5 | IP=0.0.0.0
6 |
7 | // 后台接口地址(例:http://127.0.0.1:8080)
8 | BASEURL=
9 |
10 | //后台导航地址(例:startalk_nav或newapi/nck/qtalk_nav.qunar)
11 | NAVIGATION=
12 |
13 | //公共路径
14 | PUBLICPATH=/
--------------------------------------------------------------------------------
/src/assets/README.md:
--------------------------------------------------------------------------------
1 | # assets
2 | 存放静态文件的目录,如图片、字体等,不存放代码类文件,如CSS、JavaScript
3 |
4 | ## 约定
5 | - 网页模版中对静态资源引用时使用绝对路径,如 `/images/packing-logo.png`
6 |
7 | ## js文件中如何使用图片、字体等静态资源
8 | 假设文件目录结构如下:
9 | ```
10 | ├── /hotel/
11 | │ └── /entries/
12 | │ └── /index.js
13 | └── /assets/
14 | └── /images/
15 | └── /logo.png
16 |
17 | ```
18 | 有两种方式能将静态资源引入JavaScript中:
19 |
20 | 1. 使用webpack的require机制(推荐)
21 | require或import时使用静态资源相对路径,有两种相对路径可用:
22 | - 静态文件相对于当前JavaScript文件的相对路径
23 |
24 | ```js
25 | // index.js
26 | import logo from '../../assets/images/logo.png';
27 | ```
28 | 当文件目录层级比较深时,这种方式书写较费劲
29 | - 静态文件相对于`assets`的相对路径
30 |
31 | ```js
32 | // index.js
33 | import logo from 'images/logo.png';
34 | ```
35 | 这种方式比较简洁
36 | 无论使用上述哪种方式引入的静态资源,使用时都必须使用绝对路径
37 |
38 | ```js
39 | // index.js
40 | import logo from '../../assets/images/logo.png';
41 | // import logo from 'images/logo.png';
42 | var a = new Image();
43 | a.src = `/${logo}`;
44 | ```
45 |
46 | 2. 手动拼资源的URL地址,获取到静态资源的uri地址 `process.env.CDN_ROOT`,从而手工拼接url,这种方式引入的静态资源不会做md5
47 |
48 | ```js
49 | // index.js
50 | var a = new Image();
51 | a.src = process.env.CDN_ROOT + '/images/logo.png';
52 | ```
53 |
--------------------------------------------------------------------------------
/src/assets/chat/17e02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/chat/17e02.png
--------------------------------------------------------------------------------
/src/assets/chat/38c02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/chat/38c02.png
--------------------------------------------------------------------------------
/src/assets/chat/4f302.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/chat/4f302.png
--------------------------------------------------------------------------------
/src/assets/chat/75702.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/chat/75702.png
--------------------------------------------------------------------------------
/src/assets/chat/80702.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/chat/80702.png
--------------------------------------------------------------------------------
/src/assets/chat/87702.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/chat/87702.png
--------------------------------------------------------------------------------
/src/assets/chat/cd902.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/chat/cd902.png
--------------------------------------------------------------------------------
/src/assets/chat/e8002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/chat/e8002.png
--------------------------------------------------------------------------------
/src/assets/chat/efb02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/chat/efb02.png
--------------------------------------------------------------------------------
/src/assets/chat/fd802.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/chat/fd802.png
--------------------------------------------------------------------------------
/src/assets/chat/packing-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/chat/packing-logo.png
--------------------------------------------------------------------------------
/src/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/favicon.ico
--------------------------------------------------------------------------------
/src/assets/footer/4cf2f7c2e63f4a02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/footer/4cf2f7c2e63f4a02.png
--------------------------------------------------------------------------------
/src/assets/footer/a3faf4373242d1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/footer/a3faf4373242d1.png
--------------------------------------------------------------------------------
/src/assets/footer/ff1a003aa731b0d4e2dd3d39687c8a54.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/footer/ff1a003aa731b0d4e2dd3d39687c8a54.png
--------------------------------------------------------------------------------
/src/assets/index/02d9a1cfacacfd02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/index/02d9a1cfacacfd02.png
--------------------------------------------------------------------------------
/src/assets/index/2fb5c91b6d9c4f02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/index/2fb5c91b6d9c4f02.png
--------------------------------------------------------------------------------
/src/assets/index/33bfeb065105c002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/index/33bfeb065105c002.png
--------------------------------------------------------------------------------
/src/assets/index/4d709e27fa18aa02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/index/4d709e27fa18aa02.png
--------------------------------------------------------------------------------
/src/assets/index/536fa0d98d69d102.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/index/536fa0d98d69d102.png
--------------------------------------------------------------------------------
/src/assets/index/6ea30b7cd9472a02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/index/6ea30b7cd9472a02.png
--------------------------------------------------------------------------------
/src/assets/index/9fd2391a3cf49702.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/index/9fd2391a3cf49702.png
--------------------------------------------------------------------------------
/src/assets/index/a4ae5891171faf02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/index/a4ae5891171faf02.png
--------------------------------------------------------------------------------
/src/assets/index/c463d610f0e32702.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/index/c463d610f0e32702.png
--------------------------------------------------------------------------------
/src/assets/index/f883b0fcd93f602.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/index/f883b0fcd93f602.png
--------------------------------------------------------------------------------
/src/assets/jstree/303b0c915e984e02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/jstree/303b0c915e984e02.png
--------------------------------------------------------------------------------
/src/assets/jstree/79273a9754b54002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/jstree/79273a9754b54002.png
--------------------------------------------------------------------------------
/src/assets/jstree/8bde275d8233602.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/assets/jstree/8bde275d8233602.gif
--------------------------------------------------------------------------------
/src/nodeuii/app.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: xi.guo
4 | * @Date: 2019-08-05 14:54:15
5 | * @LastEditTime: 2019-08-19 10:24:27
6 | * @LastEditors: Please set LastEditors
7 | */
8 | import Koa from 'koa'
9 | import compress from 'koa-compress'
10 | import views from 'koa-views'
11 | import serve from 'koa-static'
12 | import router from './routes/index'
13 | import path from 'path'
14 | import './dotenv'
15 | import log4js from 'log4js'
16 | import errorHandler from './middlewares/errorHandler.js'
17 | import proxyMiddleware from './middlewares/proxyMiddleware'
18 | import { getNowDate, insertStr } from './utils/formatter'
19 | import request from 'request'
20 |
21 | // development webpack-dev-middleware
22 | let webpack, webpackConfig, devMiddleware, hotMiddleware, compiler
23 | if (process.env.NODE_ENV === 'development') {
24 | webpack = require('webpack')
25 | const RawModule = require('webpack/lib/RawModule')
26 | webpackConfig = require('../webpack.config.js')
27 | devMiddleware = require('./middlewares/devMiddleware')
28 | hotMiddleware = require('./middlewares/hotMiddleware')
29 |
30 | compiler = webpack(webpackConfig)
31 |
32 | compiler.plugin('emit', (compilation, callback) => {
33 | const assets = compilation.assets
34 | let data
35 |
36 | Object.keys(assets).forEach(key => {
37 | if (key.match(/\.html$/)) {
38 | data = assets[key].source()
39 | data = data.replace('<%=navConfig%>', JSON.stringify(global.startalkNavConfig))
40 | data = data.replace('<%=keys%>', JSON.stringify(global.startalkKeys))
41 | assets[key] = (new RawModule(data)).source()
42 | }
43 | })
44 | callback()
45 | })
46 | }
47 |
48 | const app = new Koa()
49 | const { PORT, IP, BASEURL, NAVIGATION } = process.env
50 | global.startalkNavConfig = {}
51 | global.startalkKeys = {}
52 |
53 | request(`${BASEURL}/${NAVIGATION}`, (error, response, body) => {
54 | if (!error && response.statusCode == 200) {
55 | global.startalkNavConfig = JSON.parse(body)
56 |
57 | request(`${global.startalkNavConfig.baseaddess.javaurl}/qtapi/nck/rsa/get_public_key.do`, (error, response, body) => {
58 | if (!error && response.statusCode == 200) {
59 | global.startalkKeys = JSON.parse(body).data
60 | }
61 | })
62 | }
63 | })
64 |
65 | app.use(compress({
66 | threshold: 2048
67 | }))
68 |
69 | // development webpack-dev-middleware
70 | if (process.env.NODE_ENV === 'development') {
71 | app.use(devMiddleware(compiler, {
72 | publicPath: webpackConfig.output.publicPath,
73 | quiet: true
74 | }))
75 | app.use(hotMiddleware(compiler))
76 | }
77 |
78 | app.use(views(path.join(__dirname , './views'), {
79 | map: {html: 'ejs' }
80 | }))
81 | app.use(serve(path.join(__dirname , './assets')))
82 |
83 | // 错误日志处理
84 | log4js.configure({
85 | appenders: { cheese: { type: 'file', filename: path.join(__dirname, `logs/${getNowDate()}.log`) } },
86 | categories: { default: { appenders: ['cheese'], level: 'error' } }
87 | })
88 | const logger = log4js.getLogger('cheese')
89 |
90 | errorHandler.error(app, logger)
91 |
92 | // 代理
93 | app.use(proxyMiddleware())
94 |
95 | //路由
96 | app.use(router.routes())
97 |
98 | app.listen(PORT, IP, () => {
99 | console.log(`请访问端口:${PORT}`)
100 | })
--------------------------------------------------------------------------------
/src/nodeuii/middlewares/devMiddleware.js:
--------------------------------------------------------------------------------
1 | // 改造成koa中间件
2 | import webpackDev from 'webpack-dev-middleware'
3 |
4 | const devMiddleware = (compiler, opts) => {
5 | const middleware = webpackDev(compiler, opts)
6 | return async (ctx, next) => {
7 | await middleware(ctx.req, {
8 | end: (content) => {
9 | ctx.body = content
10 | },
11 | setHeader: (name, value) => {
12 | ctx.set(name, value)
13 | }
14 | }, next)
15 | }
16 | }
17 |
18 | module.exports = devMiddleware
--------------------------------------------------------------------------------
/src/nodeuii/middlewares/errorHandler.js:
--------------------------------------------------------------------------------
1 | const errorHandler = {
2 | error(app, logger){
3 | app.use(async (ctx, next) => {
4 | try {
5 | await next()
6 | } catch (error) {
7 | logger.error(error)
8 | ctx.status = error.status || 500
9 | ctx.body = "error page"
10 | }
11 | })
12 | app.use(async (ctx, next) => {
13 | await next()
14 | if(404 != ctx.status) return
15 | ctx.status = 404
16 | ctx.response.redirect('/')
17 | })
18 | }
19 | }
20 | //
21 | export default errorHandler
--------------------------------------------------------------------------------
/src/nodeuii/middlewares/hotMiddleware.js:
--------------------------------------------------------------------------------
1 | // 改造成koa中间件
2 | import webpackHot from 'webpack-hot-middleware'
3 | import stream from 'stream'
4 |
5 | const PassThrough = stream.PassThrough
6 |
7 | const hotMiddleware = (compiler, opts) => {
8 | const middleware = webpackHot(compiler, opts)
9 | return async (ctx, next) => {
10 | let stream = new PassThrough()
11 | ctx.body = stream
12 | await middleware(ctx.req, {
13 | write: stream.write.bind(stream),
14 | writeHead: (status, headers) => {
15 | ctx.status = status
16 | ctx.set(headers)
17 | }
18 | }, next)
19 | }
20 | }
21 |
22 |
23 | module.exports = hotMiddleware
--------------------------------------------------------------------------------
/src/nodeuii/middlewares/proxyMiddleware.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: xi.guo
4 | * @Date: 2019-08-05 14:54:15
5 | * @LastEditTime: 2019-08-13 14:33:20
6 | * @LastEditors: Please set LastEditors
7 | */
8 | import k2c from 'koa2-connect'
9 | import httpProxy from 'http-proxy-middleware'
10 | import url from 'url'
11 |
12 | const proxyMap = {
13 | '/package': {
14 | url: ['baseaddess', 'javaurl']
15 | },
16 | '/py/search': {
17 | url: ['ability', 'searchurl']
18 | },
19 | '/newapi': {
20 | url: ['baseaddess', 'httpurl']
21 | },
22 | '/api': {
23 | url: ['baseaddess', 'httpurl']
24 | },
25 | '/file': {
26 | url: ['baseaddess', 'fileurl']
27 | }
28 | }
29 |
30 | const getIn = (obj, arr = []) => {
31 | return arr.reduce((accumulator, currentValue) => {
32 | if (typeof accumulator === 'object') {
33 | return accumulator[currentValue]
34 | }
35 | }, obj)
36 | }
37 |
38 |
39 | const proxyMiddleware = () => {
40 | return async (ctx, next) => {
41 | for(var proxyKey in proxyMap) {
42 | if (ctx.url.startsWith(proxyKey)) {
43 | ctx.respond = false
44 | const { body } = ctx.request
45 | const contentType = ctx.request.header['content-type']
46 | const urlObj = url.parse(global.startalkNavConfig && getIn(global.startalkNavConfig, proxyMap[proxyKey].url))
47 | const defaultOpt = {}
48 |
49 | if (proxyMap[proxyKey].pathRewrite) {
50 | defaultOpt.pathRewrite = {
51 | pathRewrite: {
52 | [proxyMap.proxyKey.pathRewrite]: urlObj.pathname
53 | }
54 | }
55 | }
56 |
57 | await k2c(httpProxy(proxyKey, Object.assign({
58 | target: `${urlObj.protocol}//${urlObj.host}`,
59 | changeOrigin: true,
60 | onProxyReq: (proxyReq) => {
61 | if (body && contentType.indexOf('application/json') > -1) {
62 | const bodyData = JSON.stringify(body)
63 |
64 | proxyReq.setHeader('Content-Type', 'application/json')
65 | proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData))
66 | proxyReq.write(bodyData)
67 | }
68 | else if (body && contentType.indexOf('application/x-www-form-urlencoded') > -1) {
69 | const bodyData = Object.keys(body).map(key => `${key}=${body[key]}`).join('&')
70 |
71 | proxyReq.setHeader('Content-Type', 'application/x-www-form-urlencoded')
72 | proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData))
73 | proxyReq.write(bodyData)
74 | }
75 | }
76 | }, defaultOpt)))(ctx, next)
77 | }
78 | }
79 |
80 | await next()
81 | }
82 | }
83 |
84 | module.exports = proxyMiddleware
--------------------------------------------------------------------------------
/src/nodeuii/routes/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: your name
4 | * @Date: 2019-08-05 14:54:15
5 | * @LastEditTime: 2019-08-13 17:38:00
6 | * @LastEditors: Please set LastEditors
7 | */
8 | import Router from 'koa-router'
9 |
10 | const router = new Router()
11 |
12 |
13 | router.get('/reterievepassword', async (ctx, next) => {
14 | await ctx.render('reterievepassword')
15 | })
16 |
17 | router.get('/', async (ctx, next) => {
18 | const navConfig = JSON.stringify(global.startalkNavConfig)
19 | const keys = JSON.stringify(global.startalkKeys)
20 | await ctx.render('index', {
21 | navConfig,
22 | keys
23 | })
24 | })
25 |
26 | export default router
27 |
--------------------------------------------------------------------------------
/src/nodeuii/utils/formatter.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: your name
4 | * @Date: 2019-08-05 14:54:15
5 | * @LastEditTime: 2019-08-12 19:55:02
6 | * @LastEditors: Please set LastEditors
7 | */
8 | export const getNowDate = () => {
9 | const time = new Date()
10 | const year = time.getFullYear()
11 | const month = time.getMonth() + 1
12 | const day = time.getDate()
13 |
14 | return `${year}-${month}-${day}`
15 | }
16 |
17 | export const insertStr = (soure,start, newStr) => {
18 | return soure.slice(0, start) + newStr + soure.slice(start)
19 | }
20 |
--------------------------------------------------------------------------------
/src/web/app/common/components/message-box/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDom from 'react-dom';
3 | import Modal from '../modal';
4 |
5 | const noop = () => { };
6 |
7 | const contentWrapper = content => (
8 |
12 | );
13 |
14 | class MessageBox extends Component {
15 | constructor(props) {
16 | super(props);
17 | this.state = {
18 | show: false,
19 | title: null,
20 | content: null,
21 | isConfirm: false,
22 | okText: '确定',
23 | cancelText: '取消'
24 | };
25 | this.confirmCancel = noop;
26 | this.confirmOk = noop;
27 | }
28 |
29 | toggle = (show) => {
30 | this.setState({
31 | show
32 | });
33 | };
34 |
35 | cancel = () => {
36 | if (this.state.isConfirm) {
37 | this.confirmCancel();
38 | this.confirmCancel = noop;
39 | }
40 | };
41 |
42 | ok = () => {
43 | this.confirmOk();
44 | this.confirmOk = noop;
45 | };
46 |
47 | alert(content, title, okText = '确定') {
48 | this.setState({
49 | content: contentWrapper(content),
50 | isConfirm: false,
51 | show: true,
52 | title,
53 | okText
54 | });
55 | return {
56 | ok: (fn) => {
57 | this.confirmOk = fn;
58 | }
59 | };
60 | }
61 |
62 | confirm(content, title, okText = '确定', cancelText = '取消') {
63 | this.setState({
64 | content: contentWrapper(content),
65 | isConfirm: true,
66 | show: true,
67 | title,
68 | okText,
69 | cancelText
70 | });
71 | return this.confirmBind();
72 | }
73 |
74 | confirmBind() {
75 | const result = {
76 | ok: (fn) => {
77 | this.confirmOk = fn;
78 | return result;
79 | },
80 | cancel: (fn) => {
81 | this.confirmCancel = fn;
82 | return result;
83 | }
84 | };
85 | return result;
86 | }
87 |
88 | render() {
89 | const modalProps = {
90 | defaultFooter: true,
91 | show: this.state.show,
92 | onToggle: this.toggle,
93 | title: this.state.title,
94 | content: this.state.content,
95 | cancel: this.cancel,
96 | ok: this.ok,
97 | noCancel: !this.state.isConfirm,
98 | okText: this.state.okText,
99 | cancelText: this.state.cancelText
100 | };
101 | return (
102 |
103 | );
104 | }
105 | }
106 |
107 | const container = document.createElement('div');
108 | document.body.appendChild(container);
109 |
110 | export default ReactDom.render(
111 | ,
112 | container
113 | );
114 |
--------------------------------------------------------------------------------
/src/web/app/common/components/modal/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import cx from 'classnames';
3 | import './index.less';
4 |
5 | class Modal extends Component {
6 | onCancel = () => {
7 | const { cancel, hideAfterCancel, onToggle } = this.props;
8 | if (typeof cancel === 'function') {
9 | cancel();
10 | }
11 | if (hideAfterCancel) {
12 | onToggle(false);
13 | }
14 | };
15 |
16 | onOk = () => {
17 | const { ok, hideAfterOk, onToggle } = this.props;
18 | if (typeof ok === 'function') {
19 | ok();
20 | }
21 | if (hideAfterOk) {
22 | onToggle(false);
23 | }
24 | };
25 |
26 | hide = (e) => {
27 | if (e.target.classList.value.indexOf('modals') < 0) {
28 | return;
29 | }
30 | this.props.onToggle(false);
31 | };
32 |
33 | renderFooter() {
34 | if (this.props.footer) {
35 | return (
36 |
37 | {this.props.footer}
38 |
39 | );
40 | }
41 | if (this.props.defaultFooter) {
42 | return (
43 |
44 | {
45 | this.props.noCancel ? null : (
46 |
47 | {this.props.cancelText}
48 |
49 | )
50 | }
51 | {
52 | this.props.noOk ? null : (
53 |
54 | {this.props.okText}
55 |
56 | )
57 | }
58 |
59 | );
60 | }
61 | return null;
62 | }
63 |
64 | renderBody() {
65 | const { title, content } = this.props;
66 | return (
67 |
68 | {
69 | title ? (
70 |
71 | {title}
72 |
73 | ) : null
74 | }
75 |
76 | {content}
77 |
78 | {this.renderFooter()}
79 |
80 | );
81 | }
82 |
83 | render() {
84 | const { show, className, children } = this.props;
85 | return (
86 |
87 |
{
90 | if (dom && dom.style) {
91 | // 先 top:0, left: 0 触发渲染,计算宽度
92 | dom.style.cssText = 'top: 0; left: 0';
93 | dom.style.cssText = `top: 50%; left: 50%; margin: -${Math.floor(dom.offsetHeight / 2)}px 0 0 -${Math.floor(dom.offsetWidth / 2)}px`;
94 | }
95 | }}
96 | >
97 | {children || this.renderBody()}
98 |
99 |
100 | );
101 | }
102 | }
103 |
104 | Modal.defaultProps = {
105 | className: '',
106 | show: false,
107 | hideAfterCancel: true,
108 | hideAfterOk: true,
109 | defaultFooter: true,
110 | noCancel: false,
111 | noOk: false,
112 | okText: '确定',
113 | cancelText: '取消'
114 | };
115 |
116 | export default Modal;
117 |
--------------------------------------------------------------------------------
/src/web/app/common/components/modal/index.less:
--------------------------------------------------------------------------------
1 | .modals {
2 | position: fixed;
3 | top: 0;
4 | right: 0;
5 | bottom: 0;
6 | left: 0;
7 | width: 100%;
8 | height: 100%;
9 | background: rgba(0,0,0, 0.5);
10 | z-index: 100;
11 | text-align: left;
12 | &.hide {
13 | display: none;
14 | }
15 | .modal {
16 | margin: 0 auto;
17 | border-radius: 5px;
18 | background: #fff;
19 | min-width: 300px;
20 | // max-width: 90vw;
21 | position: fixed;
22 | overflow: hidden;
23 | .title {
24 | line-height: 50px;
25 | background: #f9f9f9;
26 | position: relative;
27 | padding-left: 15px;
28 | .close {
29 | position: absolute;
30 | right: 16px;
31 | top: 14px;
32 | }
33 | }
34 | .content {
35 | text-align: left;
36 | padding: 10px;
37 | }
38 | .footer {
39 | border-top: 1px solid #f1f1f1;
40 | padding: 15px;
41 | text-align: right;
42 | .btn {
43 | margin-left: 15px;
44 | &:first-child {
45 | margin-left: 0;
46 | }
47 | }
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/src/web/app/common/components/select2-one/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import cx from 'classnames';
3 | import './index.less';
4 |
5 | class SelectOne extends Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | show: false,
10 | inputValue: '',
11 | renderData: []
12 | };
13 | }
14 |
15 | componentWillReceiveProps(nextProps) {
16 | if (nextProps.show !== this.props.show) {
17 | this.setState({ show: nextProps.show });
18 | }
19 | if (nextProps.value !== this.props.value) {
20 | this.setState({
21 | inputValue: nextProps.value[nextProps.mapKey.text]
22 | });
23 | }
24 | this.setState({ renderData: this.props.data });
25 | }
26 |
27 | onChange(val) {
28 | let renderData = this.props.data.filter(item =>
29 | item[this.props.mapKey.text].indexOf(val) > -1);
30 | if (!val) {
31 | renderData = this.props.data;
32 | }
33 | this.setState({
34 | inputValue: val,
35 | renderData
36 | });
37 | this.props.onChange(val);
38 | }
39 |
40 | onClick(val) {
41 | this.setState({
42 | inputValue: val[this.props.mapKey.text]
43 | });
44 | this.props.onSelect(val);
45 | }
46 |
47 | render() {
48 | const { mapKey, value } = this.props;
49 | return (
50 |
51 |
52 |
this.setState({ show: true })}
56 | onBlur={() => setTimeout(() => this.setState({ show: false }), 200)}
57 | value={this.state.inputValue}
58 | onChange={e => this.onChange(e.target.value.trim())}
59 | />
60 |
61 | {
62 | this.state.renderData.map(item => (
63 | - this.onClick(item)}
67 | >
68 | {item[mapKey.text]}
69 |
70 | ))
71 | }
72 |
73 |
74 | );
75 | }
76 | }
77 |
78 | SelectOne.defaultProps = {
79 | className: '',
80 | show: false,
81 | data: [],
82 | onChange: () => {},
83 | onSelect: () => {},
84 | value: {},
85 | mapKey: { text: 'text', value: 'value' }
86 | };
87 |
88 | export default SelectOne;
89 |
--------------------------------------------------------------------------------
/src/web/app/common/components/select2-one/index.less:
--------------------------------------------------------------------------------
1 | .select {
2 | cursor: pointer;
3 | word-wrap: break-word;
4 | position: relative;
5 | line-height: 1em;
6 | white-space: normal;
7 | outline: 0;
8 | display: inline-block;
9 | color: rgba(0,0,0,.87);
10 | box-shadow: none;
11 | padding: 5px 0;
12 | transition: box-shadow .1s ease, width .1s ease, -webkit-box-shadow .1s ease;
13 | text-align: left;
14 | .icon {
15 | position: absolute;
16 | right: 10px;
17 | top: 10px;
18 | cursor: pointer;
19 | }
20 | .s-search {
21 | border: none;
22 | border-bottom: 1px solid rgba(34,36,38,.15);
23 | }
24 | .s-list {
25 | width: 100%;
26 | position: absolute;
27 | top: 25px;
28 | z-index: 3;
29 | background: #fff;
30 | &.hide {
31 | display: none;
32 | }
33 | &-item {
34 | padding: 5px 10px;
35 | width: auto;
36 | &.active {
37 | background: #07b5e9;
38 | color: #fff;
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/web/app/common/styles/icon.less:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'iconfont';
3 | src: url('//s.qunarzz.com/qtalk-web/0.0.8/qtalk_web.eot'); /* IE9*/
4 | src: url('//s.qunarzz.com/qtalk-web/0.0.8/qtalk_web.woff') format('woff'), /* chrome、firefox */
5 | url('//s.qunarzz.com/qtalk-web/0.0.8/qtalk_web.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/
6 | url('//s.qunarzz.com/qtalk-web/0.0.8/qtalk_web.svg#iconfont') format('svg'); /* iOS 4.1- */
7 | }
8 | // ../../../../assets/images/
9 | .icon {
10 | background: url(../../../../assets/index/c463d610f0e32702.png) no-repeat;
11 | background-size: 487px 462px;
12 | display: inline-block;
13 | vertical-align: middle;
14 |
15 | // 全局
16 | &.close {
17 | background-position: -150px -432px;
18 | width: 25px;
19 | height: 25px;
20 | }
21 |
22 | &.close-1 {
23 | background-position: -249px -436px;
24 | width: 14px;
25 | height: 14px;
26 | }
27 |
28 | &.sex-1, &.sex-2 {
29 | width: 16px;
30 | height: 16px;
31 | background-position: -384px -304px;
32 | }
33 | &.sex-2 {
34 | background-position: -368px -304px;
35 | }
36 |
37 | &.checked {
38 | background-position: -331px -432px;
39 | width: 20px;
40 | height: 20px;
41 | &.on {
42 | background-position: -467px -330px;
43 | }
44 | }
45 |
46 | &.arrow-down {
47 | background-position: -477px -65px;
48 | width: 10px;
49 | height: 10px;
50 | }
51 |
52 | &.arrow-up {
53 | background-position: -477px -55px;
54 | width: 10px;
55 | height: 10px;
56 | }
57 |
58 | // #panel
59 | &.panel-menu {
60 | width: 30px;
61 | height: 30px;
62 | background-position: -434px -398px;
63 | }
64 |
65 | &.panel-search {
66 | position: absolute;
67 | top: 1px;
68 | width: 30px;
69 | height: 30px;
70 | background-position: -60px -432px;
71 | }
72 |
73 | &.panel-chat {
74 | width: 35px;
75 | height: 35px;
76 | background-position: -150px -96px;
77 |
78 | &.active {
79 | background-position: -185px -96px;
80 | }
81 | }
82 |
83 | &.panel-friends {
84 | width: 35px;
85 | height: 35px;
86 | background-position: -220px -96px;
87 |
88 | &.active {
89 | background-position: -304px -246px;
90 | }
91 | }
92 |
93 | &.panel-reddot {
94 | position: absolute;
95 | top: -6px;
96 | right: -6px;
97 | color: #fff;
98 | font-style: normal;
99 | font-size: 12px;
100 | text-align: center;
101 | background-position: -451px -380px;
102 | width: 22px;
103 | height: 16px;
104 | }
105 |
106 | // #chat
107 | &.chat-header-members-add {
108 | background-position: -422px -55px;
109 | width: 55px;
110 | height: 55px;
111 | }
112 |
113 | &.chat-message-empty-logo {
114 | background-position: -96px -150px;
115 | width: 100px;
116 | height: 94px;
117 | opacity: 0.5;
118 | }
119 |
120 | &.chat-footer-face {
121 | background-position: -404px -398px;
122 | width: 30px;
123 | height: 30px;
124 | }
125 |
126 | &.chat-footer-file {
127 | background-position: -120px -432px;
128 | width: 30px;
129 | height: 30px;
130 | }
131 |
132 | &.add-user-search {
133 | background-position: -90px -432px;
134 | width: 30px;
135 | height: 30px;
136 | }
137 | &.owner {
138 | background-position: -372px -433px;
139 | width: 15px;
140 | height: 24px;
141 | }
142 | &.admin {
143 | background-position: -357px -433px;
144 | width: 15px;
145 | height: 24px;
146 | }
147 | &.arrow-d {
148 | background-position: -393px -440px;
149 | width: 10px;
150 | height: 10px;
151 | }
152 | &.arrow-r {
153 | background-position: -407px -440px;
154 | width: 10px;
155 | height: 10px;
156 | }
157 | &.card-chat {
158 | width: 22px;
159 | height: 22px;
160 | background-position: -223px -432px;
161 | }
162 | &.male {
163 | width: 24px;
164 | height: 24px;
165 | margin-left: 5px;
166 | background-position: -246px -356px;
167 | }
168 | &.female {
169 | width: 24px;
170 | height: 24px;
171 | margin-left: 5px;
172 | background-position: -246px -380px;
173 | }
174 | &.people3 {
175 | width: 32px;
176 | height: 24px;
177 | margin-right: 5px;
178 | background-position: -243px -333px;
179 | }
180 | }
181 | .iconfont{
182 | font-family: "iconfont" !important;
183 | font-size: 20px;
184 | font-style: normal;
185 | -webkit-font-smoothing: antialiased;
186 | -webkit-text-stroke-width: 0.2px;
187 | -moz-osx-font-smoothing: grayscale;
188 |
189 | &.small {
190 | font-size: 16px;
191 | }
192 | &.large {
193 | font-size: 24px;
194 | }
195 | &.x-large {
196 | font-size: 28px;
197 | }
198 | &.huge {
199 | font-size: 32px;
200 | }
201 |
202 | &.camel:before {
203 | content: '\f3eb';
204 | }
205 | &.double-arrow-up:before {
206 | content: '\e044';
207 | }
208 | &.double-arrow-down:before {
209 | content: '\e043';
210 | }
211 | &.nike:before {
212 | content: '\f3f6';
213 | }
214 | &.nike2:before {
215 | content: '\f472';
216 | }
217 | &.circle:before {
218 | content: '\f438';
219 | }
220 | &.plus:before {
221 | content: '\f016';
222 | }
223 | &.more:before {
224 | content: '\e1ba';
225 | }
226 | &.search:before {
227 | content: '\f3b9';
228 | }
229 | &.message:before {
230 | content: '\f3e4';
231 | }
232 | &.message-empty:before {
233 | content: '\f0f3';
234 | }
235 | &.card:before {
236 | content: '\f4ce';
237 | }
238 | &.card-empty:before {
239 | content: '\f0de';
240 | }
241 | &.logout:before {
242 | content: '\f1bb';
243 | }
244 | &.people:before {
245 | content: '\f4c6';
246 | }
247 | &.smile:before {
248 | content: '\f3be';
249 | }
250 | &.folder:before {
251 | content: '\e211';
252 | }
253 | &.people3:before {
254 | content: '\f10f';
255 | }
256 | &.arrow-up:before {
257 | content: '\f3ca';
258 | }
259 | &.arrow-down:before {
260 | content: '\f3cb';
261 | }
262 | &.arrow-left:before {
263 | content: '\f3cd';
264 | }
265 | &.arrow-right:before {
266 | content: '\f3cc';
267 | }
268 | &.mail:before {
269 | content: '\f493';
270 | }
271 | &.female:before {
272 | content: '\f478';
273 | }
274 | &.male:before {
275 | content: '\f47c';
276 | }
277 | &.edit:before {
278 | content: '\f4cb';
279 | }
280 | &.empty:before {
281 | content: '\f0f4';
282 | }
283 | &.clock:before {
284 | content: '\e158';
285 | }
286 | &.tebu:before {
287 | content: '\f093';
288 | }
289 | &.minus:before {
290 | content: '\f436';
291 | }
292 | }
293 |
--------------------------------------------------------------------------------
/src/web/app/common/styles/index.less:
--------------------------------------------------------------------------------
1 | body,
2 | html {
3 | height: 100%;
4 | }
5 | // ../../../../assets/index/
6 | body {
7 | font-family: Helvetica Neue, Helvetica, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
8 | background: url(../../../../assets/index/02d9a1cfacacfd02.png) no-repeat 50%;
9 | background-size: cover;
10 | }
11 |
12 | a.btn {
13 | text-decoration: none;
14 | }
15 |
16 | .red {
17 | color: red;
18 | }
19 |
20 | .btn {
21 | display: inline-block;
22 | border: 1px solid #c1c1c1;
23 | border-radius: 4px;
24 | padding: 3px 20px;
25 | font-size: 14px;
26 | cursor: pointer;
27 | }
28 |
29 | .btn-default {
30 | background-color: #c9c9c9;
31 | cursor: default;
32 | }
33 |
34 | .btn-default,
35 | .btn-primary {
36 | color: #fff;
37 | border: 1px solid #fff;
38 | }
39 |
40 | .btn-primary {
41 | background: #3caf36;
42 | border: 1px solid #3caf36;
43 | }
44 |
45 | .btn-smart {
46 | border-radius: 20px;
47 | }
48 |
49 | .btn-send {
50 | border: 0;
51 | background-color: #45CF8E;
52 | color: #fff;
53 | padding-left: 20px;
54 | padding-right: 20px;
55 | }
56 |
57 | .btn-send:hover {
58 | opacity:0.5;
59 | }
60 |
61 | .clearfix::after {
62 | content: " ";
63 | visibility: hidden;
64 | display: block;
65 | height: 0;
66 | clear: both;
67 | }
68 |
69 | #app {
70 | position: relative;
71 | top: 50%;
72 | margin-top: -300px;
73 | }
74 |
75 | #main {
76 | width: 976px;
77 | height: 580px;
78 | margin: 0 auto;
79 | border-radius: 3px;
80 | overflow: hidden;
81 | position: relative;
82 | border: 1px solid #e6e6e6;
83 | }
84 |
85 | #phone_main {
86 | width: 100vw;
87 | height: 100vh;
88 | margin: 0 auto;
89 | overflow: hidden;
90 | position: fixed;
91 | top: 0px;
92 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
93 | }
94 |
95 | @media (max-height: 813px) {
96 | #app {
97 | position: static;
98 | top: 0;
99 | margin-top: 0;
100 | }
101 | }
102 |
103 | .bg {
104 | &.b0 {
105 | background-image: url(../../../../assets/index/33bfeb065105c002.png);
106 | }
107 | &.b1 {
108 | background-image: url(../../../../assets/index/536fa0d98d69d102.png);
109 | }
110 | &.b2 {
111 | background-image: url(../../../../assets/index/2fb5c91b6d9c4f02.png);
112 | }
113 | &.b3 {
114 | background-image: url(../../../../assets/index/6ea30b7cd9472a02.png);
115 | }
116 | &.b4 {
117 | background-image: url(../../../../assets/index/a4ae5891171faf02.png);
118 | }
119 | &.b5 {
120 | background-image: url(../../../../assets/index/f883b0fcd93f602.png);
121 | }
122 | &.b6 {
123 | background-image: url(../../../../assets/index/9fd2391a3cf49702.png);
124 | }
125 | &.b7 {
126 | background-image: url(../../../../assets/index/4d709e27fa18aa02.png);
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/web/app/common/styles/login.less:
--------------------------------------------------------------------------------
1 | @mainColor: #45CF8E;
2 |
3 | .login-wrap {
4 | position: fixed;
5 | top: 50%;
6 | left: 50%;
7 | width: 304px;
8 | margin: -200px 0 0 -152px;
9 | border-radius: 4px;
10 | background: #fff;
11 | .login {
12 | width: 304px;
13 | height: 360px;
14 | border-radius: 4px;
15 | background: #fff;
16 | box-shadow: 0 4px 5px 0 rgba(0,0,0,0.20);
17 | text-align: center;
18 | padding-top: 40px;
19 | position: relative;
20 | overflow: hidden;
21 |
22 | .status {
23 | padding-top: 10px;
24 | font-size: 12px;
25 | color: #616161;
26 | }
27 | .cancel {
28 | width: 208px;
29 | height: 36px;
30 | line-height: 36px;
31 | margin: 26px auto 0 auto;
32 | border: 1px solid #E6E6E6;
33 | border-radius: 2px;
34 | font-size: 16px;
35 | color: #616161;
36 | cursor: pointer;
37 | }
38 | .avatar {
39 | width: 100px;
40 | height: 100px;
41 | border-radius: 50%;
42 | margin: auto;
43 | position: relative;
44 | &.green {
45 | background: @mainColor;
46 | }
47 | .iconfont.camel {
48 | font-size: 60px;
49 | position: absolute;
50 | top: 50%;
51 | left: 50%;
52 | margin-left: -30px;
53 | margin-top: -48px;
54 | color: #fff;
55 | }
56 | img {
57 | width: 100px;
58 | height: 100px;
59 | border-radius: 50%;
60 | }
61 | }
62 | .form {
63 | margin-top: 30px;
64 | .account-info {
65 | width: 208px;
66 | margin: 0 auto;
67 | border: 1px solid #E6E6E6;
68 | border-radius: 2px;
69 | .item {
70 | background: rgba(255, 255, 255, 0.3);
71 | width: 208px;
72 | margin: 0 auto;
73 |
74 | &.bb {
75 | border-bottom: 1px solid #E6E6E6;
76 | }
77 | .input {
78 | width: 208px;
79 | height: 36px;
80 | line-height: 36px;
81 | border: 0;
82 | font-size: 14px;
83 | color: #333;
84 | background: none;
85 | }
86 | .domain {
87 | width: 80px;
88 | height: 36px;
89 | line-height: 36px;
90 | color: #BDBDBD;
91 | font-size: 14px;
92 | float: right;
93 | margin-right: 10px;
94 | overflow: hidden;
95 | text-overflow: ellipsis;
96 | white-space: nowrap;
97 | word-wrap: normal;
98 | }
99 | }
100 | }
101 |
102 | .btn-login {
103 | border-radius: 2px;
104 | display: block;
105 | width: 208px;
106 | background: @mainColor;
107 | height: 36px;
108 | line-height: 36px;
109 | margin: 16px auto 0 auto;
110 | color: #fff;
111 | font-size: 16px;
112 | cursor: pointer;
113 |
114 | &.disabled {
115 | background: #9c9c9c;
116 | color: #d4d4d4;
117 | cursor: default;
118 | }
119 | }
120 |
121 | }
122 | .domain-choose {
123 | position: absolute;
124 | bottom: 16px;
125 | left: 50%;
126 | margin-left: -9px;
127 | cursor: pointer;
128 | .iconfont.double-arrow-down{
129 | font-size: 18px;
130 | color: #9E9E9E;
131 | }
132 | .iconfont.double-arrow-up{
133 | font-size: 18px;
134 | color: #9E9E9E;
135 | }
136 | }
137 | }
138 | .domain-block {
139 | padding: 17px 0 43px 48px;
140 | font-size: 12px;
141 | .domain-list {
142 | max-height: 60px;
143 | overflow-y: scroll;
144 | .item {
145 | margin-bottom: 7px;
146 | cursor: pointer;
147 | .iconfont.circle {
148 | display: inline-block;
149 | font-size: 16px;
150 | color: #9E9E9E;
151 | vertical-align: middle;
152 | }
153 | .iconfont.nike {
154 | display: inline-block;
155 | font-size: 16px;
156 | color: @mainColor;
157 | vertical-align: middle;
158 | }
159 | span {
160 | display: inline-block;
161 | margin-left: 10px;
162 | height: 16px;
163 | }
164 |
165 | .input {
166 | position: relative;
167 | display: inline-block;
168 | width: 148px;
169 | .check {
170 | position: absolute;
171 | right: 0;
172 | top: 0;
173 | width: 26px;
174 | height: 26px;
175 | background: #E6E6E6;
176 | border-radius: 2px;
177 | &.right {
178 | background: @mainColor;
179 | }
180 | .iconfont.nike2 {
181 | font-size: 14px;
182 | color: #fff;
183 | position: absolute;
184 | top: 50%;
185 | left: 50%;
186 | margin-left: -7px;
187 | margin-top: -11px;
188 | }
189 | }
190 | input {
191 | width: 107px;
192 | height: 24px;
193 | border: 1px solid #E6E6E6;
194 | border-radius: 2px;
195 | padding-left: 5px;
196 | margin-left: 10px;
197 | }
198 | }
199 |
200 | }
201 | }
202 | .domain-add-btn {
203 | width: 24px;
204 | padding-left: 17px;
205 | position: absolute;
206 | cursor: pointer;
207 | right: 48px;
208 | bottom: 16px;
209 | font-size: 12px;
210 | height: 22px;
211 | line-height: 22px;
212 | color: #39B87C;
213 | .iconfont.plus {
214 | font-size: 14px;
215 | color: #39B87C;
216 | position: absolute;
217 | left: 0;
218 | }
219 | }
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/src/web/app/common/styles/reset.less:
--------------------------------------------------------------------------------
1 | html {
2 | -ms-text-size-adjust: 100%;
3 | -webkit-text-size-adjust: 100%;
4 | }
5 |
6 | body {
7 | -webkit-font-smoothing: antialiased;
8 | line-height: 1.6;
9 | }
10 |
11 | a,
12 | button,
13 | input,
14 | textarea {
15 | outline: 0;
16 | }
17 |
18 | body,
19 | dd,
20 | dl,
21 | fieldset,
22 | h1,
23 | h2,
24 | h3,
25 | h4,
26 | h5,
27 | h6,
28 | ol,
29 | p,
30 | textarea,
31 | ul {
32 | margin: 0;
33 | }
34 |
35 | fieldset,
36 | input,
37 | legend,
38 | textarea {
39 | padding: 0;
40 | }
41 |
42 | ol,
43 | ul {
44 | padding-left: 0;
45 | list-style-type: none;
46 | }
47 |
48 | a img,
49 | fieldset {
50 | border: 0;
51 | }
52 |
53 | article,
54 | aside,
55 | details,
56 | figcaption,
57 | figure,
58 | footer,
59 | header,
60 | hgroup,
61 | main,
62 | nav,
63 | section,
64 | summary {
65 | display: block;
66 | }
67 |
68 | audio,
69 | canvas,
70 | video {
71 | display: inline-block;
72 | }
73 |
74 | audio:not([controls]) {
75 | display: none;
76 | height: 0;
77 | }
78 |
79 | [hidden] {
80 | display: none;
81 | }
82 |
83 | svg:not(:root) {
84 | overflow: hidden;
85 | }
86 |
87 | figure {
88 | margin: 0;
89 | }
90 |
91 | button,
92 | input,
93 | select,
94 | textarea {
95 | font-family: inherit;
96 | font-size: 100%;
97 | margin: 0;
98 | }
99 |
100 | button,
101 | select {
102 | text-transform: none;
103 | }
104 |
105 | button,
106 | html input[type=button],
107 | input[type=reset],
108 | input[type=submit] {
109 | cursor: pointer;
110 | -webkit-appearance: button;
111 | }
112 |
113 | button[disabled],
114 | html input[disabled] {
115 | cursor: default;
116 | }
117 |
118 | input[type=checkbox],
119 | input[type=radio] {
120 | box-sizing: border-box;
121 | padding: 0;
122 | }
123 |
124 | input[type=search] {
125 | box-sizing: content-box;
126 | -moz-box-sizing: content-box;
127 | -webkit-appearance: textfield;
128 | -webkit-box-sizing: content-box;
129 | }
130 |
131 | // input[type=search]::-webkit-search-cancel-button,
132 | // input[type=search]::-webkit-search-decoration {
133 | // -webkit-appearance: none;
134 | // }
135 |
136 | button::-moz-focus-inner,
137 | input::-moz-focus-inner {
138 | border: 0;
139 | padding: 0;
140 | }
141 |
142 | textarea {
143 | overflow: auto;
144 | vertical-align: top;
145 | resize: none;
146 | }
147 |
148 | input:-webkit-autofill,
149 | select:-webkit-autofill,
150 | textarea:-webkit-autofill {
151 | box-shadow: inset 0 0 0 1000px #fff;
152 | -moz-box-shadow: inset 0 0 0 1000px #fff;
153 | -webkit-box-shadow: inset 0 0 0 1000px #fff;
154 | }
155 |
156 | select {
157 | border-radius: 0;
158 | -webkit-border-radius: 0;
159 | }
160 |
--------------------------------------------------------------------------------
/src/web/app/common/utils/namespace-actions.js:
--------------------------------------------------------------------------------
1 | import { createActions } from 'redux-actions';
2 |
3 | function appendNs(namespace, ...args) {
4 | const actions = createActions.apply(this, args);
5 | Object.keys(actions).forEach((key) => {
6 | const fn = actions[key];
7 | actions[key] = (...actionArgs) => {
8 | const action = fn.apply(this, actionArgs);
9 | action.type = `${namespace}/${action.type}`;
10 | return action;
11 | };
12 | });
13 | args.forEach((name) => {
14 | actions[name] = `${namespace}/${name}`;
15 | });
16 | return actions;
17 | }
18 |
19 | export default namespace => (...args) => (appendNs(namespace, ...args));
20 |
--------------------------------------------------------------------------------
/src/web/app/common/utils/namespace-reducers.js:
--------------------------------------------------------------------------------
1 | import { handleActions } from 'redux-actions';
2 |
3 | function appendNs(namespace, reducerMap, initialState) {
4 | const reducerMapWithNs = Object.keys(reducerMap).reduce((result, key) => {
5 | const reducer = reducerMap[key];
6 | result[`${namespace}/${key}`] = reducer;
7 | return result;
8 | }, {});
9 | return handleActions(reducerMapWithNs, initialState);
10 | }
11 |
12 | export default namespace => (...args) => (appendNs(namespace, ...args));
13 |
--------------------------------------------------------------------------------
/src/web/app/common/utils/router.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Prompt,
4 | Link,
5 | BrowserRouter,
6 | HashRouter,
7 | Route
8 | } from 'react-router-dom';
9 |
10 | const MyRoute = ({
11 | component: Component,
12 | exact,
13 | path,
14 | strict,
15 | ...rest
16 | }) => (
17 | ()
21 | }
22 | />
23 | );
24 |
25 | export default {
26 | Prompt,
27 | Link,
28 | BrowserRouter,
29 | HashRouter,
30 | Route: MyRoute
31 | };
32 |
--------------------------------------------------------------------------------
/src/web/app/index.html:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | startalk
16 |
17 |
18 |
19 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/actions.js:
--------------------------------------------------------------------------------
1 | import nsActions from '../../common/utils/namespace-actions';
2 |
3 | const createActions = nsActions('INDEX');
4 |
5 | export default createActions(
6 | 'CHANGE_CHAT_FIELD',
7 | 'SET_CHAT_FIELD',
8 | 'SET_USER_INFO',
9 | // 会话
10 | 'SET_SESSION_LIST',
11 | 'SET_SESSION_TOP',
12 | 'MOVE_SESSION',
13 | 'CLEAR_SESSION_CNT',
14 | 'SET_LAST_SESSION_MESSAGE',
15 | 'SET_CURRENT_SESSION_USERS',
16 | 'REMOVE_CURRENT_SESSION_USER',
17 | // 'MERGE_CURRENT_SESSION_USER',
18 | 'SET_CURRENT_SESSION',
19 | 'REMOVE_SESSION',
20 | // 右键菜单
21 | 'SET_CONTENT_MENU',
22 | // 消息
23 | 'SET_MESSAGE',
24 | 'APPEND_MESSAGE',
25 | 'CLEAR_MESSAGE',
26 | 'SET_MESSAGE_READ',
27 | 'REVOKE_MESSAGE',
28 | // 卡片弹层信息
29 | 'SET_MODAL_USER_CARD',
30 | 'SET_MODAL_GROUP_CARD',
31 | // 通讯录
32 | 'SET_FRIENDS_MUCS',
33 | 'SET_FRIENDS_USERS',
34 | // 发起会话
35 | 'SET_MEMBERS_INFO',
36 | // 导航
37 | 'SET_STARTALK_NAV',
38 | 'SET_PUBLIC_KEY'
39 | );
40 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/consts.js:
--------------------------------------------------------------------------------
1 | const atTips = {
2 | single: '你被@了一次',
3 | all: '@全体成员'
4 | };
5 |
6 | const treeKey = 'struct';
7 | export {
8 | atTips,
9 | treeKey
10 | };
11 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/entry.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | // import { createStore, applyMiddleware } from 'redux';
5 | import { createStore } from 'redux';
6 | // import createSagaMiddleware from 'redux-saga';
7 | import reducer from './reducer';
8 | // import saga from './saga';
9 | import Page from './';
10 | import './import-less';
11 |
12 | const root = document.getElementById('app');
13 | // const sagaMiddleware = createSagaMiddleware();
14 | // const store = createStore(reducer, applyMiddleware(sagaMiddleware));
15 | const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
16 | // sagaMiddleware.run(saga);
17 |
18 | const render = (Entry) => {
19 | ReactDOM.render(
20 |
21 |
22 | ,
23 | root
24 | );
25 | };
26 |
27 | render(Page);
28 |
29 | if (module.hot) {
30 | module.hot.accept(['.'], () => {
31 | eslint-disable-next-line
32 | render(require('.'));
33 | });
34 | }
35 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/entry.settings.js:
--------------------------------------------------------------------------------
1 | const { QTALK_SDK_URL } = process.env;
2 | export default {
3 | QTALK_SDK_URL
4 | };
5 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/import-less.js:
--------------------------------------------------------------------------------
1 | import '../../common/styles/reset.less';
2 | import '../../common/styles/icon.less';
3 | import '../../common/styles/animate.less';
4 |
5 | import '../../common/styles/index.less';
6 | import '../../common/styles/panel.less';
7 | import '../../common/styles/chat.less';
8 | import '../../common/styles/login.less';
9 | import '../../common/styles/modals.less';
10 | import '../../common/styles/jstree.css';
11 |
12 | import '../../common/styles/phone/panel.less';
13 | import '../../common/styles/phone/modals.less';
14 | import '../../common/styles/phone/chat.less';
15 |
16 | const styleContent = `
17 | ::-webkit-scrollbar{
18 | width: 6px;
19 | background-color: #F5F5F5;
20 | }
21 | ::-webkit-scrollbar-thumb{
22 | background-color: rgba(50,50,50,.3);
23 | }
24 | ::-webkit-scrollbar-track{
25 | background-color: rgba(50,50,50,.5);
26 | }
27 | `;
28 |
29 | const doc = window.document;
30 | const platform = window.navigator.platform.toLowerCase();
31 | if (platform.indexOf('mac') === -1) {
32 | if (doc.all) {
33 | window.qtalkWebStyle = styleContent;
34 | // eslint-disable-next-line
35 | doc.createStyleSheet('javascript:qtalkWebStyle');
36 | } else {
37 | const style = doc.createElement('style');
38 | style.type = 'text/css';
39 | style.innerHTML = styleContent;
40 | doc.getElementsByTagName('HEAD').item(0).appendChild(style);
41 | }
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: your name
4 | * @Date: 2019-08-06 11:24:02
5 | * @LastEditTime: 2019-08-20 17:25:02
6 | * @LastEditors: chaos.dong
7 | */
8 | import React, { Component } from 'react';
9 | import { connect } from 'react-redux';
10 | import axios from 'axios';
11 | // import moment from 'moment';
12 | import actions from './actions';
13 | import Login from './ui/login';
14 | import Chat from './ui/chat';
15 | import Panel from './ui/panel';
16 | import UserCard from './ui/modal/userCard';
17 | import GroupCard from './ui/modal/groupCard';
18 | import ContentMenu from './ui/modal/contentmenu';
19 | import Members from './ui/modal/members';
20 |
21 | import PhonePanel from './phone-ui/panel';
22 | import PhoneUserCard from './phone-ui/modal/userCard';
23 | import PhoneGroupCard from './phone-ui/modal/groupCard';
24 | import PhoneMembers from './phone-ui/modal/members';
25 | import PhoneChat from './phone-ui/chat';
26 | import PhoneContentMenu from './phone-ui/modal/contentmenu';
27 |
28 | import { treeKey } from './consts';
29 | import sdk from './sdk';
30 |
31 | const webConfig = {
32 | fileurl: startalkNav.baseaddess && startalkNav.baseaddess.fileurl
33 | }
34 | const users = {};
35 | const usersName = {};
36 | const bu = [];
37 |
38 | @connect(
39 | state => ({
40 | connectStatus: state.getIn(['chat', 'connectStatus']),
41 | nav: state.getIn(['nav'])
42 | }),
43 | actions
44 | )
45 | export default class Page extends Component {
46 | constructor(props) {
47 | super(props)
48 | }
49 |
50 | componentDidMount() {
51 | sdk.ready(async () => {
52 | const res = await sdk.getCompanyStruct();
53 | if (res.ret) {
54 | const treeData = this.createTree(res.data);
55 | // res.data 处理成jstree结构
56 | this.props.setChatField({
57 | companyStruct: this.genTreeData(treeData || [], treeKey),
58 | companyUsers: users,
59 | companyUsersName: usersName
60 | });
61 | }
62 | });
63 | }
64 |
65 | // 把平级数组转化为树状结构
66 | createTree(data) {
67 | // 控制每个人的可视数据
68 | const visible = [];
69 | data.forEach((item) => {
70 | if (item.visibleFlag === true) {
71 | visible.push(item);
72 | }
73 | });
74 | const treeArray = [];
75 | visible.forEach((item) => {
76 | const d = item.D;
77 | let floor = [];
78 | floor = d.split('/').slice(1);
79 | let nowArray = treeArray;
80 | for (let i = 0; i < floor.length; i++) {
81 | const index = this.checkIfExist(nowArray, floor[i]);
82 | const vote = {};
83 | vote.N = item.N;
84 | vote.U = item.U;
85 | vote.S = item.S;
86 | if (index !== false && i !== floor.length - 1) {
87 | nowArray = nowArray[index].SD;
88 | } else if (index !== false && i === floor.length - 1) {
89 | nowArray[index].UL.push(vote);
90 | } else if (index === false && i === floor.length - 1) {
91 | nowArray.push({
92 | D: floor[i],
93 | UL: [vote],
94 | SD: []
95 | });
96 | } else {
97 | nowArray.push({
98 | D: floor[i],
99 | UL: [],
100 | SD: []
101 | });
102 | nowArray = nowArray[nowArray.length - 1].SD;
103 | }
104 | }
105 | });
106 | return treeArray;
107 | }
108 |
109 | // 判断当前数组中有没有传进去的name的这个部门
110 | checkIfExist(array = [], name = '') {
111 | let flag = false;
112 | if (array.length === 0) {
113 | flag = false;
114 | } else {
115 | for (let i = 0; i < array.length; i++) {
116 | if (array[i].D === name) {
117 | flag = i;
118 | break;
119 | }
120 | }
121 | }
122 | return flag;
123 | }
124 |
125 | genTreeData(data, id) {
126 | const ret = [];
127 | data.length && data.forEach((item, idx) => {
128 | const key = `${id}-${idx}`;
129 | bu.push(item.D);
130 | const ul = [];
131 | item.UL.forEach((u) => {
132 | users[u.U] = {
133 | bu: bu.slice(0),
134 | U: u.U,
135 | N: u.N,
136 | text: `${u.N}[${u.U}]`,
137 | icon: webConfig.fileurl + '/file/v2/download/8c9d42532be9316e2202ffef8fcfeba5.png',
138 | key: `${key}-${u.U}`
139 | };
140 | usersName[u.N] = {
141 | bu: bu.slice(0),
142 | U: u.U,
143 | N: u.N,
144 | text: `${u.N}[${u.U}]`,
145 | icon: webConfig.fileurl + '/file/v2/download/8c9d42532be9316e2202ffef8fcfeba5.png',
146 | key: `${key}-${u.U}`
147 | };
148 | usersName[u.U] = {
149 | bu: bu.slice(0),
150 | U: u.U,
151 | N: u.N,
152 | text: `${u.N}[${u.U}]`,
153 | icon: webConfig.fileurl + '/file/v2/download/8c9d42532be9316e2202ffef8fcfeba5.png',
154 | key: `${key}-${u.U}`
155 | };
156 | ul.push(users[u.U]);
157 | });
158 | ret.push({
159 | text: item.D,
160 | children: [].concat(ul, this.genTreeData(item.SD, key)),
161 | key
162 | });
163 | bu.pop();
164 | });
165 | return ret;
166 | }
167 |
168 | render() {
169 | const { connectStatus } = this.props;
170 | const phone = /Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent);
171 |
172 | if (connectStatus === 'success' && !phone) {
173 | Notification.requestPermission();//用户是否同意显示通知
174 | return (
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 | );
184 | } else if(connectStatus === 'success' && phone) {
185 | return (
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 | )
195 | }
196 | return (
197 |
198 | );
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/phone-ui/chat/emotions.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import cls from 'classnames';
3 |
4 | export default class Emotions extends Component {
5 | constructor() {
6 | super();
7 | const emotions = Object.assign({}, window.QtalkSDK.emotions[0]);
8 | const { faces } = emotions;
9 | delete emotions.faces;
10 | const pageSize = 32;
11 | this.state = {
12 | showMenu: false,
13 | emotions,
14 | faces,
15 | pageCount: Math.ceil(faces.length / pageSize),
16 | pageSize,
17 | page: 0
18 | };
19 | }
20 |
21 | componentDidMount() {
22 | }
23 |
24 | onChangePage = (e) => {
25 | this.setState({ page: parseInt(e.target.getAttribute('data-page'), 10) });
26 | };
27 |
28 | time = null;
29 |
30 | showMenu = (b) => {
31 | clearTimeout(this.time);
32 | this.time = setTimeout(() => {
33 | this.setState({ showMenu: b });
34 | }, 100);
35 | };
36 |
37 | render() {
38 | const {
39 | showMenu, faces, emotions, pageCount, page, pageSize
40 | } = this.state;
41 | let i = 0;
42 | const pageEl = [];
43 | let data = [];
44 | if (showMenu) {
45 | data = faces.slice(page * pageSize, (page * pageSize) + pageSize);
46 | while (i < pageCount) {
47 | pageEl.push();
53 | i += 1;
54 | }
55 | }
56 | return (
57 | { this.showMenu(false); }}
60 | onClick={() => { this.showMenu(true); }}
61 | >
62 |
63 |
68 |
69 | {
70 | data.map((face, index) => (
71 | - { this.props.selected(emotions, face); }}
73 | key={`face-${index + 1}`}
74 | >
75 |
76 |
77 | ))
78 | }
79 |
80 |
{pageEl}
81 |
82 |
83 | );
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/phone-ui/chat/empty.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | export default class Empty extends Component {
4 | componentDidMount() {
5 | }
6 |
7 | render() {
8 | return (
9 |
10 |
21 |
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/phone-ui/chat/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import actions from '../../actions';
4 | import Header from './header';
5 | import Message from './message';
6 | import Footer from './footer';
7 | import Empty from './empty';
8 | import GroupCard from './groupCard';
9 | import UserCard from './userCard';
10 | // import sdk from '../../sdk';
11 |
12 | @connect(
13 | state => ({
14 | currentSession: state.getIn(['chat', 'currentSession']),
15 | switchIndex: state.getIn(['chat', 'switchIndex']),
16 | currentFriend: state.getIn(['chat', 'currentFriend']),
17 | isChat: state.getIn(['chat', 'isChat']),
18 | isCard: state.getIn(['chat', 'isCard'])
19 | }),
20 | actions
21 | )
22 | export default class Chat extends Component {
23 | constructor() {
24 | super();
25 | this.state = {
26 | messageScrollToBottom: false
27 | };
28 | }
29 |
30 | messageScrollToBottom = () => {
31 | this.setState({
32 | messageScrollToBottom: true
33 | }, () => {
34 | this.setState({
35 | messageScrollToBottom: false
36 | });
37 | });
38 | };
39 |
40 | render() {
41 | const {
42 | currentSession,
43 | switchIndex,
44 | currentFriend,
45 | isChat,
46 | isCard
47 | } = this.props;
48 | const currId = currentSession.get('user') || currentSession.get('groupname');
49 | const currFri = currentFriend.get('user');
50 | if (switchIndex === 'chat' && currId && isChat) {
51 | return (
52 |
53 |
54 |
55 |
56 |
57 | );
58 | } else if (switchIndex === 'friends' && currFri && isCard) {
59 | return (
60 |
61 | {
62 | currentFriend.get('mFlag') === '2' ?
63 | :
64 |
65 | }
66 |
67 | );
68 | }
69 |
70 | return ();
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/phone-ui/modal/addFriends.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import Cookies from 'js-cookie';
4 | import LazyLoad from 'react-lazyload';
5 | import Modal from '../../../../common/components/modal';
6 | import Select from '../../../../common/components/select2-one';
7 | import actions from '../../actions';
8 | import sdk from '../../sdk';
9 | import webConfig from '../../../../../../web_config';
10 |
11 | @connect(
12 | state => ({
13 | userInfo: state.get('userInfo'),
14 | companyStruct: state.getIn(['chat', 'companyStruct']),
15 | currentSession: state.getIn(['chat', 'currentSession'])
16 | }),
17 | actions
18 | )
19 | export default class AddFriends extends Component {
20 | constructor() {
21 | super();
22 | this.state = {
23 | domainList: [], // 域列表
24 | selectDomain: {}, // 选中的域
25 | users: []
26 | };
27 | }
28 |
29 | componentDidMount() {
30 | sdk.ready(async () => {
31 | const res = await sdk.getDomainList();
32 | if (res.ret) {
33 | this.setState({ domainList: res.data.domains, selectDomain: res.data.domains[0] || {} });
34 | }
35 | });
36 | }
37 |
38 | // componentWillReceiveProps(nextProps) {
39 | // // if (nextProps.userList.length > 0) {
40 | // // this.initTree(nextProps);
41 | // // }
42 | // }
43 |
44 | onSearch = (e) => {
45 | const val = e.target.value.trim();
46 | clearTimeout(this.time);
47 | this.time = setTimeout(async () => {
48 | const res = await sdk.searchSbuddy({
49 | id: this.state.selectDomain.id,
50 | key: val,
51 | ckey: Cookies.get('q_ckey'),
52 | limit: 12,
53 | offset: 0
54 | });
55 | if (res.ret) {
56 | this.setState({
57 | users: res.data.users
58 | });
59 | }
60 | }, 250);
61 | };
62 |
63 | render() {
64 | return (
65 |
66 |
67 | 查找联系人
68 | { this.props.hide(); }} className="icon close" />
69 |
70 |
71 |
72 |
查找范围:
73 |
81 |
82 |
83 |
89 |
90 |
110 |
111 |
112 | );
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/phone-ui/modal/addUser.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: xi.guo
4 | * @Date: 2019-08-06 11:24:03
5 | * @LastEditTime: 2019-08-13 12:01:48
6 | * @LastEditors: Please set LastEditors
7 | */
8 | import React, { Component } from 'react';
9 | import { connect } from 'react-redux';
10 | import cls from 'classnames';
11 | import Modal from '../../../../common/components/modal';
12 | import actions from '../../actions';
13 | import $ from '../../../../common/lib/jstree';
14 | import sdk from '../../sdk';
15 |
16 | const webConfig = {
17 | domain: startalkNav.baseaddess && startalkNav.baseaddess.domain
18 | }
19 |
20 | @connect(
21 | state => ({
22 | userInfo: state.get('userInfo'),
23 | companyStruct: state.getIn(['chat', 'companyStruct']),
24 | currentSession: state.getIn(['chat', 'currentSession'])
25 | }),
26 | actions
27 | )
28 | export default class AddUser extends Component {
29 | constructor() {
30 | super();
31 | this.state = {
32 | selected: []
33 | };
34 | }
35 |
36 | componentDidMount() {
37 | this.initTree();
38 | }
39 |
40 | onSearch = (e) => {
41 | const val = e.target.value.trim();
42 | clearTimeout(this.time);
43 | this.time = setTimeout(() => {
44 | $('#addUserTree').jstree(true).search(val);
45 | }, 300);
46 | };
47 |
48 | time = null;
49 |
50 | initTree(nextProps) {
51 | const { companyStruct, currentSession, userInfo } = nextProps || this.props;
52 | $('#addUserTree')
53 | .on('changed.jstree', (e, data) => {
54 | const ret = [];
55 | data.selected.forEach((id, index) => {
56 | const json = data.instance.get_node(data.selected[index]).original;
57 | if (json.U) {
58 | ret.push(json);
59 | }
60 | });
61 | // 单聊,要把会话对象加入
62 | if (ret.length > 0 && currentSession.get('mFlag') === '1') {
63 | const u = userInfo.get(currentSession.get('user'));
64 | ret.push({
65 | U: u.get('username') || window.QtalkSDK.env.Strophe.getNodeFromJid(currentSession.get('user')),
66 | N: u.get('nickname') || ''
67 | });
68 | }
69 | this.setState({
70 | selected: ret
71 | });
72 | })
73 | .jstree({
74 | core: {
75 | data: [
76 | {
77 | text: 'Staff',
78 | state: {
79 | opened: true
80 | },
81 | children: companyStruct
82 | }
83 | ]
84 | },
85 | plugins: [
86 | 'checkbox',
87 | 'search'
88 | ]
89 | });
90 | // this.initTree = () => { };
91 | }
92 |
93 | addUser = async () => {
94 | const { selected } = this.state;
95 | const { changeChatField, currentSession, clearSessionCnt } = this.props;
96 | const users = selected.map(u => ({ jid: `${u.U}@${webConfig.domain}`, nick: u.N }));
97 | if (users.length > 0) {
98 | const res = await sdk.addUser(users);
99 | if (res.ret) {
100 | this.props.hide();
101 | // 单聊创建新群后,激活会话
102 | if (currentSession.get('mFlag') === '1') {
103 | setTimeout(() => {
104 | changeChatField({
105 | currentSession: {
106 | cnt: 0,
107 | sdk_msg: '',
108 | simpmsg: '',
109 | user: res.data,
110 | mFlag: '2'
111 | }
112 | });
113 | clearSessionCnt();
114 | }, 0);
115 | }
116 | } else {
117 | alert(res.errmsg);
118 | }
119 | }
120 | };
121 |
122 | render() {
123 | const { selected } = this.state;
124 | return (
125 |
126 |
127 | 添加会话成员
128 | { this.props.hide(); }} className="icon close" />
129 |
130 |
131 |
132 |
133 |
139 |
140 |
143 |
144 |
155 |
156 | );
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/phone-ui/panel/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Info from './info';
3 | import Search from './search';
4 | import Tab from './tab';
5 | // import sdk from '../../sdk';
6 |
7 | export default class Panel extends Component {
8 | componentDidMount() {
9 | }
10 |
11 | render() {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/phone-ui/panel/info.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: xi.guo
4 | * @Date: 2019-08-06 11:24:03
5 | * @LastEditTime: 2019-08-13 12:08:26
6 | * @LastEditors: Please set LastEditors
7 | */
8 | import React, { Component } from 'react';
9 | import { connect } from 'react-redux';
10 | import cls from 'classnames';
11 | import Cookies from 'js-cookie';
12 | import MessageBox from '../../../../common/components/message-box';
13 | import actions from '../../actions';
14 | import sdk from '../../sdk';
15 |
16 | const webConfig = {
17 | fileurl: startalkNav.baseaddess && startalkNav.baseaddess.fileurl
18 | }
19 |
20 | @connect(
21 | state => ({
22 | userInfo: state.get('userInfo')
23 | }),
24 | actions
25 | )
26 | export default class Info extends Component {
27 | constructor() {
28 | super();
29 | this.state = {
30 | showMenu: false
31 | };
32 | }
33 |
34 | componentDidMount() {
35 | const { setUserInfo } = this.props;
36 | // 可以正常收发消息了
37 | sdk.ready(async () => {
38 | const res = await sdk.getUserCard([sdk.bareJid]);
39 | if (res.ret) {
40 | setUserInfo(res.data);
41 | }
42 | });
43 | }
44 |
45 | onShowUserCard = (e) => {
46 | const { setModalUserCard } = this.props;
47 | setModalUserCard({
48 | show: true,
49 | pos: {
50 | left: `${e.clientX}px`,
51 | top: `${e.clientY + 50}px`
52 | },
53 | user: sdk.bareJid
54 | });
55 | }
56 |
57 | onShowMembers = () => {
58 | const { setMembersInfo } = this.props;
59 | setMembersInfo({
60 | show: true,
61 | isNew: true
62 | });
63 | this.showMenu(false);
64 | }
65 |
66 | time = null;
67 |
68 | showMenu = (b) => {
69 | clearTimeout(this.time);
70 | this.time = setTimeout(() => {
71 | this.setState({ showMenu: b });
72 | }, 100);
73 | };
74 |
75 | logout = () => {
76 | MessageBox.confirm(
77 | '确认退出?',
78 | '提示'
79 | ).ok(() => {
80 | let img = webConfig.fileurl+'/file/v2/download/8c9d42532be9316e2202ffef8fcfeba5.png?w=80&h=80';
81 | const info = this.props.userInfo.get(sdk.bareJid);
82 | if (info) {
83 | img = info.get('imageurl') || '';
84 | if (!/^(https:|http:|\/\/)/g.test(img)) {
85 | img = `${webConfig.fileurl}/${img}`;
86 | }
87 | }
88 | Cookies.set('qt_avatar', img, { expires: 1 });
89 | Cookies.remove('qt_username');
90 | Cookies.remove('qt_password');
91 | sdk.connection.disConnection();
92 | this.props.changeChatField({ connectStatus: '' });
93 | window.location.reload();
94 | });
95 | }
96 |
97 | render() {
98 | const { userInfo } = this.props;
99 | let img = webConfig.fileurl+'/file/v2/download/8c9d42532be9316e2202ffef8fcfeba5.png?w=80&h=80';
100 | let name = '';
101 | const info = userInfo.get(sdk.bareJid);
102 | if (info) {
103 | name = info.get('nickname') || '';
104 | img = info.get('imageurl') || '';
105 | if (!/^(https:|http:|\/\/)/g.test(img)) {
106 | img = `${webConfig.fileurl}/${img}`;
107 | }
108 | }
109 | return (
110 |
111 |
112 |

113 |
114 |
115 |
116 | {name}
117 | { this.showMenu(false); }}
120 | onClick={() => { this.showMenu(true); }}
121 | >
122 |
123 |
137 |
138 |
139 |
140 |
141 | );
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/phone-ui/panel/search.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import LazyLoad from 'react-lazyload';
4 | import Cookies from 'js-cookie';
5 | // import cls from 'classnames';
6 | import actions from '../../actions';
7 | import sdk from '../../sdk';
8 |
9 | @connect(
10 | state => ({
11 | currentSession: state.getIn(['chat', 'currentSession'])
12 | }),
13 | actions
14 | )
15 | export default class Search extends Component {
16 | constructor() {
17 | super();
18 | this.state = {
19 | showModal: false,
20 | value: '',
21 | result: []
22 | };
23 | }
24 |
25 | componentDidMount() {
26 | }
27 |
28 | onClearClick() {
29 | this.setState({
30 | value: '',
31 | showModal: false
32 | });
33 | }
34 |
35 | onSessionClick = async (data) => {
36 | const {
37 | setUserInfo,
38 | setCurrentSession,
39 | changeChatField
40 | } = this.props;
41 | this.setState({
42 | value: '',
43 | showModal: false
44 | });
45 | if (data.mFlag === '2') {
46 | const res = await sdk.getGroupCard([data.user]);
47 | if (res.ret) {
48 | setUserInfo(res.data);
49 | }
50 | }
51 | setCurrentSession(data);
52 | changeChatField({ switchIndex: 'chat' });
53 | window.QtalkSDK.$('.session').scrollTop(0);
54 | }
55 |
56 | onChange = (e) => {
57 | const val = e.target.value.trim();
58 | this.setState({ value: val });
59 | clearTimeout(this.timer);
60 | this.timer = setTimeout(async () => {
61 | const state = {};
62 | if (val.length > 1) {
63 | state.showModal = true;
64 | // 获取查询结果
65 | const res = await sdk.searchUser(val);
66 | if (res.data) {
67 | state.result = res.data;
68 | }
69 | } else {
70 | state.showModal = false;
71 | }
72 | this.setState(state);
73 | }, 250);
74 | }
75 |
76 | timer = null;
77 | renderResult = () => {
78 | const { result } = this.state;
79 | if (result && result.length < 1) {
80 | return null;
81 | }
82 | return (
83 |
107 | );
108 | }
109 | render() {
110 | return (
111 |
112 |
113 |
{ setTimeout(() => { this.setState({ showModal: false }); }, 300); }}
120 | />
121 | {
122 | this.state.showModal &&
123 |
124 | {this.renderResult()}
125 | {/*
找不到?尝试打开会话
126 |
this.onSessionClick({ user: this.state.value, mFlag: '1' })}>打开ID为[{this.state.value}]的对话
*/}
127 |
128 | }
129 |
130 | );
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/phone-ui/panel/tab.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import cls from 'classnames';
4 | import Session from './session';
5 | import Friends from './friends';
6 | import actions from '../../actions';
7 | // import sdk from '../../sdk';
8 |
9 | @connect(
10 | state => ({
11 | switchIndex: state.getIn(['chat', 'switchIndex'])
12 | }),
13 | actions
14 | )
15 | export default class Tab extends Component {
16 | onSwitch(switchIndex) {
17 | this.props.changeChatField({ switchIndex });
18 | }
19 |
20 | render() {
21 | const { switchIndex } = this.props;
22 | return (
23 |
24 |
25 |
{ this.onSwitch('chat'); }}>
26 |
27 |
28 |
29 |
30 |
{ this.onSwitch('friends'); }}>
31 |
32 |
33 |
34 |
35 |
36 |
37 |
{ this.onSwitch('chat'); }}
39 | show={switchIndex === 'friends'}
40 | />
41 |
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/phone-ui/tree/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: xi.guo
4 | * @Date: 2019-08-06 11:24:03
5 | * @LastEditTime: 2019-08-13 12:10:29
6 | * @LastEditors: Please set LastEditors
7 | */
8 | import React, { Component } from 'react';
9 | import { connect } from 'react-redux';
10 | import cls from 'classnames';
11 | import actions from '../../actions';
12 | import './index.less';
13 | import sdk from '../../sdk';
14 |
15 | const webConfig = {
16 | fileurl: startalkNav.baseaddess && startalkNav.baseaddess.fileurl,
17 | domain: startalkNav.baseaddess && startalkNav.baseaddess.domain
18 | }
19 |
20 | @connect(
21 | state => ({
22 | userInfo: state.get('userInfo')
23 | }),
24 | actions
25 | )
26 | class Tree extends Component {
27 | constructor(props) {
28 | super(props);
29 | this.state = {
30 | tree: {}, // { key: true } key=true 展开 false 闭合
31 | selected: {}, // { key: true } key=true 被选择的
32 | searchTree: {} // { key: true } key=true 搜索的结果
33 | };
34 | }
35 |
36 | componentWillReceiveProps(nextProps) {
37 | const { selected, tree, searchTree } = this.state;
38 | if (nextProps.data.length === 0) {
39 | return null;
40 | }
41 | this.setState({
42 | selected: nextProps.selected ? nextProps.selected : selected,
43 | tree: nextProps.tree ? nextProps.tree : tree,
44 | searchTree: nextProps.searchTree ? nextProps.searchTree : searchTree
45 | });
46 | return true;
47 | }
48 |
49 | onTreeClick(e, data, disable) {
50 | e.stopPropagation();
51 | if (disable) {
52 | return null;
53 | }
54 | const { key } = data;
55 | const { tree } = this.state;
56 | const newTree = Object.assign({}, tree, {
57 | [key]: !tree[key]
58 | });
59 | this.setState({
60 | tree: newTree
61 | });
62 | let u = '';
63 | if (data.U && this.props.onClick) {
64 | const { changeChatField } = this.props;
65 | changeChatField({ isCard: true });
66 | u = `${data.U}@${webConfig.domain}`;
67 | }
68 | if (data.children) {
69 | const users = [];
70 | data.children.forEach((item) => {
71 | if (item.U) {
72 | users.push(`${item.U}@${webConfig.domain}`);
73 | }
74 | });
75 | if (users.length > 0) {
76 | this.cacheUserCard(users);
77 | }
78 | }
79 | this.props.onClick(u, newTree);
80 | if (this.props.showSelect && !data.children) {
81 | this.onSelect(data);
82 | }
83 | return true;
84 | }
85 |
86 | onSelect(item) {
87 | const { selected } = this.state;
88 | const newSelected = Object.assign(
89 | {},
90 | selected,
91 | {
92 | [item.key]: {
93 | flag: !(selected[item.key] && selected[item.key].flag),
94 | value: item
95 | }
96 | }
97 | );
98 | this.setState({
99 | selected: newSelected
100 | });
101 |
102 | this.props.onSelect(newSelected);
103 | }
104 |
105 | async cacheUserCard(users) {
106 | const { setUserInfo } = this.props;
107 | const res = await sdk.getUserCard(users);
108 | if (res.ret) {
109 | setUserInfo(res.data);
110 | }
111 | return res;
112 | }
113 |
114 | imgError(e) {
115 | e.target.src = webConfig.fileurl+'/file/v2/download/8c9d42532be9316e2202ffef8fcfeba5.png';//darlyn'
116 | }
117 |
118 | renderItems(data) {
119 | const { selected, searchTree } = this.state;
120 | const { showSelect, noSelected } = this.props;
121 | return (
122 |
123 | {
124 | data.map((item) => {
125 | const tree = this.state.tree[item.key];
126 | const active = !item.children && selected[item.key] && selected[item.key].flag;
127 | const disable = showSelect && !item.children && noSelected[item.U];
128 | if (Object.keys(searchTree).length > 0 && tree === undefined) {
129 | return null;
130 | }
131 | return (
132 | - this.onTreeClick(e, item, disable)}
135 | className={cls('tree-list-item', {
136 | deep: !item.children,
137 | disabled: showSelect && !item.children && noSelected[item.U]
138 | })}
139 | >
140 | {
141 | item.children ?
142 | :
148 |
153 | }
154 | {item.text}
155 | {
156 | showSelect && !item.children && !noSelected[item.U] &&
157 |
160 | }
161 | {
162 | tree && item.children && this.renderItems(item.children)
163 | }
164 |
165 | );
166 | })
167 | }
168 |
169 | );
170 | }
171 |
172 | render() {
173 | const { data } = this.props;
174 | return (
175 |
176 | {this.renderItems(data)}
177 |
178 | );
179 | }
180 | }
181 |
182 | const noob = () => {};
183 | Tree.defaultProps = {
184 | className: '',
185 | data: [],
186 | showSelect: false,
187 | onSelect: noob,
188 | onClick: '',
189 | selected: [],
190 | noSelected: {}
191 | };
192 |
193 | export default Tree;
194 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/phone-ui/tree/index.less:
--------------------------------------------------------------------------------
1 | .tree {
2 | min-height: 22px;
3 | padding: 15px 0;
4 | cursor: pointer;
5 | &-list {
6 | width: 248px;
7 | min-height: 40px;
8 | z-index: 1;
9 | &-item {
10 | width: 248px;
11 | min-height: 40px;
12 | float: left;
13 | position: relative;
14 | &.deep {
15 | padding: 9px 0;
16 | }
17 | &.search {
18 | .text {
19 | color: #E92F2F;
20 | }
21 | }
22 | &.disabled {
23 | // pointer-events: none
24 | .text {
25 | color: #9E9E9E;
26 | }
27 | }
28 | .iconfont {
29 | float: left;
30 | color: #45CF8E;
31 | font-size: 14px;
32 | vertical-align: middle;
33 | margin-top: 9px;
34 | }
35 | .iconfont.nike {
36 | font-size: 24px;
37 | color: #BDBDBD;
38 | position: absolute;
39 | right: 10px;
40 | top: 50%;
41 | margin-top: -24px;
42 | cursor: pointer;
43 | &.active {
44 | color: #45CF8E;
45 | }
46 | }
47 | .text {
48 | width: 166px;
49 | height: 40px;
50 | line-height: 40px;
51 | float: left;
52 | font-weight: 400;
53 | font-size: 14px;
54 | margin-left: 6px;
55 | overflow: hidden;
56 | text-overflow:ellipsis;
57 | white-space: nowrap;
58 | word-wrap: normal;
59 | user-select:none;
60 | &.text-active{
61 | color: #45CF8E;
62 | }
63 | }
64 | img {
65 | width: 45px;
66 | height: 45px;
67 | border-radius: 50%;
68 | float: left;
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/sdk.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: your name
4 | * @Date: 2019-08-06 11:24:02
5 | * @LastEditTime: 2019-08-12 16:54:37
6 | * @LastEditors: Please set LastEditors
7 | */
8 |
9 | /**
10 | * qtlk sdk
11 | */
12 | const { baseaddess: {domain, xmpp, xmppmport, fileurl, javaurl, socketurl} = {} } = startalkNav
13 |
14 |
15 | const sdk = new window.QtalkSDK({
16 | // 调试
17 | debug: true,
18 | xmpp: xmpp,
19 | // 链接配置
20 | connect: {
21 | host: socketurl
22 | },
23 | maType: 6 // 平台类型web端:6
24 | });
25 |
26 | export default sdk;
27 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/ui/chat/emotions.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import cls from 'classnames';
3 |
4 | export default class Emotions extends Component {
5 | constructor() {
6 | super();
7 | const emotions = Object.assign({}, window.QtalkSDK.emotions[0]);
8 | const { faces } = emotions;
9 | delete emotions.faces;
10 | const pageSize = 32;
11 | this.state = {
12 | showMenu: false,
13 | emotions,
14 | faces,
15 | pageCount: Math.ceil(faces.length / pageSize),
16 | pageSize,
17 | page: 0
18 | };
19 | }
20 |
21 | componentDidMount() {
22 | }
23 |
24 | onChangePage = (e) => {
25 | this.setState({ page: parseInt(e.target.getAttribute('data-page'), 10) });
26 | };
27 |
28 | time = null;
29 |
30 | showMenu = (b) => {
31 | clearTimeout(this.time);
32 | this.time = setTimeout(() => {
33 | this.setState({ showMenu: b });
34 | }, 100);
35 | };
36 |
37 | render() {
38 | const {
39 | showMenu, faces, emotions, pageCount, page, pageSize
40 | } = this.state;
41 | let i = 0;
42 | const pageEl = [];
43 | let data = [];
44 | if (showMenu) {
45 | data = faces.slice(page * pageSize, (page * pageSize) + pageSize);
46 | while (i < pageCount) {
47 | pageEl.push();
53 | i += 1;
54 | }
55 | }
56 | return (
57 | { this.showMenu(false); }}
60 | onMouseEnter={() => { this.showMenu(true); }}
61 | >
62 |
63 |
68 |
69 | {
70 | data.map((face, index) => (
71 | - { this.props.selected(emotions, face); }}
73 | key={`face-${index + 1}`}
74 | >
75 |
76 |
77 | ))
78 | }
79 |
80 |
{pageEl}
81 |
82 |
83 | );
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/ui/chat/empty.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | export default class Empty extends Component {
4 | componentDidMount() {
5 | }
6 |
7 | render() {
8 | return (
9 |
10 |
21 |
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/ui/chat/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import actions from '../../actions';
4 | import Header from './header';
5 | import Message from './message';
6 | import Footer from './footer';
7 | import Empty from './empty';
8 | import GroupCard from './groupCard';
9 | import UserCard from './userCard';
10 | // import sdk from '../../sdk';
11 |
12 | @connect(
13 | state => ({
14 | currentSession: state.getIn(['chat', 'currentSession']),
15 | switchIndex: state.getIn(['chat', 'switchIndex']),
16 | currentFriend: state.getIn(['chat', 'currentFriend'])
17 | }),
18 | actions
19 | )
20 | export default class Chat extends Component {
21 | constructor() {
22 | super();
23 | this.state = {
24 | messageScrollToBottom: false
25 | };
26 | }
27 |
28 | messageScrollToBottom = () => {
29 | this.setState({
30 | messageScrollToBottom: true
31 | }, () => {
32 | this.setState({
33 | messageScrollToBottom: false
34 | });
35 | });
36 | };
37 |
38 | render() {
39 | const {
40 | currentSession,
41 | switchIndex,
42 | currentFriend
43 | } = this.props;
44 |
45 | const currId = currentSession.get('user') || currentSession.get('groupname');
46 | const currFri = currentFriend.get('user');
47 | if (switchIndex === 'chat' && currId) {
48 | return (
49 |
50 |
51 |
52 |
53 |
54 | );
55 | } else if (switchIndex === 'friends' && currFri) {
56 | return (
57 |
58 | {
59 | currentFriend.get('mFlag') === '2' ?
60 | :
61 |
62 | }
63 |
64 | );
65 | }
66 |
67 | return ();
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/ui/chat/members.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: xi.guo
4 | * @Date: 2019-08-06 11:24:02
5 | * @LastEditTime: 2019-08-13 19:47:39
6 | * @LastEditors: Please set LastEditors
7 | */
8 | import React, { Component } from 'react';
9 | import { connect } from 'react-redux';
10 | import cls from 'classnames';
11 | import LazyLoad from 'react-lazyload';
12 | import MessageBox from '../../../../common/components/message-box';
13 | import actions from '../../actions';
14 | import sdk from '../../sdk';
15 | import webConfig from '../../../../../../web_config';
16 | import footera3faf4373242d1 from '../../../../../../assets/footer/a3faf4373242d1.png';
17 |
18 | const { $ } = window.QtalkSDK;
19 |
20 | @connect(
21 | state => ({
22 | userInfo: state.get('userInfo'),
23 | currentSession: state.getIn(['chat', 'currentSession'])
24 | }),
25 | actions
26 | )
27 | export default class Members extends Component {
28 | constructor() {
29 | super();
30 | this.state = {
31 | showRemove: false,
32 | owner: false,
33 | admin: false
34 | };
35 | }
36 |
37 | componentDidMount() {
38 | this.getUserInfo();
39 | $(window)
40 | .on('keydown', (e) => {
41 | // ctrl 显示 踢人按钮
42 | if (e.keyCode === 17
43 | && this.props.show
44 | && (this.state.owner || this.state.admin)
45 | ) {
46 | this.setState({ showRemove: true });
47 | }
48 | })
49 | .on('keyup', (e) => {
50 | if (e.keyCode === 17
51 | && this.props.show
52 | && (this.state.owner || this.state.admin)
53 | ) {
54 | this.setState({ showRemove: false });
55 | }
56 | });
57 | }
58 |
59 | componentWillReceiveProps(nextProps) {
60 | const { show } = this.props;
61 | // 隐藏的时候重置
62 | if (show !== nextProps.show && !nextProps.show) {
63 | this.setState({
64 | showRemove: false,
65 | owner: false,
66 | admin: false
67 | });
68 | }
69 | if (nextProps.show) {
70 | this.getUserInfo(nextProps);
71 | }
72 | }
73 |
74 | onShowUserCard = (e, user) => {
75 | const { setModalUserCard } = this.props;
76 | const pos = {
77 | left: `${e.clientX}px`,
78 | top: `${e.clientY + 50}px`
79 | };
80 | if (e.clientX + 280 > $(window).width()) {
81 | pos.left = `${e.clientX - 280}px`;
82 | }
83 | setModalUserCard({
84 | show: true,
85 | pos,
86 | user
87 | });
88 | }
89 |
90 | onRemoveUser(jid) {
91 | const { currentSession } = this.props;
92 | if (jid === sdk.bareJid) {
93 | MessageBox.confirm(
94 | '即将从该群退出,如果继续且需要恢复此操作,
你需要让当前群成员重新邀请。是否继续?',
95 | '警告'
96 | ).ok(() => {
97 | sdk.groupExit(jid);
98 | });
99 | } else {
100 | sdk.groupRemoveUser(jid, currentSession.get('user'));
101 | }
102 | }
103 |
104 | onContextMenu(e, jid, role) {
105 | e.preventDefault();
106 | const { setContentMenu, currentSession } = this.props;
107 | const { owner, admin } = this.state;
108 | setContentMenu({
109 | show: true,
110 | type: 'member',
111 | data: {
112 | user: jid,
113 | userRole: role,
114 | owner,
115 | admin,
116 | groupId: currentSession.get('user')
117 | },
118 | pos: {
119 | left: `${e.clientX}px`,
120 | top: `${e.clientY}px`
121 | }
122 | });
123 | }
124 |
125 | async getUserInfo(nextProps) {
126 | const { setUserInfo, userList, userInfo } = nextProps || this.props;
127 | if (userList && userList.size > 0) {
128 | const users = [];
129 | const usersCheck = {};
130 | userList.forEach((item) => {
131 | // 是否有权限踢人,跟升级管理员
132 | const jid = item.get('jid');
133 | const affiliation = item.get('affiliation');
134 | if (jid === sdk.bareJid && affiliation) {
135 | this.setState({
136 | [affiliation]: true
137 | });
138 | }
139 | if (!userInfo.get(jid) && !usersCheck[jid]) {
140 | users.push(jid);
141 | usersCheck[jid] = true;
142 | }
143 | });
144 | if (users.length > 0) {
145 | const res = await sdk.getUserCard(users);
146 | if (res.ret) {
147 | setUserInfo(res.data);
148 | }
149 | }
150 | }
151 | }
152 |
153 | addUser = () => {
154 | this.props.addUser();
155 | };
156 |
157 | imgError(e) {
158 | e.target.src = webConfig.fileurl+'/file/v2/download/8c9d42532be9316e2202ffef8fcfeba5.png';
159 | }
160 |
161 | render() {
162 | const { show, userList, userInfo } = this.props;
163 | const { showRemove, owner, admin } = this.state;
164 | if (!show) {
165 | return null;
166 | }
167 | return (
168 |
169 |
170 |
171 |
172 |
173 | {
174 | userList.map((item, index) => {
175 | let img = footera3faf4373242d1;
176 | const jid = item.get('jid');
177 | const affiliation = item.get('affiliation');
178 | let name = jid;
179 | const info = userInfo.get(jid);
180 | if (info) {
181 | name = info.get('nickname') || jid;
182 | img = info.get('imageurl') || '';
183 | if (!/^(https:|http:|\/\/)/g.test(img)) {
184 | img = webConfig.fileurl+`/${img}`;
185 | }
186 | }
187 | return (
188 |
{ this.onContextMenu(e, jid, affiliation); }}
192 | >
193 | {
194 | showRemove
195 | && ((admin && affiliation !== 'owner') || owner)
196 | ? (
197 |
{ this.onRemoveUser(jid); }}
199 | className="opt animation animating fadeIn"
200 | >
201 |
202 |
203 | )
204 | : null
205 | }
206 |
207 |
208 |
209 |
210 |
{ this.onShowUserCard(e, jid); }}
212 | className="avatar user-card"
213 | src={img}
214 | alt=""
215 | onError={this.imgError}
216 | />
217 |
218 |
{name}
219 |
220 | );
221 | })
222 | }
223 |
224 | {
225 | (owner || admin)
226 | ?
群人员管理[右键]菜单,按住[ctrl]快捷操作
227 | : null
228 | }
229 |
230 | );
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/ui/modal/addFriends.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import Cookies from 'js-cookie';
4 | import LazyLoad from 'react-lazyload';
5 | import Modal from '../../../../common/components/modal';
6 | import Select from '../../../../common/components/select2-one';
7 | import actions from '../../actions';
8 | import sdk from '../../sdk';
9 | import webConfig from '../../../../../../web_config';
10 |
11 | @connect(
12 | state => ({
13 | userInfo: state.get('userInfo'),
14 | companyStruct: state.getIn(['chat', 'companyStruct']),
15 | currentSession: state.getIn(['chat', 'currentSession'])
16 | }),
17 | actions
18 | )
19 | export default class AddFriends extends Component {
20 | constructor() {
21 | super();
22 | this.state = {
23 | domainList: [], // 域列表
24 | selectDomain: {}, // 选中的域
25 | users: []
26 | };
27 | }
28 |
29 | componentDidMount() {
30 | sdk.ready(async () => {
31 | const res = await sdk.getDomainList();
32 | if (res.ret) {
33 | this.setState({ domainList: res.data.domains, selectDomain: res.data.domains[0] || {} });
34 | }
35 | });
36 | }
37 |
38 | // componentWillReceiveProps(nextProps) {
39 | // // if (nextProps.userList.length > 0) {
40 | // // this.initTree(nextProps);
41 | // // }
42 | // }
43 |
44 | onSearch = (e) => {
45 | const val = e.target.value.trim();
46 | clearTimeout(this.time);
47 | this.time = setTimeout(async () => {
48 | const res = await sdk.searchSbuddy({
49 | id: this.state.selectDomain.id,
50 | key: val,
51 | ckey: Cookies.get('q_ckey'),
52 | limit: 12,
53 | offset: 0
54 | });
55 | if (res.ret) {
56 | this.setState({
57 | users: res.data.users
58 | });
59 | }
60 | }, 250);
61 | };
62 |
63 | render() {
64 | return (
65 |
66 |
67 | 查找联系人
68 | { this.props.hide(); }} className="icon close" />
69 |
70 |
71 |
72 |
查找范围:
73 |
81 |
82 |
83 |
89 |
90 |
110 |
111 |
112 | );
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/ui/modal/addUser.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: xi.guo
4 | * @Date: 2019-08-06 11:24:03
5 | * @LastEditTime: 2019-08-13 12:01:48
6 | * @LastEditors: Please set LastEditors
7 | */
8 | import React, { Component } from 'react';
9 | import { connect } from 'react-redux';
10 | import cls from 'classnames';
11 | import Modal from '../../../../common/components/modal';
12 | import actions from '../../actions';
13 | import $ from '../../../../common/lib/jstree';
14 | import sdk from '../../sdk';
15 |
16 | const webConfig = {
17 | domain: startalkNav.baseaddess && startalkNav.baseaddess.domain
18 | }
19 |
20 | @connect(
21 | state => ({
22 | userInfo: state.get('userInfo'),
23 | companyStruct: state.getIn(['chat', 'companyStruct']),
24 | currentSession: state.getIn(['chat', 'currentSession'])
25 | }),
26 | actions
27 | )
28 | export default class AddUser extends Component {
29 | constructor() {
30 | super();
31 | this.state = {
32 | selected: []
33 | };
34 | }
35 |
36 | componentDidMount() {
37 | this.initTree();
38 | }
39 |
40 | onSearch = (e) => {
41 | const val = e.target.value.trim();
42 | clearTimeout(this.time);
43 | this.time = setTimeout(() => {
44 | $('#addUserTree').jstree(true).search(val);
45 | }, 300);
46 | };
47 |
48 | time = null;
49 |
50 | initTree(nextProps) {
51 | const { companyStruct, currentSession, userInfo } = nextProps || this.props;
52 | $('#addUserTree')
53 | .on('changed.jstree', (e, data) => {
54 | const ret = [];
55 | data.selected.forEach((id, index) => {
56 | const json = data.instance.get_node(data.selected[index]).original;
57 | if (json.U) {
58 | ret.push(json);
59 | }
60 | });
61 | // 单聊,要把会话对象加入
62 | if (ret.length > 0 && currentSession.get('mFlag') === '1') {
63 | const u = userInfo.get(currentSession.get('user'));
64 | ret.push({
65 | U: u.get('username') || window.QtalkSDK.env.Strophe.getNodeFromJid(currentSession.get('user')),
66 | N: u.get('nickname') || ''
67 | });
68 | }
69 | this.setState({
70 | selected: ret
71 | });
72 | })
73 | .jstree({
74 | core: {
75 | data: [
76 | {
77 | text: 'Staff',
78 | state: {
79 | opened: true
80 | },
81 | children: companyStruct
82 | }
83 | ]
84 | },
85 | plugins: [
86 | 'checkbox',
87 | 'search'
88 | ]
89 | });
90 | // this.initTree = () => { };
91 | }
92 |
93 | addUser = async () => {
94 | const { selected } = this.state;
95 | const { changeChatField, currentSession, clearSessionCnt } = this.props;
96 | const users = selected.map(u => ({ jid: `${u.U}@${webConfig.domain}`, nick: u.N }));
97 | if (users.length > 0) {
98 | const res = await sdk.addUser(users);
99 | if (res.ret) {
100 | this.props.hide();
101 | // 单聊创建新群后,激活会话
102 | if (currentSession.get('mFlag') === '1') {
103 | setTimeout(() => {
104 | changeChatField({
105 | currentSession: {
106 | cnt: 0,
107 | sdk_msg: '',
108 | simpmsg: '',
109 | user: res.data,
110 | mFlag: '2'
111 | }
112 | });
113 | clearSessionCnt();
114 | }, 0);
115 | }
116 | } else {
117 | alert(res.errmsg);
118 | }
119 | }
120 | };
121 |
122 | render() {
123 | const { selected } = this.state;
124 | return (
125 |
126 |
127 | 添加会话成员
128 | { this.props.hide(); }} className="icon close" />
129 |
130 |
131 |
132 |
133 |
139 |
140 |
143 |
144 |
155 |
156 | );
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/ui/panel/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Info from './info';
3 | import Search from './search';
4 | import Tab from './tab';
5 | // import sdk from '../../sdk';
6 |
7 | export default class Panel extends Component {
8 | componentDidMount() {
9 | }
10 |
11 | render() {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/ui/panel/info.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: xi.guo
4 | * @Date: 2019-08-06 11:24:03
5 | * @LastEditTime: 2019-08-13 12:08:26
6 | * @LastEditors: Please set LastEditors
7 | */
8 | import React, { Component } from 'react';
9 | import { connect } from 'react-redux';
10 | import cls from 'classnames';
11 | import Cookies from 'js-cookie';
12 | import MessageBox from '../../../../common/components/message-box';
13 | import actions from '../../actions';
14 | import sdk from '../../sdk';
15 |
16 | const webConfig = {
17 | fileurl: startalkNav.baseaddess && startalkNav.baseaddess.fileurl
18 | }
19 |
20 | @connect(
21 | state => ({
22 | userInfo: state.get('userInfo')
23 | }),
24 | actions
25 | )
26 | export default class Info extends Component {
27 | constructor() {
28 | super();
29 | this.state = {
30 | showMenu: false
31 | };
32 | }
33 |
34 | componentDidMount() {
35 | const { setUserInfo } = this.props;
36 | // 可以正常收发消息了
37 | sdk.ready(async () => {
38 | const res = await sdk.getUserCard([sdk.bareJid]);
39 | if (res.ret) {
40 | setUserInfo(res.data);
41 | }
42 | });
43 | }
44 |
45 | onShowUserCard = (e) => {
46 | const { setModalUserCard } = this.props;
47 | setModalUserCard({
48 | show: true,
49 | pos: {
50 | left: `${e.clientX}px`,
51 | top: `${e.clientY + 50}px`
52 | },
53 | user: sdk.bareJid
54 | });
55 | }
56 |
57 | onShowMembers = () => {
58 | const { setMembersInfo } = this.props;
59 | setMembersInfo({
60 | show: true,
61 | isNew: true
62 | });
63 | this.showMenu(false);
64 | }
65 |
66 | time = null;
67 |
68 | showMenu = (b) => {
69 | clearTimeout(this.time);
70 | this.time = setTimeout(() => {
71 | this.setState({ showMenu: b });
72 | }, 100);
73 | };
74 |
75 | logout = () => {
76 | MessageBox.confirm(
77 | '确认退出?',
78 | '提示'
79 | ).ok(() => {
80 | let img = webConfig.fileurl+'/file/v2/download/8c9d42532be9316e2202ffef8fcfeba5.png?w=80&h=80';
81 | const info = this.props.userInfo.get(sdk.bareJid);
82 | if (info) {
83 | img = info.get('imageurl') || '';
84 | if (!/^(https:|http:|\/\/)/g.test(img)) {
85 | img = `${webConfig.fileurl}/${img}`;
86 | }
87 | }
88 | Cookies.set('qt_avatar', img, { expires: 1 });
89 | Cookies.remove('qt_username');
90 | Cookies.remove('qt_password');
91 | sdk.connection.disConnection();
92 | this.props.changeChatField({ connectStatus: '' });
93 | window.location.reload();
94 | });
95 | }
96 |
97 | render() {
98 | const { userInfo } = this.props;
99 | let img = webConfig.fileurl+'/file/v2/download/8c9d42532be9316e2202ffef8fcfeba5.png?w=80&h=80';
100 | let name = '';
101 | const info = userInfo.get(sdk.bareJid);
102 | if (info) {
103 | name = info.get('nickname') || '';
104 | img = info.get('imageurl') || '';
105 | if (!/^(https:|http:|\/\/)/g.test(img)) {
106 | img = `${webConfig.fileurl}/${img}`;
107 | }
108 | }
109 | return (
110 |
111 |
112 |

113 |
114 |
115 |
116 | {name}
117 | { this.showMenu(false); }}
120 | onMouseEnter={() => { this.showMenu(true); }}
121 | >
122 |
123 |
137 |
138 |
139 |
140 |
141 | );
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/ui/panel/search.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import LazyLoad from 'react-lazyload';
4 | import Cookies from 'js-cookie';
5 | // import cls from 'classnames';
6 | import actions from '../../actions';
7 | import sdk from '../../sdk';
8 |
9 | @connect(
10 | state => ({
11 | currentSession: state.getIn(['chat', 'currentSession'])
12 | }),
13 | actions
14 | )
15 | export default class Search extends Component {
16 | constructor() {
17 | super();
18 | this.state = {
19 | showModal: false,
20 | value: '',
21 | result: []
22 | };
23 | }
24 |
25 | componentDidMount() {
26 | }
27 |
28 | onClearClick() {
29 | this.setState({
30 | value: '',
31 | showModal: false
32 | });
33 | }
34 |
35 | onSessionClick = async (data) => {
36 | const {
37 | setUserInfo,
38 | setCurrentSession,
39 | changeChatField
40 | } = this.props;
41 | this.setState({
42 | value: '',
43 | showModal: false
44 | });
45 | if (data.mFlag === '2') {
46 | const res = await sdk.getGroupCard([data.user]);
47 | if (res.ret) {
48 | setUserInfo(res.data);
49 | }
50 | }
51 | setCurrentSession(data);
52 | changeChatField({ switchIndex: 'chat' });
53 | window.QtalkSDK.$('.session').scrollTop(0);
54 | }
55 |
56 | onChange = (e) => {
57 | const val = e.target.value.trim();
58 | this.setState({ value: val });
59 | clearTimeout(this.timer);
60 | this.timer = setTimeout(async () => {
61 | const state = {};
62 | if (val.length > 1) {
63 | state.showModal = true;
64 | // 获取查询结果
65 | const res = await sdk.searchUser(val);
66 | if (res.data) {
67 | state.result = res.data;
68 | }
69 | } else {
70 | state.showModal = false;
71 | }
72 | this.setState(state);
73 | }, 250);
74 | }
75 |
76 | timer = null;
77 | renderResult = () => {
78 | const { result } = this.state;
79 | if (result && result.length < 1) {
80 | return null;
81 | }
82 | return (
83 |
107 | );
108 | }
109 | render() {
110 | return (
111 |
112 |
113 |
{ setTimeout(() => { this.setState({ showModal: false }); }, 300); }}
120 | />
121 | {
122 | this.state.showModal &&
123 |
124 | {this.renderResult()}
125 | {/*
找不到?尝试打开会话
126 |
this.onSessionClick({ user: this.state.value, mFlag: '1' })}>打开ID为[{this.state.value}]的对话
*/}
127 |
128 | }
129 |
130 | );
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/ui/panel/tab.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import cls from 'classnames';
4 | import Session from './session';
5 | import Friends from './friends';
6 | import actions from '../../actions';
7 | // import sdk from '../../sdk';
8 |
9 | @connect(
10 | state => ({
11 | switchIndex: state.getIn(['chat', 'switchIndex'])
12 | }),
13 | actions
14 | )
15 | export default class Tab extends Component {
16 | onSwitch(switchIndex) {
17 | this.props.changeChatField({ switchIndex });
18 | }
19 |
20 | render() {
21 | const { switchIndex } = this.props;
22 | return (
23 |
24 |
25 |
{ this.onSwitch('chat'); }}>
26 |
27 |
28 |
29 |
30 |
{ this.onSwitch('friends'); }}>
31 |
32 |
33 |
34 |
35 |
36 |
37 |
{ this.onSwitch('chat'); }}
39 | show={switchIndex === 'friends'}
40 | />
41 |
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/ui/tree/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: xi.guo
4 | * @Date: 2019-08-06 11:24:03
5 | * @LastEditTime: 2019-08-13 12:10:29
6 | * @LastEditors: Please set LastEditors
7 | */
8 | import React, { Component } from 'react';
9 | import { connect } from 'react-redux';
10 | import cls from 'classnames';
11 | import actions from '../../actions';
12 | import './index.less';
13 | import sdk from '../../sdk';
14 |
15 | const webConfig = {
16 | fileurl: startalkNav.baseaddess && startalkNav.baseaddess.fileurl,
17 | domain: startalkNav.baseaddess && startalkNav.baseaddess.domain
18 | }
19 |
20 | @connect(
21 | state => ({
22 | userInfo: state.get('userInfo')
23 | }),
24 | actions
25 | )
26 | class Tree extends Component {
27 | constructor(props) {
28 | super(props);
29 | this.state = {
30 | tree: {}, // { key: true } key=true 展开 false 闭合
31 | selected: {}, // { key: true } key=true 被选择的
32 | searchTree: {} // { key: true } key=true 搜索的结果
33 | };
34 | }
35 |
36 | componentWillReceiveProps(nextProps) {
37 | const { selected, tree, searchTree } = this.state;
38 | if (nextProps.data.length === 0) {
39 | return null;
40 | }
41 | this.setState({
42 | selected: nextProps.selected ? nextProps.selected : selected,
43 | tree: nextProps.tree ? nextProps.tree : tree,
44 | searchTree: nextProps.searchTree ? nextProps.searchTree : searchTree
45 | });
46 | return true;
47 | }
48 |
49 | onTreeClick(e, data, disable) {
50 | e.stopPropagation();
51 | if (disable) {
52 | return null;
53 | }
54 | const { key } = data;
55 | const { tree } = this.state;
56 | const newTree = Object.assign({}, tree, {
57 | [key]: !tree[key]
58 | });
59 | this.setState({
60 | tree: newTree
61 | });
62 | let u = '';
63 | if (data.U && this.props.onClick) {
64 | u = `${data.U}@${webConfig.domain}`;
65 | }
66 | if (data.children) {
67 | const users = [];
68 | data.children.forEach((item) => {
69 | if (item.U) {
70 | users.push(`${item.U}@${webConfig.domain}`);
71 | }
72 | });
73 | if (users.length > 0) {
74 | this.cacheUserCard(users);
75 | }
76 | }
77 | this.props.onClick(u, newTree);
78 | if (this.props.showSelect && !data.children) {
79 | this.onSelect(data);
80 | }
81 | return true;
82 | }
83 |
84 | onSelect(item) {
85 | const { selected } = this.state;
86 | const newSelected = Object.assign(
87 | {},
88 | selected,
89 | {
90 | [item.key]: {
91 | flag: !(selected[item.key] && selected[item.key].flag),
92 | value: item
93 | }
94 | }
95 | );
96 | this.setState({
97 | selected: newSelected
98 | });
99 |
100 | this.props.onSelect(newSelected);
101 | }
102 |
103 | async cacheUserCard(users) {
104 | const { setUserInfo } = this.props;
105 | const res = await sdk.getUserCard(users);
106 | if (res.ret) {
107 | setUserInfo(res.data);
108 | }
109 | return res;
110 | }
111 |
112 | imgError(e) {
113 | e.target.src = webConfig.fileurl+'/file/v2/download/8c9d42532be9316e2202ffef8fcfeba5.png';//darlyn'
114 | }
115 |
116 | renderItems(data) {
117 | const { selected, searchTree } = this.state;
118 | const { showSelect, noSelected } = this.props;
119 | return (
120 |
121 | {
122 | data.map((item) => {
123 | const tree = this.state.tree[item.key];
124 | const active = !item.children && selected[item.key] && selected[item.key].flag;
125 | const disable = showSelect && !item.children && noSelected[item.U];
126 | if (Object.keys(searchTree).length > 0 && tree === undefined) {
127 | return null;
128 | }
129 | return (
130 | - this.onTreeClick(e, item, disable)}
133 | className={cls('tree-list-item', {
134 | deep: !item.children,
135 | disabled: showSelect && !item.children && noSelected[item.U]
136 | })}
137 | >
138 | {
139 | item.children ?
140 | :
146 |
151 | }
152 | {item.text}
153 | {
154 | showSelect && !item.children && !noSelected[item.U] &&
155 |
158 | }
159 | {
160 | tree && item.children && this.renderItems(item.children)
161 | }
162 |
163 | );
164 | })
165 | }
166 |
167 | );
168 | }
169 |
170 | render() {
171 | const { data } = this.props;
172 | return (
173 |
174 | {this.renderItems(data)}
175 |
176 | );
177 | }
178 | }
179 |
180 | const noob = () => {};
181 | Tree.defaultProps = {
182 | className: '',
183 | data: [],
184 | showSelect: false,
185 | onSelect: noob,
186 | onClick: '',
187 | selected: [],
188 | noSelected: {}
189 | };
190 |
191 | export default Tree;
192 |
--------------------------------------------------------------------------------
/src/web/app/pages/index/ui/tree/index.less:
--------------------------------------------------------------------------------
1 | .tree {
2 | min-height: 22px;
3 | padding: 15px 0;
4 | cursor: pointer;
5 | &-list {
6 | width: 248px;
7 | min-height: 40px;
8 | z-index: 1;
9 | &-item {
10 | width: 248px;
11 | min-height: 40px;
12 | float: left;
13 | position: relative;
14 | &.deep {
15 | padding: 9px 0;
16 | }
17 | &.search {
18 | .text {
19 | color: #E92F2F;
20 | }
21 | }
22 | &.disabled {
23 | // pointer-events: none
24 | .text {
25 | color: #9E9E9E;
26 | }
27 | }
28 | .iconfont {
29 | float: left;
30 | color: #45CF8E;
31 | font-size: 14px;
32 | vertical-align: middle;
33 | margin-top: 9px;
34 | }
35 | .iconfont.nike {
36 | font-size: 24px;
37 | color: #BDBDBD;
38 | position: absolute;
39 | right: 10px;
40 | top: 50%;
41 | margin-top: -24px;
42 | cursor: pointer;
43 | &.active {
44 | color: #45CF8E;
45 | }
46 | }
47 | .text {
48 | width: 166px;
49 | height: 40px;
50 | line-height: 40px;
51 | float: left;
52 | font-weight: 400;
53 | font-size: 14px;
54 | margin-left: 6px;
55 | overflow: hidden;
56 | text-overflow:ellipsis;
57 | white-space: nowrap;
58 | word-wrap: normal;
59 | user-select:none;
60 | &.text-active{
61 | color: #45CF8E;
62 | }
63 | }
64 | img {
65 | width: 45px;
66 | height: 45px;
67 | border-radius: 50%;
68 | float: left;
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/web/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import './styles/index.css'
4 |
5 | ReactDOM.render(
6 | hello
,
7 | document.getElementById('app')
8 | )
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/web/sdk/common/assets/20180423_qtalk_msg.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/startalk_web/2278d146bd4220f45639391d9e8ac3e9f0268407/src/web/sdk/common/assets/20180423_qtalk_msg.mp3
--------------------------------------------------------------------------------
/src/web/sdk/common/lib/strophejs-plugin-iexdomain.js:
--------------------------------------------------------------------------------
1 | import { Strophe } from 'strophe.js';
2 |
3 | Strophe.addConnectionPlugin('iexdomain', {
4 | init: function(conn) {
5 | // replace Strophe.Request._newXHR with new IE CrossDomain version
6 | var nativeXHR = new XMLHttpRequest();
7 | if (window.XDomainRequest && ! ("withCredentials" in nativeXHR)) {
8 | Strophe.Request.prototype._newXHR = function() {
9 | var xhr = new XDomainRequest();
10 | xhr.setRequestHeader = function () {} ;
11 | xhr.readyState = 0;
12 |
13 | xhr.onreadystatechange = this.func.bind(undefined, this);
14 | xhr.onerror = function() {
15 | xhr.readyState = 4;
16 | xhr.status = 500;
17 | xhr.onreadystatechange(xhr.responseText);
18 | };
19 | xhr.ontimeout = function() {
20 | xhr.readyState = 4;
21 | xhr.status = 0;
22 | xhr.onreadystatechange(xhr.responseText);
23 | };
24 | xhr.onload = function() {
25 | xhr.readyState = 4;
26 | xhr.status = 200;
27 | var _response = xhr.responseText;
28 | var _xml = new ActiveXObject('Microsoft.XMLDOM');
29 | _xml.async = 'false';
30 | _xml.loadXML(_response);
31 | xhr.responseXML = _xml;
32 | xhr.onreadystatechange(_response);
33 | };
34 | return xhr;
35 | };
36 | }
37 | // else {
38 | // console.info("Browser doesnt support XDomainRequest." + " Falling back to native XHR implementation.");
39 | // }
40 | }
41 | });
--------------------------------------------------------------------------------
/src/web/sdk/common/utils/messageHelper.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: your name
4 | * @Date: 2019-08-05 21:14:24
5 | * @LastEditTime: 2019-08-12 17:02:36
6 | * @LastEditors: Please set LastEditors
7 | */
8 | import { getEmoticonsUrl, createUUID } from './utils';
9 | import $ from 'jquery';
10 |
11 | const sdkConfig = {
12 | fileurl: startalkNav.baseaddess && startalkNav.baseaddess.fileurl
13 | }
14 | /**
15 | * 消息转换处理
16 | */
17 |
18 | // 取出 type value width height
19 | const OBJ_RE = /\[obj type=\"(.*?)\" value=\"\[?(.*?)\]?\"( width=(.*?) height=(.*?))?.*?\]/g;
20 | const URL_RE = /\b(https?:\/\/|www\.|https?:\/\/www\.)[^\s<]{2,200}\b/g;
21 | const IMG_RE = /<(img|IMG) src=\"(.*?)\" data-emoticon=\"(.*?)\".*?>/g;
22 |
23 | /**
24 | * 编码
25 | *
[obj type="img" value="src"]
26 | * url => [obj type="url" value="url"]
27 | * => ' '
28 | *
29 | */
30 | const encode = (content) => {
31 | const objcache = {};
32 | content = content.replace(/ /g, ' '); // 空格
33 | content = content.replace(/\n\s*\n/g, '\n'); // 回车
34 | content = content.replace(/\n/g, '');
35 | content = content.replace(/
]*>/ig, '\n'); // 回车
36 | // 只保留 img
37 | content = content.replace(/(<[^>]*>)/g, ($0, $1) => {
38 | // 因为要替换 url ,图片地址也属于 url,所以先把img替换掉并缓存起来
39 | if (/
]*>/gi.test($1)) {
40 | const uuid = createUUID();
41 | let src = $1.match(/src="(.*?)"/);
42 | let emoticon = $1.match(/data-emoticon="(.*?)"/);
43 | let categery = $1.match(/data-categery="(.*?)"/);
44 | let type = $1.match(/data-type="(.*?)"/);
45 | if (!src && !src[1]) {
46 | return '';
47 | }
48 | src = src[1];
49 | if (emoticon && emoticon[1]) {
50 | emoticon = `[${emoticon[1]}]`;
51 | }
52 | if (categery && categery[1]) {
53 | categery = categery[1];
54 | }
55 | if (type && type[1]) {
56 | type = type[1];
57 | }
58 | // 表情
59 | if (emoticon && categery) {
60 | objcache[uuid] = `[obj type="emoticon" value="${emoticon}" width=${categery} height=0]`;
61 | } else if (type === 'base64') {
62 | objcache[uuid] = `[obj type="base64" value="${src}"]`;
63 | } else {
64 | objcache[uuid] = `[obj type="image" value="${src}"]`;
65 | }
66 | return uuid;
67 | } else {
68 | return '';
69 | }
70 | });
71 | // 兼容客户端,需要转回来
72 | content = content.replace(/</g, '<');
73 | content = content.replace(/>/g, '>');
74 | content = content.replace(/"/g, '\'');
75 | content = content.replace(/&/g, '&');
76 | // 编码URL
77 | const list = content.match(URL_RE);
78 | if (list) {
79 | for (let i = 0; i < list.length;) {
80 | const prot = list[i].indexOf('http://') === 0 || list[i].indexOf('https://') === 0 ? '' : 'http://';
81 | const escapedUrl = encodeURI(decodeURI(list[i])).replace(/[!'()]/g, escape).replace(/\*/g, '%2A');
82 | const uuid = createUUID();
83 | objcache[uuid] = `[obj type="url" value="${prot}${escapedUrl}"]`;
84 | content = content.replace(list[i], uuid);
85 | i += 1;
86 | }
87 | }
88 | // 把img替换回来
89 | Object.keys(objcache).forEach((key) => {
90 | content = content.replace(key, objcache[key]);
91 | });
92 | return $.trim(content);
93 | };
94 |
95 | // 解码
96 | const decode = (content, msgType) => {
97 | if (!content) {
98 | return '';
99 | }
100 | if (msgType.toString() === '5') {
101 | let file;
102 | try {
103 | file = JSON.parse(content);
104 | } catch (e) {
105 | return content;
106 | }
107 | const { FileName, HttpUrl } = file;
108 | let url = HttpUrl;
109 | if (url.indexOf('http') === -1) {
110 | url = sdkConfig.fileurl + url;
111 | }
112 | // 文件都是单行数据
113 | return `${FileName}`;
114 | }
115 | content = content.replace(//g, '>');
116 | content = content.replace(OBJ_RE, (...args) => {
117 | if (args && args.length > 2) {
118 | let ret = args[0];
119 | const type = args[1];
120 | let val = args[2];
121 | const width = args[4];
122 | switch (type) {
123 | case 'image':
124 | if (val.indexOf('http') === -1) {
125 | val = sdkConfig.fileurl + val;
126 | }
127 | ret = `
`;
128 | break;
129 | case 'emoticon':
130 | ret = `
`;
131 | break;
132 | case 'url':
133 | ret = `${val}`;
134 | break;
135 | }
136 | return ret;
137 | }
138 | });
139 | return content;
140 | };
141 |
142 | /**
143 | * 过滤
144 | * [obj type="image" ...] => [图片]
145 | * [obj type="file" ...] => [文件]
146 | * [obj type="emoticon" ...] => [表情]
147 | */
148 | const filter = (content, msgType = '') => {
149 | if (!content) {
150 | return '';
151 | }
152 | if (msgType.toString() === '5') {
153 | return '[文件]';
154 | } else if (msgType.toString() === '666') {
155 | return '[分享]';
156 | }else if (content.indexOf('[') > -1) {
157 | content = content.replace(OBJ_RE, (...args) => {
158 | if (args && args.length > 2) {
159 | let ret = args[0];
160 | const type = args[1];
161 | if (type === 'image') {
162 | ret = '[图片]';
163 | } else if (type === 'emoticon') {
164 | ret = '[表情]';
165 | } else if (type === 'url') {
166 | ret = '[url]';
167 | }
168 | return ret;
169 | }
170 | });
171 | }
172 | return content;
173 | };
174 |
175 | export default {
176 | encode,
177 | decode,
178 | filter
179 | };
180 |
--------------------------------------------------------------------------------
/src/web/sdk/common/utils/publicEncrypt.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: your name
4 | * @Date: 2019-08-05 21:14:24
5 | * @LastEditTime: 2019-08-12 13:22:46
6 | * @LastEditors: Please set LastEditors
7 | */
8 | import parseKeys from 'parse-asn1';
9 | import createHash from 'create-hash';
10 | import bn from 'bn.js';
11 | import crt from 'browserify-rsa';
12 | import randomBytes from './randomBytes';
13 |
14 | function withPublic(paddedMsg, key) {
15 | return new Buffer(paddedMsg
16 | .toRed(bn.mont(key.modulus))
17 | .redPow(new bn(key.publicExponent))
18 | .fromRed()
19 | .toArray());
20 | }
21 |
22 | function mgf(seed, len) {
23 | var t = new Buffer('');
24 | var i = 0, c;
25 | while (t.length < len) {
26 | c = i2ops(i++);
27 | t = Buffer.concat([t, createHash('sha1').update(seed).update(c).digest()]);
28 | }
29 | return t.slice(0, len);
30 | }
31 |
32 | function i2ops(c) {
33 | var out = new Buffer(4);
34 | out.writeUInt32BE(c,0);
35 | return out;
36 | }
37 |
38 | function xor(a, b) {
39 | var len = a.length;
40 | var i = -1;
41 | while (++i < len) {
42 | a[i] ^= b[i];
43 | }
44 | return a
45 | }
46 |
47 |
48 | function oaep(key, msg){
49 | var k = key.modulus.byteLength();
50 | var mLen = msg.length;
51 | var iHash = createHash('sha1').update(new Buffer('')).digest();
52 | var hLen = iHash.length;
53 | var hLen2 = 2 * hLen;
54 | if (mLen > k - hLen2 - 2) {
55 | throw new Error('message too long');
56 | }
57 | var ps = new Buffer(k - mLen - hLen2 - 2);
58 | ps.fill(0);
59 | var dblen = k - hLen - 1;
60 | var seed = randomBytes(hLen);
61 | var maskedDb = xor(Buffer.concat([iHash, ps, new Buffer([1]), msg], dblen), mgf(seed, dblen));
62 | var maskedSeed = xor(seed, mgf(maskedDb, hLen));
63 | return new bn(Buffer.concat([new Buffer([0]), maskedSeed, maskedDb], k));
64 | }
65 | function pkcs1(key, msg, reverse){
66 | var mLen = msg.length;
67 | var k = key.modulus.byteLength();
68 | if (mLen > k - 11) {
69 | throw new Error('message too long');
70 | }
71 | var ps;
72 | if (reverse) {
73 | ps = new Buffer(k - mLen - 3);
74 | ps.fill(0xff);
75 | } else {
76 | ps = nonZero(k - mLen - 3);
77 | }
78 | return new bn(Buffer.concat([new Buffer([0, reverse?1:2]), ps, new Buffer([0]), msg], k));
79 | }
80 | function nonZero(len, crypto) {
81 | var out = new Buffer(len);
82 | var i = 0;
83 | var cache = randomBytes(len*2);
84 | var cur = 0;
85 | var num;
86 | while (i < len) {
87 | if (cur === cache.length) {
88 | cache = randomBytes(len*2);
89 | cur = 0;
90 | }
91 | num = cache[cur++];
92 | if (num) {
93 | out[i++] = num;
94 | }
95 | }
96 | return out;
97 | }
98 |
99 | export default (public_key, msg, reverse) => {
100 | var padding;
101 | if (public_key.padding) {
102 | padding = public_key.padding;
103 | } else if (reverse) {
104 | padding = 1;
105 | } else {
106 | padding = 4;
107 | }
108 | var key = parseKeys(public_key);
109 | var paddedMsg;
110 | if (padding === 4) {
111 | paddedMsg = oaep(key, msg);
112 | } else if (padding === 1) {
113 | paddedMsg = pkcs1(key, msg, reverse);
114 | } else if (padding === 3) {
115 | paddedMsg = new bn(msg);
116 | if (paddedMsg.cmp(key.modulus) >= 0) {
117 | throw new Error('data too long for modulus');
118 | }
119 | } else {
120 | throw new Error('unknown padding');
121 | }
122 | if (reverse) {
123 | return crt(paddedMsg, key);
124 | } else {
125 | return withPublic(paddedMsg, key);
126 | }
127 | };
128 |
129 |
--------------------------------------------------------------------------------
/src/web/sdk/common/utils/randomBytes.js:
--------------------------------------------------------------------------------
1 | import getRandomValues from 'polyfill-crypto.getrandomvalues';
2 | // if (!window.crypto) {
3 | // window.crypto = { getRandomValues };
4 | // }
5 |
6 |
7 | // function oldBrowser () {
8 | // throw new Error('Secure random number generation is not supported by this browser.\nUse Chrome, Firefox or Internet Explorer 11')
9 | // }
10 |
11 | var Buffer = require('safe-buffer').Buffer
12 | var crypto = global.crypto || global.msCrypto
13 |
14 | // if (crypto && crypto.getRandomValues) {
15 | export default randomBytes
16 | // } else {
17 | // module.exports = oldBrowser
18 | // }
19 |
20 | function randomBytes (size, cb) {
21 | // phantomjs needs to throw
22 | if (size > 65536) throw new Error('requested too many random bytes')
23 | // in case browserify isn't using the Uint8Array version
24 | var rawBytes = new global.Uint8Array(size)
25 |
26 | // This will not work in older browsers.
27 | // See https://developer.mozilla.org/en-US/docs/Web/API/window.crypto.getRandomValues
28 | if (size > 0) { // getRandomValues fails on IE if size == 0
29 | if (crypto && crypto.getRandomValues) {
30 | crypto.getRandomValues(rawBytes)
31 | } else {
32 | getRandomValues(rawBytes);
33 | }
34 | }
35 |
36 | // XXX: phantomjs doesn't like a buffer being passed here
37 | var bytes = Buffer.from(rawBytes.buffer)
38 |
39 | if (typeof cb === 'function') {
40 | return process.nextTick(function () {
41 | cb(null, bytes)
42 | })
43 | }
44 |
45 | return bytes
46 | }
47 |
--------------------------------------------------------------------------------
/src/web/sdk/common/utils/utils.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: your name
4 | * @Date: 2019-08-05 21:14:24
5 | * @LastEditTime: 2019-08-12 17:03:03
6 | * @LastEditors: Please set LastEditors
7 | */
8 | import $ from 'jquery';
9 |
10 | const sdkConfig = {
11 | fileurl: startalkNav.baseaddess && startalkNav.baseaddess.fileurl
12 | }
13 |
14 | const checkUpLoadFileExist = (url) => {
15 | return $.ajax({
16 | url,
17 | type: 'GET',
18 | dataType: 'json',
19 | data: {},
20 | jsonp: 'callback'
21 | });
22 | };
23 |
24 | const isObject = (o) => {
25 | return Object.prototype.toString.call(o) === '[object Object]';
26 | };
27 | /**
28 | * 配置混入
29 | * originObject => { a: 1, b: { c: 1, d: 2}}
30 | * newObject = { a: 2, b: {c: 2} }
31 | * return => {a: 1, b: { c:2, d: 2}}
32 | */
33 | const configMix = (originObject, newObject) => {
34 | const ret = Object.assign({}, originObject);
35 | Object.keys(newObject).forEach((key) => {
36 | if (ret[key] === undefined) {
37 | return;
38 | }
39 | if (isObject(ret[key])) {
40 | ret[key] = configMix(ret[key], newObject[key]);
41 | } else {
42 | ret[key] = newObject[key];
43 | }
44 | });
45 | return ret;
46 | };
47 |
48 | const isSupportWebSocket = !!(window.WebSocket && window.WebSocket.prototype.send);
49 |
50 | /**
51 | * 反转枚举
52 | */
53 | const reverseEnum = (o) => {
54 | const reverse = {};
55 | Object.keys(o).forEach((key) => {
56 | reverse[o[key]] = key;
57 | });
58 | return reverse;
59 | };
60 |
61 | const createUUID = () => {
62 | let d = new Date().getTime();
63 | const uuid = 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
64 | var r = (d + Math.random() * 16) % 16 | 0;
65 | d = Math.floor(d / 16);
66 | return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16);
67 | });
68 | return uuid.toUpperCase();
69 | };
70 |
71 | const getEmoticonsUrl = (shortcut, category = 'EmojiOne') => {
72 | if (!shortcut || shortcut.length == 0) {
73 | return '';
74 | }
75 |
76 | return sdkConfig.fileurl+`/file/v1/emo/d/e/${category}/${shortcut.replace('/', '')}/org`;
77 | };
78 |
79 | const bytesToMB = (bytes) => {
80 | if (bytes === 0) {
81 | return '1';
82 | }
83 | const m = 1024 * 1024;
84 | let result = Math.floor(bytes / m);
85 | if (result < 1) { // 如果小于1都为1
86 | result = 1;
87 | }
88 | return result;
89 | };
90 |
91 | const bytesToSize = (bytes) => {
92 | if (bytes === 0) {
93 | return '0 B';
94 | }
95 | const k = 1024;
96 | const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
97 | const i = Math.floor(Math.log(bytes) / Math.log(k));
98 | return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i];
99 | };
100 |
101 | const audioPlayer = (() => {
102 | let player;
103 | const id = createUUID();
104 | // const file = sdkConfig.fileurl+'/zhuanti/20180423_qtalk_msg.mp3';
105 | const file = "../assets/20180423_qtalk_msg.mp3";
106 | const init = () => {
107 | if (!player) {
108 | player = window.document.createElement('audio');
109 | player.id = id;
110 | const mp3 = document.createElement('source');
111 | mp3.src = file;
112 | mp3.type = 'audio/mpeg';
113 | player.appendChild(mp3);
114 | window.document.body.appendChild(player);
115 | }
116 | };
117 | return () => {
118 | init();
119 | player.play();
120 | };
121 | })();
122 |
123 | const getCookie = (name) => {
124 | const reg = new RegExp(`(^| )${name}=([^;]*)(;|$)`);
125 | const arr = window.document.cookie.match(reg);
126 | if (arr) {
127 | return unescape(arr[2]);
128 | }
129 | return null;
130 | };
131 |
132 | // 将base64转换为文件
133 | const dataURLtoFile = (dataurl, filename) => {
134 | const arr = dataurl.split(',');
135 | const mime = arr[0].match(/:(.*?);/)[1];
136 | const bstr = atob(arr[1]);
137 | let n = bstr.length;
138 | const u8arr = new Uint8Array(n);
139 | // eslint-disable-next-line
140 | while (n--) {
141 | u8arr[n] = bstr.charCodeAt(n);
142 | }
143 | return new window.File([u8arr], filename, { type: mime });
144 | };
145 |
146 | const utils = {
147 | configMix,
148 | isObject,
149 | isSupportWebSocket,
150 | reverseEnum,
151 | createUUID,
152 | getEmoticonsUrl,
153 | bytesToMB,
154 | bytesToSize,
155 | checkUpLoadFileExist,
156 | audioPlayer,
157 | getCookie,
158 | dataURLtoFile
159 | }
160 |
161 | export {
162 | configMix,
163 | isObject,
164 | isSupportWebSocket,
165 | reverseEnum,
166 | createUUID,
167 | getEmoticonsUrl,
168 | bytesToMB,
169 | bytesToSize,
170 | checkUpLoadFileExist,
171 | audioPlayer,
172 | getCookie,
173 | dataURLtoFile
174 | };
175 |
176 | export default utils
177 |
--------------------------------------------------------------------------------
/src/web/sdk/core/auth.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: chaos.dong
4 | * @Date: 2019-10-25 21:14:24
5 | * @LastEditTime: 2019-08-13 17:32:30
6 | * @LastEditors: Please set LastEditors
7 | */
8 | import dayjs from 'dayjs';
9 | import publicEncrypt from '../common/utils/publicEncrypt';
10 | import axios from 'axios';
11 |
12 | const pubKeyFullkey = startalkKeys.pub_key_fullkey
13 | const webConfig = {
14 | loginType: startalkNav.Login && startalkNav.Login.loginType,
15 | domain: startalkNav.baseaddess && startalkNav.baseaddess.domain,
16 | }
17 |
18 | /**
19 | * 密码加密
20 | */
21 |
22 | // 公钥 sdkConfig.pub_key_fullkey
23 |
24 | const encrypt = raw => (
25 | publicEncrypt({
26 | key: pubKeyFullkey,
27 | padding: 1
28 | }, raw).toString('base64')
29 | );
30 |
31 | function generateUUID() {
32 | var d = new Date().getTime();
33 | var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
34 | var r = (d + Math.random() * 16) % 16 | 0;
35 | d = Math.floor(d / 16);
36 | return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
37 | });
38 | return uuid;
39 | };
40 |
41 | export default (username, password) => {
42 | if (webConfig.loginType === 'password') {
43 | const uinfo = {
44 | p: password,
45 | a: 'testapp',
46 | u: username,
47 | d: dayjs().format('YYYY-MM-DD HH:mm:ss')
48 | };
49 | // eslint-disable-next-line
50 | const encrypted = encrypt(new Buffer(JSON.stringify(uinfo)));
51 | return encrypted.toString('base64');
52 |
53 | } else if (webConfig.loginType === 'newpassword') {
54 | const newEncrypted = encrypt(new Buffer(password));
55 | const requestData = {
56 | p: newEncrypted,
57 | h: webConfig.domain,
58 | u: username,
59 | mk: generateUUID(),
60 | plat: "web"
61 | };
62 |
63 | return new Promise((resolve, reject) => {
64 | axios({
65 | url: '/newapi/nck/qtlogin.qunar',
66 | method: 'POST',
67 | data: requestData
68 | }).then(res => {
69 | const data = [res.data.data.t,requestData.mk]
70 | resolve(data);
71 | }).catch(e => {
72 | console.info(e)
73 | })
74 | })
75 | }
76 | };
77 |
--------------------------------------------------------------------------------
/src/web/sdk/core/buildMessage.js:
--------------------------------------------------------------------------------
1 | import { Strophe } from './strophe';
2 | import messageHelper from '../common/utils/messageHelper';
3 |
4 | /**
5 | * 生成公共消息内容
6 | */
7 | export default (data, myId) => {
8 | let isMe = false;
9 | const { message, body, from, t, muc, carbonMessage, readType } = data; // read_flag
10 | const { type, sendjid } = message;
11 | const { id, msgType, content, backupinfo, extendInfo } = body;
12 | const fromName = Strophe.getBareJidFromJid(sendjid || '');
13 | let sj = '';
14 | let ids = [];
15 | if (type === 'chat') {
16 | // carbonMessage --> 抄送消息,from to 是反得,实际上是自己发的
17 | if (from === myId || carbonMessage) {
18 | isMe = true;
19 | }
20 | sj = from;
21 | } else if (type === 'groupchat') {
22 | if (fromName === myId) {
23 | isMe = true;
24 | }
25 | sj = fromName;
26 | } else if (type === 'readmark') {
27 | try {
28 | ids = JSON.parse(content);
29 | } catch (e) {
30 | ids = [];
31 | }
32 | ids = ids.map(item => item.id);
33 | } else if (type === 'revoke') {
34 | if (muc) {
35 | sj = message.from;
36 | } else {
37 | sj = from;
38 | }
39 | }
40 | return {
41 | // 公共
42 | id, // 消息ID,
43 | msgType, // 消息类型
44 | content: messageHelper.decode(content, msgType), // 消息内容
45 | simpcontent: messageHelper.filter(content, msgType),
46 | time: t * 1000, // 消息时间
47 | isMe, // 是否自己
48 | type, // 消息类型 groupchat 群消息, chat 单人消息
49 | sendjid: sj, // 发送者的jid
50 | backupinfo,
51 | extendInfo,
52 | carbonMessage, // 是否抄送
53 |
54 | // 单聊属性
55 | isRead: Math.floor(data.read_flag / 2) % 2 === 1,
56 |
57 | // 群属性
58 | muc, // 群ID
59 |
60 | // readmark
61 | readType,
62 | ids
63 |
64 | // revoke
65 | };
66 | };
67 |
--------------------------------------------------------------------------------
/src/web/sdk/core/connection.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: xi.guo
4 | * @Date: 2019-08-05 21:14:24
5 | * @LastEditTime: 2019-08-13 17:33:34
6 | * @LastEditors: Please set LastEditors
7 | */
8 | import EventEmitter from 'events';
9 | import auth from './auth';
10 | import {
11 | isSupportWebSocket,
12 | reverseEnum
13 | } from '../common/utils/utils';
14 | import { Strophe } from './strophe';
15 | import defaultOptions from '../options';
16 |
17 | const { config: sdkConfig } = defaultOptions
18 | const webConfig = {
19 | loginType: startalkNav.Login && startalkNav.Login.loginType
20 | }
21 |
22 | /**
23 | * 链接websocket或者http-bind
24 | */
25 | class Connection extends EventEmitter {
26 | STATUS = {
27 | 0: 'ERROR', // 连接错误
28 | 1: 'CONNECTING', // 连接中
29 | 2: 'CONNFAIL', // 连接失败
30 | 3: 'AUTHENTICATING', // 认证中
31 | 4: 'AUTHFAIL', // 认证失败
32 | 5: 'CONNECTED', // 已连接
33 | 6: 'DISCONNECTED', // 断开连接
34 | 7: 'DISCONNECTING', // 断开连接中
35 | 8: 'ATTACHED', // 已连接
36 | 9: 'REDIRECT' // 连接转发
37 | };
38 |
39 | constructor(options) {
40 | super();
41 | this.options = options;
42 | this.STATUS_REVERSE = reverseEnum(this.STATUS);
43 | const { host, path, delay } = options;
44 | this.reconnectCount = 0;
45 | this.delay = delay;
46 | // let { protocol } = window.location;
47 | // let bind = 'http-bind';
48 | // if (isSupportWebSocket) {
49 | // protocol = protocol === 'https:' ? 'wss:' : 'ws:';
50 | // bind = 'websocket';
51 | // }
52 | // sdkConfig.httpurl
53 | // this.stropheConnection = new Strophe.Connection(`${protocol}//${host}${path}/${bind}`);
54 | this.stropheConnection = new Strophe.Connection(host || '');
55 | }
56 |
57 | connect(user, pwd, domain, autologin) {
58 | if (webConfig.loginType === 'password') {
59 | if (!autologin) {
60 | const buildPwd = auth(user, pwd);
61 | pwd = buildPwd;
62 | }
63 | if (!domain) {
64 | domain = sdkConfig.domain;
65 | }
66 | this.auth = { user, pwd };
67 | this.stropheConnection.connect(`${user}@${domain}`, pwd, this.onConnectStatusChange);
68 | } else if (webConfig.loginType === 'newpassword') {
69 | if (!domain) {
70 | domain = sdkConfig.domain;
71 | }
72 | if (!autologin) {
73 | auth(user, pwd).then(res => {
74 | const token = res[0];
75 | const uinfo = {
76 | nauth: {
77 | p: token,
78 | u: `${user}@${domain}`,
79 | mk: res[1]
80 | }
81 | };
82 | pwd = JSON.stringify(uinfo);
83 | this.auth = { user, pwd };
84 | this.stropheConnection.connect(`${user}@${domain}`, pwd, this.onConnectStatusChange);
85 | })
86 | } else {
87 | this.auth = { user, pwd };
88 | this.stropheConnection.connect(`${user}@${domain}`, pwd, this.onConnectStatusChange);
89 | }
90 | }
91 | }
92 |
93 | reconnect() {
94 | const { jid, pass, wait, hold, route } = this.stropheConnection;
95 | if (this.reconnectCount >= this.options.reconnectCount) {
96 | return;
97 | }
98 | setTimeout(() => {
99 | this.reconnectCount += 1;
100 | this.emit('connect:reconnect');
101 | this.stropheConnection.connect(
102 | jid,
103 | pass,
104 | this.onConnectStatusChange,
105 | wait,
106 | hold,
107 | route
108 | );
109 | this.userDisConnection = false;
110 | }, this.delay);
111 | }
112 |
113 | /**
114 | * 主动断开链接
115 | */
116 | disConnection() {
117 | this.userDisConnection = true;
118 | if (this.stropheConnection.connected) {
119 | this.stropheConnection.disconnect();
120 | }
121 | }
122 |
123 | onConnectStatusChange = (status, condition) => {
124 | status = status.toString();
125 | if (status === this.STATUS_REVERSE.CONNECTED || status === this.STATUS_REVERSE.ATTACHED) {
126 | delete this.disconnection_cause;
127 | // 已连接
128 | this.emit('connect:success', this.stropheConnection);
129 | } else if (status === this.STATUS_REVERSE.DISCONNECTED) {
130 | if (this.disconnection_cause === this.STATUS_REVERSE.CONNFAIL) {
131 | // 因为连接失败而断开连接,则 重新连接
132 | this.reconnect();
133 | } else {
134 | // 已断开连接
135 | this.emit('connect:disconnected', this.stropheConnection);
136 | }
137 | } else if (status === this.STATUS_REVERSE.AUTHFAIL) {
138 | // 认证失败
139 | this.emit('connect:authfail', status, condition);
140 | } else if (status === this.STATUS_REVERSE.CONNFAIL) {
141 | // 连接失败
142 | if (!this.userDisConnection) {
143 | this.disconnection_cause = status; // 记录连接失败原因
144 | }
145 | this.emit('connect:fail', status, condition);
146 | }
147 | this.emit('connect', status, condition);
148 | };
149 | }
150 |
151 | export default Connection;
152 |
--------------------------------------------------------------------------------
/src/web/sdk/core/emotions/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: xi.guo
4 | * @Date: 2019-08-05 21:14:24
5 | * @LastEditTime: 2019-08-13 11:55:01
6 | * @LastEditors: Please set LastEditors
7 | */
8 | import { oneEmotions } from './oneEmotions';
9 |
10 | const sdkConfig = {
11 | fileurl: startalkNav.baseaddess && startalkNav.baseaddess.fileurl
12 | }
13 |
14 | const emotions = [
15 | oneEmotions
16 | ];
17 |
18 | function getEmoticons() {
19 | const ret = [];
20 | emotions.forEach((item) => {
21 | const df = item.FACESETTING.DEFAULTFACE;
22 | const ns = df.categoryNew || df['-categery'];
23 | const child = {
24 | name: df['-name'],
25 | width: df['-width'],
26 | height: df['-height'],
27 | categery: ns,
28 | faces: []
29 | };
30 | df.FACE.forEach((f) => {
31 | const shortcut = f['-shortcut'];
32 | child.faces.push({
33 | url: sdkConfig.fileurl+`/file/v1/emo/d/e/${ns}/${shortcut.replace('/', '')}/fixed`,
34 | shortcut,
35 | tip: f['-tip']
36 | });
37 | });
38 | ret.push(child);
39 | });
40 | return ret;
41 | }
42 |
43 | export default getEmoticons();
44 |
--------------------------------------------------------------------------------
/src/web/sdk/core/index.js:
--------------------------------------------------------------------------------
1 | import Connection from './connection';
2 | import Ping from './ping';
3 | import Message from './message';
4 | import buildMessage from './buildMessage';
5 | import upload from './upload';
6 | import emotions from './emotions';
7 |
8 | export {
9 | Connection,
10 | Ping,
11 | Message,
12 | buildMessage,
13 | upload,
14 | emotions
15 | };
16 |
--------------------------------------------------------------------------------
/src/web/sdk/core/ping.js:
--------------------------------------------------------------------------------
1 | import { Strophe } from './strophe';
2 |
3 | class Ping {
4 | constructor(stropheConnection, pingInterval = 20) {
5 | this.stropheConnection = stropheConnection;
6 | this.pingInterval = pingInterval;
7 | }
8 |
9 | register() {
10 | const self = this;
11 | const { disco, ping } = self.stropheConnection;
12 |
13 | disco.addFeature(Strophe.NS.PING);
14 | ping.addPingHandler((pi) => {
15 | self.lastStanzaDate = new Date();
16 | ping.pong(pi);
17 | return true;
18 | });
19 |
20 | if (self.pingInterval > 0) {
21 | this.stropheConnection.addHandler(() => {
22 | self.lastStanzaDate = new Date();
23 | return true;
24 | });
25 | this.stropheConnection.addTimedHandler(1000, () => {
26 | const now = new Date();
27 | if (!self.lastStanzaDate) {
28 | self.lastStanzaDate = now;
29 | }
30 | const interval = (now - self.lastStanzaDate) / 1000;
31 | if (interval > self.pingInterval) {
32 | return self.ping();
33 | }
34 | return true;
35 | });
36 | }
37 | }
38 |
39 | ping(jid) {
40 | this.lastStanzaDate = new Date();
41 | if (jid === undefined) {
42 | const bareJid = Strophe.getBareJidFromJid(this.stropheConnection.jid);
43 | jid = Strophe.getDomainFromJid(bareJid);
44 | }
45 | this.stropheConnection.ping.ping(jid, null, null, null);
46 | return true;
47 | }
48 | }
49 |
50 | export default Ping;
51 |
--------------------------------------------------------------------------------
/src/web/sdk/core/strophe.js:
--------------------------------------------------------------------------------
1 | import strophe, { Strophe } from 'strophe.js';
2 | import 'strophejs-plugin-disco';
3 | import 'strophejs-plugin-ping';
4 | import 'strophejs-plugin-vcard';
5 | // ie <= 8 使用
6 | // import '../common/lib/strophejs-plugin-iexdomain';
7 |
8 | // Strophe.js => export default =>
9 | // root.Strophe = wrapper.Strophe;
10 | // root.$build = wrapper.$build;
11 | // root.$iq = wrapper.$iq;
12 | // root.$msg = wrapper.$msg;
13 | // root.$pres = wrapper.$pres;
14 | // root.SHA1 = wrapper.SHA1;
15 | // root.MD5 = wrapper.MD5;
16 | // root.b64_hmac_sha1 = wrapper.b64_hmac_sha1;
17 | // root.b64_sha1 = wrapper.b64_sha1;
18 | // root.str_hmac_sha1 = wrapper.str_hmac_sha1;
19 | // root.str_sha1 = wrapper.str_sha1;
20 |
21 | // const { Strophe } = strophe;
22 |
23 | Strophe.addNamespace('CHATSTATES', 'http://jabber.org/protocol/chatstates');
24 | Strophe.addNamespace('REGISTER', 'jabber:iq:register');
25 | Strophe.addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx');
26 | Strophe.addNamespace('XFORM', 'jabber:x:data');
27 | Strophe.addNamespace('CSI', 'urn:xmpp:csi:0');
28 |
29 | const { $msg, $iq, MD5, $pres } = strophe
30 |
31 | export { Strophe, $msg, $iq, MD5, $pres };
32 | export default strophe;
33 |
--------------------------------------------------------------------------------
/src/web/sdk/core/upload.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 | import '../common/lib/jquery.iframe.transport';
3 | import '../common/lib/jquery.fileupload';
4 | import '../common/lib/jquery.md5';
5 | import {
6 | createUUID,
7 | bytesToMB,
8 | bytesToSize,
9 | checkUpLoadFileExist
10 | } from '../common/utils/utils';
11 |
12 | const limitFileSize = 1024 * 1024 * 50; // 50M;
13 | const iframe = (() => {
14 | let ret = false;
15 | const browser = window.navigator.appName;
16 | const version = (window.navigator.appVersion || '').split(';');
17 | if (browser === 'Microsoft Internet Explorer'
18 | && version.length > 1
19 | && parseInt(version[1].replace(/[ ]/g, '').replace(/MSIE/g, ''), 10) < 10
20 | ) {
21 | ret = true;
22 | }
23 | return ret;
24 | })();
25 |
26 | // $inputImg.fileupload({
27 | // drop: function (e, data) {
28 | // $.each(data.files, function (index, file) {
29 | // alert('Dropped file: ' + file.name);
30 | // });
31 | // }
32 | // });
33 |
34 | function image(beforeFn, successFn, progressFn, filesList, onlyUrl) {
35 | const { myId, key } = this;
36 | const url = `/file/v2/upload/img?size=48&u=${myId}&k=${key}`;
37 | const $inputImg = $('');
38 | $(window.document.body).append($inputImg);
39 | $inputImg.fileupload({
40 | url,
41 | dataType: 'json',
42 | autoUpload: false,
43 | forceIframeTransport: iframe,
44 | limitMultiFileUploadSize: 1024 * 1024 * 50, // 50M
45 | add: (e, data) => {
46 | data.process().done(() => {
47 | $.each(data.files, (index, { size, name }) => {
48 | const uuid = $.md5(createUUID()); // $.md5(file.name);
49 | const sizeMB = bytesToMB(size);
50 | const paramLink = `name=${name}&size=${sizeMB}&u=${myId}&k=${key}&key=${uuid}&p=qim_web`;
51 | const newUrl = `/file/v2/upload/img?${paramLink}`;
52 | if (size > limitFileSize) {
53 | alert('图片大小不能超过50M');
54 | return;
55 | }
56 | // 设置新的提交地址
57 | data.setSubmitURL(newUrl);
58 | // 校验上传的图片是否存在
59 | // 如果图片存在了就不上传了,直接显示为和上传成功的效果
60 | const checkFileUrl = `/file/v2/inspection/img?${paramLink}`;
61 | checkUpLoadFileExist(checkFileUrl)
62 | .done(async (res) => {
63 | beforeFn();
64 | if (res.ret) {
65 | data.submit();
66 | } else {
67 | let result = res.data; // 存在的图片URL地址
68 | if (iframe) {
69 | result = $('pre', result).text();
70 | }
71 | const msg = `
`;
72 | if (onlyUrl) {
73 | successFn(result);
74 | } else {
75 | // 发送消息
76 | const ret = await this.sendMessage(msg);
77 | successFn(ret);
78 | }
79 | progressFn(100);
80 | $inputImg.remove();
81 | }
82 | });
83 | });
84 | });
85 | },
86 | done: async (e, data) => {
87 | let result = data.result.data;
88 | if (iframe) {
89 | result = $('pre', result).text();
90 | }
91 | const msg = `
`;
92 | if (onlyUrl) {
93 | successFn(result);
94 | } else {
95 | // 发送消息
96 | const ret = await this.sendMessage(msg);
97 | successFn(ret);
98 | }
99 | $inputImg.remove();
100 | },
101 | progress: (e, data) => {
102 | const progress = parseInt((data.loaded / data.total) * 100, 10);
103 | progressFn(progress);
104 | }
105 | });
106 | if (filesList && filesList.length > 0) {
107 | $inputImg.fileupload('add', { files: filesList });
108 | } else {
109 | $inputImg.trigger('click');
110 | }
111 | }
112 |
113 | function file(beforeFn, successFn, progressFn, filesList, onlyUrl) {
114 | const { myId, key } = this;
115 | const url = `/file/v2/upload/file?size=46&u=${myId}&k=${key}`;
116 | const $inputFile = $('');
117 | $(window.document.body).append($inputFile);
118 | $inputFile.fileupload({
119 | url,
120 | dataType: 'json',
121 | forceIframeTransport: iframe,
122 | add: (e, data) => {
123 | data.process().done(() => {
124 | $.each(data.files, (index, { size, name }) => {
125 | const uuid = $.md5(createUUID()); // $.md5(file.name);
126 | const sizeMB = bytesToMB(size);
127 | const paramLink = `name=${name}&size=${sizeMB}&u=${myId}&k=${key}&key=${uuid}&p=qim_web`;
128 | const newUrl = `/file/v2/upload/file?${paramLink}`;
129 | if (size > limitFileSize) {
130 | alert('图片大小不能超过50M');
131 | return;
132 | }
133 | // 设置新的提交地址
134 | data.setSubmitURL(newUrl);
135 | // 校验上传的文件是否存在
136 | // 如果文件存在了就不上传了,直接显示为和上传成功的效果
137 | const checkFileUrl = `/file/v2/inspection/file?${paramLink}`;
138 | checkUpLoadFileExist(checkFileUrl)
139 | .done(async (res) => {
140 | beforeFn();
141 | if (res.ret) {
142 | data.submit();
143 | } else {
144 | let result = res.data; // 存在的文件URL地址
145 | if (iframe) {
146 | result = $('pre', result).text();
147 | }
148 | const msg = {
149 | FILEID: new Date().getTime(),
150 | FILEMD5: '123',
151 | FileName: name,
152 | FileSize: bytesToSize(size),
153 | HttpUrl: result
154 | };
155 | if (onlyUrl) {
156 | successFn(result);
157 | } else {
158 | // 发送消息
159 | const ret = await this.sendMessage(JSON.stringify(msg), 5);
160 | successFn(ret);
161 | }
162 | progressFn(100);
163 | $inputFile.remove();
164 | }
165 | });
166 | });
167 | });
168 | },
169 | done: async (e, data) => {
170 | let result = data.result.data;
171 | if (iframe) {
172 | result = $('pre', result).text();
173 | }
174 | if (data && data.files && data.files.length > 0) {
175 | const msg = {
176 | FILEID: new Date().getTime(),
177 | FILEMD5: '123',
178 | FileName: data.files[0].name,
179 | FileSize: bytesToSize(data.files[0].size),
180 | HttpUrl: result
181 | };
182 | if (onlyUrl) {
183 | successFn(result);
184 | } else {
185 | // 发送消息
186 | const ret = await this.sendMessage(JSON.stringify(msg), 5);
187 | successFn(ret);
188 | }
189 | }
190 | $inputFile.remove();
191 | },
192 | progress: (e, data) => {
193 | const progress = parseInt((data.loaded / data.total) * 100, 10);
194 | progressFn(progress);
195 | }
196 | });
197 | if (filesList && filesList.length > 0) {
198 | $inputFile.fileupload('add', { files: filesList });
199 | } else {
200 | $inputFile.trigger('click');
201 | }
202 | }
203 |
204 | export default {
205 | image,
206 | file
207 | };
208 |
--------------------------------------------------------------------------------
/src/web/sdk/options.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: your name
4 | * @Date: 2019-08-05 21:14:24
5 | * @LastEditTime: 2019-08-13 11:47:33
6 | * @LastEditors: Please set LastEditors
7 | */
8 | export default {
9 | debug: false,
10 | xmpp:"",
11 | // 链接配置
12 | connect: {
13 | reconnectCount: 10, // 最多重连10次
14 | delay: 5000, // 重新连接间隔
15 | host: '', // 主机名
16 | // port: 80, // 端口
17 | path: '' // 路径
18 | },
19 | maType: 6, // 平台类型web端:6
20 | // 20 秒ping一次
21 | pingInterval: 20
22 | };
23 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description: In User Settings Edit
3 | * @Author: xi.guo
4 | * @Date: 2019-08-05 14:54:15
5 | * @LastEditTime: 2019-08-13 19:54:57
6 | * @LastEditors: Please set LastEditors
7 | */
8 |
9 | const MiniCssExtractPlugin = require("mini-css-extract-plugin")
10 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
11 | const CopyPlugin = require('copy-webpack-plugin')
12 | const merge = require('webpack-merge')
13 | const path = require('path')
14 | const dotenv = require('dotenv')
15 | dotenv.config()
16 | process.env.NODE_ENV === 'production' && require('./dotenv')
17 | const _mode = process.env.NODE_ENV || 'development'
18 | const _mergeConfig = require(`./config/webpack.${_mode}.js`)
19 | const HtmlWebpackPlugin = require('html-webpack-plugin')
20 | const htmlAfterWebpackPlugin = require('./config/htmlAfterWebpackPlugin')
21 | const ASSETS = path.join(__dirname, '/dist/assets/')
22 |
23 | // 用户可配置 config
24 | const devPaths ={
25 | COMMON: path.join(__dirname , '/src/web/common'),
26 | COMPONENTS: path.join(__dirname , '/src/web/components'),
27 | CONTAINERS: path.join(__dirname , '/src/web/containers'),
28 | HOC: path.join(__dirname , '/src/web/hoc'),
29 | IMAGES: path.join(__dirname , '/src/web/images'),
30 | MODULES: path.join(__dirname , '/src/web/modules'),
31 | STORE: path.join(__dirname , '/src/web/store'),
32 | CONFIG: path.join(__dirname , '/src/web/config'),
33 | strophe: 'strophe.js'
34 | }
35 |
36 | // 用户可配置 config
37 | const entry = () => {
38 | return {
39 | _startalk_sdk: './src/web/sdk/entry.js',
40 | index: './src/web/app/pages/index/entry.js'
41 | }
42 | }
43 |
44 | let webpackConfig = {
45 | mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
46 | entry: entry(),
47 | output: {
48 | filename: 'scripts/[name].js',
49 | // 静态资源输出路径
50 | path: ASSETS,
51 | // 所有资源的基础路径
52 | publicPath: process.env.PUBLICPATH
53 | },
54 | module: {
55 | rules: [
56 | {
57 | test: /\.js$/,
58 | exclude: /node_modules/,
59 | use: {
60 | loader: 'babel-loader?cacheDirectory',
61 | options: {
62 | 'presets': [ '@babel/preset-env', '@babel/preset-react'],
63 | plugins: [
64 | ['@babel/plugin-proposal-decorators',{ 'legacy': true }],
65 | '@babel/plugin-proposal-class-properties',
66 | // 配合路由懒加载
67 | '@babel/plugin-syntax-dynamic-import',
68 | '@babel/plugin-transform-runtime'
69 | ]
70 | }
71 | }
72 | },
73 | {
74 | test: /\.css$/,
75 | use: [
76 | MiniCssExtractPlugin.loader,
77 | 'css-loader',
78 | { loader: 'postcss-loader', options: { plugins: [ require('autoprefixer') ]}}
79 | ]
80 | },
81 | {
82 | test:/\.less$/,
83 | use:[
84 | MiniCssExtractPlugin.loader,
85 | 'css-loader',
86 | { loader: 'postcss-loader', options: { plugins: [ require('autoprefixer') ] }},
87 | 'less-loader'
88 | ]
89 | },
90 | {
91 | test: /\.(png|jpg|jpeg|gif)$/,
92 | use: [
93 | {
94 | loader: 'url-loader',
95 | options: {
96 | name: "[name]-[hash:5].min.[ext]",
97 | limit: 1024,
98 | publicPath: "../images/",
99 | outputPath: "images/"
100 | }
101 | },
102 | {
103 | loader: 'img-loader'
104 | }
105 | ]
106 | },
107 | {
108 | test: /\.html$/,
109 | loader: 'html-loader',
110 | }
111 | ]
112 | },
113 | resolve: {
114 | extensions: ["*",".jsx",".js"],
115 | alias: devPaths
116 | },
117 | optimization: {
118 | splitChunks: {
119 | cacheGroups: {
120 | common: {
121 | test: /[\\/]node_modules[\\/]/,
122 | name: 'common',
123 | chunks: 'all',
124 | priority: 2,
125 | minChunks: 2,
126 | },
127 | }
128 | }
129 | },
130 | plugins: [
131 | new OptimizeCssAssetsPlugin(),
132 | new CopyPlugin([{
133 | from: path.join(__dirname, './package.json'),
134 | to: path.join(__dirname, '/dist/package.json')
135 | }]),
136 | new HtmlWebpackPlugin({
137 | template: __dirname + '/src/web/app/index.html',
138 | //development 环境使用 webpack-dev-middleware 插件打包资源到内存
139 | //production 环境打包到 dist/view 作为 node 的 html 模板
140 | filename: _mode === 'production' ? '../views/index.html' : 'index.html',
141 | inject: false,
142 | minify: false,
143 | favicon: __dirname + '/src/assets/favicon.ico'
144 | }),
145 | new htmlAfterWebpackPlugin()
146 | ]
147 | }
148 |
149 | module.exports = merge(webpackConfig, _mergeConfig)
--------------------------------------------------------------------------------
/开发人员使用手册.md:
--------------------------------------------------------------------------------
1 | # 开发人员使用手册
2 |
3 | ## 代码各部分简介
4 |
5 | 一共分为四个文件夹:config,profiles,src,dist
6 |
7 | ### config文件夹
8 | 对webpack打包过程进行了针对性配置
9 |
10 | ### profiles文件夹
11 | 对开发和生产两种不同环境来配置不同的项目启动端口,IP,后台接口地址和公共路径
12 |
13 | ### src文件夹
14 |
15 | * assets
16 | 存放静态文件的目录,如图片、字体等,不存放代码类文件,如CSS、JavaScript
17 | * nodeuii
18 | node部分的代码,配置路由和中间件
19 | * web
20 | 为主要不断完善维护部分,app文件夹为页面逻辑,sdk文件夹为与后台交互逻辑
21 |
22 | ### dist文件夹
23 | 运行打包命令之后会打包进该文件夹,进行dev打包时静态文件会在本机内存中进行运行,线上环境打包时会将静态文件同时打包进该文件夹
24 |
25 | # 功能清单
26 |
27 | | **功能** | **功能说明** |
28 | | ------------ | ------------------------------------------------------------ |
29 | | 单聊 | 两人聊天 |
30 | | 群聊 | 多人聊天,群个数无上限,单个群的群人数无上限;支持创建群、添加群成员、退出群聊支持修改群名及群公告 |
31 | | 消息类型 | 支持文本、语音、图片、视频、音视频、表情、文件等 |
32 | | 已读未读标识 | 消息已读未读提示 |
33 | | 会话加密 | 除会话双方客服端,会话消息不会在任何地方留存 |
34 | | @ | 群组中,当@某人时,被@的人会收到提示 |
35 | | 置顶 | 重要群组及联系人会话置顶,被置顶的会话展示在列表最上方 |
36 | | 消息提醒 | 未读消息数提示 |
37 | | 引用 | 引用提问回复,避免群内消息量大时上下文连不上 |
38 | | Push | 支持通过push提示新消息 |
39 | | 多端同步 | 支持消息在移动端、PC端同步 |
40 | | 全局搜索 | 支持搜索联系人、群组、共同群组 |
41 | | 组织架构 | 支持多级树形组织架构 |
42 | | 好友 | 支持查看好友 |
43 | | 群组 | 展示所有群组,快速进入群组 |
44 |
45 | # 接口汇总
46 |
47 | | | 接口 | 接口路径 |
48 | | :--- | :--------------------- | :----------------------------------------------- |
49 | | 1 | 获取用户名片信息 | /newapi/domain/get_vcard_info.qunar |
50 | | 2 | 获取用户个性签名 | /newapi/domain/get_vcard_info.qunar |
51 | | 3 | 更新用户个性签名 | /newapi/profile/set_profile.qunar |
52 | | 4 | 获取群名片 | /newapi/muc/get_muc_vcard.qunar |
53 | | 5 | 获取组织架构 | /newapi/getUpdateUsers.qunar |
54 | | 6 | 更新群名片 | /newapi/muc/set_muc_vcard.qunar |
55 | | 7 | 获取置顶信息 | /newapi/configuration/getincreclientconfig.qunar |
56 | | 8 | 设置置顶 | /newapi/configuration/setclientconfig.qunar |
57 | | 9 | 群列表 | newapi/muc/get_increment_mucs.qunar |
58 | | 10 | 获取状态码 | newapi/domain/get_user_status.qunar |
59 | | 11 | 获取直属领导,员工编号 | 需使用者自己实现 |
60 | | 12 | 查询用户电话 | 需使用者自己实现 |
61 | | 13 | 获取会话列表 | /package/qtapi/getrbl.qunar |
62 | | 14 | 获取单人历史消息 | /package/qtapi/getmsgs.qunar |
63 | | 15 | 获取群历史消息 | /package/qtapi/getmucmsgs.qunar |
64 | | 17 | 查询用户和群组 | /py/search |
65 | | 18 | php获取域列表 | /newapi/domain/get_domain_list.qunar?t=qtalk |
66 |
67 |
--------------------------------------------------------------------------------